I have a view that contains a UIScrollview
with various UITextField
s contained within the scrollview. When I tap on an UITextField
to enter data, the UITextField
scrolls up unter the subviews above the UIScrolView
and is hidden from view. I'm using a kewboard notification solution I found here on StackOverflow
, but it doesn't work when a scrollview is present. What do I need to do to force the entire view up so that the text entry is visible?
My Code:
public override void ViewDidLoad()
{
this.NavigationItem.SetHidesBackButton(true, false);
btnExit.Hidden = false;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
var NavBarHeight = UIApplication.SharedApplication.StatusBarFrame.Size.Height +
(NavigationController?.NavigationBar.Frame.Height ?? 0.0);
RegisterForKeyboardNotifications();
scrollView = new UIScrollView
{
Frame = new CGRect(0, 0, View.Frame.Width, h + 2 * padding),
ContentSize = new SizeF((w + padding) * 7, (h + padding) * n),
BackgroundColor = UIColor.White,
AutoresizingMask = UIViewAutoresizing.FlexibleWidth
};
.
.
.
. Bunch of irrelevant code creating subviews to add to scrollview
.
.
.
scrollView.Add(lblDOB);
scrollView.Add(txtDOB);
scrollView.Add(lblFirstName);
scrollView.Add(txtFirstName);
scrollView.Add(lblMI);
scrollView.Add(txtMI);
scrollView.Add(lblLastName);
scrollView.Add(txtLastName);
scrollView.Add(lblSuffix);
scrollView.Add(txtSuffix);
scrollView.Add(lblAddress);
scrollView.Add(txtAddress);
scrollView.Add(lblCity);
scrollView.Add(txtCity);
scrollView.Add(lblState);
scrollView.Add(txtState);
scrollView.Add(lblZip);
scrollView.Add(txtZip);
View.Add(scrollView);
scrollView.BackgroundColor = UIKit.UIColor.White;
scrollView.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
scrollView.AddConstraints(
lblDOB.AtTopOf(scrollView, 0),
lblDOB.AtLeftOf(scrollView, 0),
txtDOB.ToRightOf(lblDOB, 2),
txtDOB.Width().EqualTo(250.0f),
lblFirstName.Below(lblDOB, 5),
txtFirstName.Below(lblDOB, 0).Plus(5),
txtFirstName.ToRightOf(lblFirstName, 2),
txtFirstName.Width().EqualTo(250.0f),
lblMI.Below(lblFirstName, 5),
txtMI.Below(lblFirstName, 0).Plus(5),
txtMI.ToRightOf(lblMI, 2),
txtMI.Width().EqualTo(50.0f),
lblLastName.Below(lblMI, 5),
txtLastName.Below(lblMI, 0).Plus(5),
txtLastName.ToRightOf(lblLastName, 2),
txtLastName.Width().EqualTo(250.0f),
lblSuffix.Below(lblLastName, 5),
txtSuffix.Below(lblLastName, 0).Plus(5),
txtSuffix.ToRightOf(lblSuffix, 2),
txtSuffix.Width().EqualTo(50.0f),
lblAddress.Below(lblSuffix, 5),
txtAddress.Below(lblSuffix, 0).Plus(5),
txtAddress.ToRightOf(lblAddress, 2),
txtAddress.Width().EqualTo(250.0f),
lblCity.Below(lblAddress, 5),
txtCity.Below(lblAddress, 0).Plus(5),
txtCity.ToRightOf(lblCity, 2),
txtCity.Width().EqualTo(250.0f),
lblState.Below(lblCity, 5),
txtState.Below(lblCity, 0).Plus(5),
txtState.ToRightOf(lblState, 2),
txtState.Width().EqualTo(250.0f),
lblZip.Below(lblState, 5),
txtZip.Below(lblState, 0).Plus(5),
txtZip.ToRightOf(lblZip, 2),
txtZip.Width().EqualTo(250.0f)
);
View.SubviewsDoNotTranslateAutoresizingMaskIntoConstraints();
View.AddConstraints
(
banner.AtTopOf(View, (float)NavBarHeight),
banner.AtRightOf(View, 0),
banner.AtLeftOf(View, 0),
employeeimage.Below(banner, 2),
employeeimage.Height().EqualTo(180),
employeeimage.Width().EqualTo(180),
btnSave.AtTopOf(employeeimage),
btnSave.ToRightOf(employeeimage, 5),
btnExit.Below(btnSave),
btnExit.WithSameLeft(btnSave),
btnExit.WithSameWidth(btnSave),
lblName.Below(employeeimage, 2),
lblName.WithSameWidth(banner),
line.Below(lblName, 0),
line.WithSameWidth(banner),
line.Height().EqualTo(2),
scrollView.Below(line, 0),
scrollView.AtBottomOf(View, 10),
scrollView.AtLeftOf(View, 10),
scrollView.AtRightOf(View, 10)
);
The view displays correctly with all subviews being present. The code that is supposed to push the view up when the keyboard pops up is:
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);
}
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 = (RectangleF)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((PointF)this.View.Frame.Location, new SizeF((float)this.View.Frame.Width, (float)(this.View.Frame.Size.Height - keyboardBounds.Size.Height)));
RectangleF activeFieldAbsoluteFrame = (RectangleF)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, (float)(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;
});
}
View extensions class:
public static class ViewExtensions
{
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;
}
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;
}
}
When the DOB textview is tapped, the textview disappears under the subviews located above the scrollview, I've tried everything I can think of and various methods I've found online, but nothing seems to have any effect. Any suggestions?
Well, I was unable to make the code in the question work like I needed it to, but I did find this:
ViewController with scroll when appear keyboard
It's almost perfect, I installed the IQKeyboardManager
from NuGet and added the following code to my app in the initial viewcontroller:
Xamarin.IQKeyboardManager.SharedManager.EnableAutoToolbar = true;
Xamarin.IQKeyboardManager.SharedManager.ShouldResignOnTouchOutside = true;
Xamarin.IQKeyboardManager.SharedManager.ShouldToolbarUsesTextFieldTintColor = true;
Xamarin.IQKeyboardManager.SharedManager.KeyboardDistanceFromTextField = 150f;
I just need to figure out how to stop the scrollview from pushing up, but I can live with the default as the user can scroll the text field back into view, but I think I can get it to stop doing that. Otherwise, this works VERY well.
**** Update ****
I was able to stop the scrollview from scrolling the UITextField
up out of sight by using the UITextField
delegate. I added the following code to my view:
First I added IUITextFieldDelegate
to the view controller:
public partial class EmployeeInfo : UIViewController, IUITextFieldDelegate
Next I added the following code for each UITextField
:
MyUITextField.Delegate = this;
I did this for each and every UITextField
added to to scrollview.
Then added the following two functions in the ViewDidLoad()
:
[Export("textFieldDidBeginEditing:")]
public void EditingStarted(UITextField textfield)
{
this.scrollView.ScrollEnabled = false;// freeze the scrollview
CGPoint point = textfield.Frame.Location;
point.X = 0; // make sure they can see the identifying label
this.scrollView.SetContentOffset(point, true);
}
[Export("textFieldDidEndEditing:reason:")]
public void EditingEnded(UITextField textField, UITextFieldDidEndEditingReason reason)
{
this.scrollView.ScrollEnabled = true; // Re-enable the scrollview
}
If there's a drawback, it's that one cannot scroll the scrollview while editing a textview, but that's a minor issue. If you tap into another textfield, the scrollview position is updated.