Here is a snippet that I use in my base view controller class to handle scrolling views that would be hidden by the keyboard when it appears. Any view within a UIScrollView can be made to scroll automatically to be visible when the keyboard appears over it.
This is based on the following Apple documentation example: Managing the Keyboard: Moving Content That Is Located Under the Keyboard.
This differs from the Apple example in that it uses the UIKeyboardWillShowNotification instead of UIKeyboardDidShowNotification, and also animates resetting the scrolling when hiding the keyboard. The code can also handle content that might already be scrolled and I have found that the positioning of the UIView’s is more accurate in complicated view hierarchies.
I place this in my base view controller “BaseViewController” which I descend all my other custom UIViewController’s from. You could put this in each view controller that needs it directly if you wanted.
If I want to support auto keyboard scrolling in a particular view controller, I simply put a UIScrollView at the top of the view hierarchy and any view that is the first responder within this UIScrollView when the UIKeyboard is shown will be scrolled into view.
When I need to control what is scrolled into view I override the GetKeyboardActiveView. E.g. for a login view where I want the login button to also be visible I will return the UIButton or perhaps a view that contains/defines the bounds of all the login text fields and button. Then when the password field is active, the login button is also visible.
public override void ViewDidLoad () { base.ViewDidLoad (); // Setup keyboard event handlers RegisterForKeyboardNotifications(); } public override void ViewDidUnload() { base.ViewDidUnload(); UnregisterKeyboardNotifications(); } NSObject _keyboardObserverWillShow; NSObject _keyboardObserverWillHide; protected virtual void RegisterForKeyboardNotifications () { _keyboardObserverWillShow = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillShowNotification, KeyboardWillShowNotification); _keyboardObserverWillHide = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillHideNotification, KeyboardWillHideNotification); } protected virtual void UnregisterKeyboardNotifications() { NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardObserverWillShow); NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardObserverWillHide); } /// <summary> /// Gets the UIView that represents the "active" user input control (e.g. textfield, or button under a text field) /// </summary> /// <returns> /// A <see cref="UIView"/> /// </returns> protected virtual UIView KeyboardGetActiveView() { return this.View.FindFirstResponder(); } protected virtual void KeyboardWillShowNotification (NSNotification notification) { UIView activeView = KeyboardGetActiveView(); if (activeView == null) return; UIScrollView scrollView = activeView.FindSuperviewOfType(this.View, typeof(UIScrollView)) as UIScrollView; if (scrollView == null) return; RectangleF keyboardBounds = UIKeyboard.BoundsFromNotification(notification); UIEdgeInsets contentInsets = new UIEdgeInsets(0.0f, 0.0f, keyboardBounds.Size.Height, 0.0f); scrollView.ContentInset = contentInsets; scrollView.ScrollIndicatorInsets = contentInsets; // If activeField is hidden by keyboard, scroll it so it's visible RectangleF viewRectAboveKeyboard = new RectangleF(this.View.Frame.Location, new SizeF(this.View.Frame.Width, this.View.Frame.Size.Height - keyboardBounds.Size.Height)); RectangleF activeFieldAbsoluteFrame = activeView.Superview.ConvertRectToView(activeView.Frame, this.View); // activeFieldAbsoluteFrame is relative to this.View so does not include any scrollView.ContentOffset // Check if the activeField will be partially or entirely covered by the keyboard if (!viewRectAboveKeyboard.Contains(activeFieldAbsoluteFrame)) { // Scroll to the activeField Y position + activeField.Height + current scrollView.ContentOffset.Y - the keyboard Height PointF scrollPoint = new PointF(0.0f, activeFieldAbsoluteFrame.Location.Y + activeFieldAbsoluteFrame.Height + scrollView.ContentOffset.Y - viewRectAboveKeyboard.Height); scrollView.SetContentOffset(scrollPoint, true); } } protected virtual void KeyboardWillHideNotification (NSNotification notification) { UIView activeView = KeyboardGetActiveView(); if (activeView == null) return; UIScrollView scrollView = activeView.FindSuperviewOfType (this.View, typeof(UIScrollView)) as UIScrollView; if (scrollView == null) return; // Reset the content inset of the scrollView and animate using the current keyboard animation duration double animationDuration = UIKeyboard.AnimationDurationFromNotification(notification); UIEdgeInsets contentInsets = new UIEdgeInsets(0.0f, 0.0f, 0.0f, 0.0f); UIView.Animate(animationDuration, delegate{ scrollView.ContentInset = contentInsets; scrollView.ScrollIndicatorInsets = contentInsets; }); }
Here are the UIView extensions for FindFirstResponder and FindSuperviewOfType:
public static class ViewExtensions { /// <summary> /// Find the first responder in the <paramref name="view"/>'s subview hierarchy /// </summary> /// <param name="view"> /// A <see cref="UIView"/> /// </param> /// <returns> /// A <see cref="UIView"/> that is the first responder or null if there is no first responder /// </returns> public static UIView FindFirstResponder (this UIView view) { if (view.IsFirstResponder) { return view; } foreach (UIView subView in view.Subviews) { var firstResponder = subView.FindFirstResponder(); if (firstResponder != null) return firstResponder; } return null; } /// <summary> /// Find the first Superview of the specified type (or descendant of) /// </summary> /// <param name="view"> /// A <see cref="UIView"/> /// </param> /// <param name="stopAt"> /// A <see cref="UIView"/> that indicates where to stop looking up the superview hierarchy /// </param> /// <param name="type"> /// A <see cref="Type"/> to look for, this should be a UIView or descendant type /// </param> /// <returns> /// A <see cref="UIView"/> if it is found, otherwise null /// </returns> public static UIView FindSuperviewOfType(this UIView view, UIView stopAt, Type type) { if (view.Superview != null) { if (type.IsAssignableFrom(view.Superview.GetType())) { return view.Superview; } if (view.Superview != stopAt) return view.Superview.FindSuperviewOfType(stopAt, type); } return null; } }
Don’t know how you haven’t got any comments saying this already, but this is excellent – thank you!
This worked great and was exactly what I was looking for! Thanks!!
Outstanding! I had a version I was pretty proud of written in Objective-C — very similar to this conceptually. On a deadline and was not looking forward to porting mine. Yours works perfectly for me.
In the KeyboardWillShowNotification method, the following line needs to be replaced (obsolete API):
RectangleF keyboardBounds = UIKeyboard.BoundsFromNotification(notification);
with this one:
RectangleF keyboardBounds = UIKeyboard.FrameBeginFromNotification(notification);
Thanks!
Thanks Tony,
I haven’t checked it against iOS 5.0 yet, so thanks for that.
Cheers,
J
Thanks so much – what a timesaver
This should be in the hall of fame of MonoTouch code! Stackoverflow is overrated ;-)
I also fix one UI Bug.
When Textbox is partially overlay by keyboard, it does not scroll.
Change:
// Check if the activeField will be partially or entirely covered by the keyboard
if (!viewRectAboveKeyboard.Contains(activeFieldAbsoluteFrame))
{
// Scroll to the activeField Y position + activeField.Height + current scrollView.ContentOffset.Y – the keyboard Height
PointF scrollPoint = new PointF(0.0f, activeFieldAbsoluteFrame.Location.Y + activeFieldAbsoluteFrame.Height + scrollView.ContentOffset.Y – viewRectAboveKeyboard.Height);
scrollView.SetContentOffset(scrollPoint, true);
}
To:
// Scroll to the activeField Y position + activeField.Height + current scrollView.ContentOffset.Y – the keyboard Height
PointF scrollPoint = new PointF (0.0f, activeFieldAbsoluteFrame.Location.Y + activeFieldAbsoluteFrame.Height + scrollView.ContentOffset.Y – viewRectAboveKeyboard.Height);
// Check if the activeField will be partially or entirely covered by the keyboard
if (viewRectAboveKeyboard.IntersectsWith (activeFieldAbsoluteFrame) &&
! viewRectAboveKeyboard.Contains (activeFieldAbsoluteFrame)) {
scrollView.SetContentOffset (scrollPoint, true);
} else if (!viewRectAboveKeyboard.Contains (activeFieldAbsoluteFrame)) {
scrollView.SetContentOffset (scrollPoint, true);
}
Ok, I’m a beginner here. What do I do with the UIView Extensions to make them get used by my UIViewController?
Just make sure you are referencing the namespace that you have put them in and they will be available on any UIView instance.
Works like a charme. Thanks a lot.
With iOS 6 and iPad version I had problem, so here is my code, which works great.
First, replace the ViewDidUnload by ViewDidDisappear (bool animated) and and ViewDidLoad by ViewWillAppear (bool animated)
(base constructor will take animated args).
Then, replace all the KeyboardWillShowNotification (NSNotification notification)
by
protected virtual void KeyboardWillShowNotification (NSNotification notification)
{
UIView activeView = KeyboardGetActiveView();
if (activeView == null)
return;
UIScrollView scrollView = activeView.FindSuperviewOfType(this.View, typeof(UIScrollView)) as UIScrollView;
if (scrollView == null)
return;
RectangleF keyboardBounds = UIKeyboard.FrameBeginFromNotification(notification);
UIEdgeInsets contentInsets = new UIEdgeInsets(0.0f, 0.0f, keyboardBounds.Size.Height, 0.0f);
scrollView.ContentInset = contentInsets;
scrollView.ScrollIndicatorInsets = contentInsets;
// If activeField is hidden by keyboard, scroll it so it’s visible
RectangleF viewRectAboveKeyboard = new RectangleF(this.View.Frame.Location, new SizeF(this.View.Frame.Width, this.View.Frame.Size.Height – keyboardBounds.Size.Height * 0.5f));
RectangleF activeFieldAbsoluteFrame = activeView.Superview.ConvertRectToView(activeView.Frame, this.View);
// activeFieldAbsoluteFrame is relative to this.View so does not include any scrollView.ContentOffset
// Check if the activeField will be partially or entirely covered by the keyboard
if (!viewRectAboveKeyboard.Contains(activeFieldAbsoluteFrame))
{
// Scroll to the activeField Y position + activeField.Height + current scrollView.ContentOffset.Y – the keyboard Height
PointF scrollPoint = new PointF(0.0f, activeFieldAbsoluteFrame.Location.Y – viewRectAboveKeyboard.Height * 0.5f);
scrollView.SetContentOffset(scrollPoint, true);
}
}
I am a beginner. I created a baseviewcontroller and copied all the above code to it. I have inherited it to my signupviewcontroller like this
public partial class signupviewcontroller:baseviewcontroller
and the doubt is about the constructor here.
In signupviewcontroller the constructor is like this.
public signupviewcontroller () : base (“signupviewcontroller”,null)
{
}
and in the baseviewcontroller it is like this
public baseviewcontroller () : base (“baseviewcontroller”,null)
{
}
In this way it shows error- there is no 2 arguments for the constructor in baseviewcontroller. What is the right way to write the constructor? please reply me soon
It’s been a while since I’ve looked at this code, but try creating another constructor override in the baseviewcontroller that accepts the string etc also.
This worked great………. Thanks so much :)
Thanks, helped a lot