/
CustomButton_IOS.cs
156 lines (137 loc) · 6.33 KB
/
CustomButton_IOS.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
using System;
using System.Threading.Tasks;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using XamarinExamples.Forms.MoveWithKeyboard.iOS.Renderers;
using XamarinExamples.Forms.MoveWithKeyboard.Renderers;
[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButton_IOS))]
namespace XamarinExamples.Forms.MoveWithKeyboard.iOS.Renderers
{
/// <summary>
/// Custom Button renderer for IOS that shows how to
/// move a forms element (this example is a button) when
/// the soft keyboard appears.
///
/// This prevents the keyboard from hiding the element
/// </summary>
public class CustomButton_IOS : ButtonRenderer
{
//Y Coordinate Tracking
nfloat _originalY = 0;
nfloat _aboveKeyboardY = 0;
//State Tracking
bool _keyboardNotificationSetup = false;
bool _keyboardVisible = false;
//Observers
NSObject _showObserver;
NSObject _hideObserver;
public CustomButton_IOS() { }
/// <summary>
/// Implementation of IDisposable
///
/// Cleans up any observers if they were setup
/// </summary>
/// <returns>The dispose.</returns>
/// <param name="disposing">If set to <c>true</c> disposing.</param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_showObserver != null)
NSNotificationCenter.DefaultCenter.RemoveObserver(_showObserver);
if (_hideObserver != null)
NSNotificationCenter.DefaultCenter.RemoveObserver(_hideObserver);
}
/// <summary>
/// Fired off when the button is firrst loaded and displayed to the user.
///
/// We track if the keyboard notifications are already setup to prevent duplicate
/// notifications being sent to NSNotificationCenter
/// </summary>
/// <param name="e">E.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if ((Control != null) && (e.NewElement != null))
{
var _customButton = (e.NewElement as CustomButton);
if (_customButton != null && _customButton.MoveWithKeyboard && !_keyboardNotificationSetup)
{
/*
* Some things I need to document here
*
* 1) These event notification delegates only allow the button touch events to still work if you reference "this"
* not "control". Using "Control" will not allow any of the touch events to fire... for some reason...
*
* 2) The hide event requires us to move the button async because having the move event here will result in
* the touch event not being fired (at least in the emulator). I should probably try it on the phone, but yeah,
* this one took half a day to figure out. That being said, this seems to work.
*
* My guess is that this is a race condition where the move is happening before TouchUp, and when the btuton is move
* the event sequence is interrupted. Will probably need to reach out to Xamarin about this one
*/
_showObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, (notification) =>
{
try
{
//Calculate new point and save original one
if (_originalY == 0)
_originalY = this.Center.Y;
if (_aboveKeyboardY == 0)
_aboveKeyboardY = _originalY - ((NSValue)notification.UserInfo.ObjectForKey(UIKeyboard.BoundsUserInfoKey)).RectangleFValue.Height;
//Move the Button
this.Center = new CoreGraphics.CGPoint() { X = this.Center.X, Y = _aboveKeyboardY };
_keyboardVisible = true;
return;
}
catch
{
return;
}
});
_hideObserver = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, (notification) =>
{
try
{
//For hardware keyboard, the show observer is never registered, and a keybaord is never displayed
if (!_keyboardVisible)
return;
//-- DOES NOT WORK: Will not allow "Clicked" event to be fired from the Form
// Bugzilla Report: https://bugzilla.xamarin.com/show_bug.cgi?id=58263
//this.Center = new CoreGraphics.CGPoint() { X = this.Center.X, Y = _originalY };
//-- THIS WORKS: Delays the move just long enough to actually register the touch event before moving
Task.Factory.StartNew(() => {
RestoreOriginalPosition();
});
_keyboardVisible = false;
return;
}
catch
{
return;
}
});
_keyboardNotificationSetup = true;
}
}
}
private void RestoreOriginalPosition()
{
InvokeOnMainThread(() =>
{
try
{
//Move the Button
if (!_keyboardVisible)
this.Center = new CoreGraphics.CGPoint() { X = this.Center.X, Y = _originalY };
}
catch
{
//This shouldn't hit since we're disposing properly...
return;
}
});
}
}
}