Xamarin.iOS:UIScrollView内UITextField点击被遮挡问题求助
Xamarin.iOS ScrollView内UITextField被上方视图遮挡问题
在Xamarin.iOS开发中,视图内包含一个UIScrollView,其内部有多个UITextField。点击UITextField输入内容时,输入框会向上滚动并被ScrollView上方的子视图遮挡。采用了键盘通知方案但无效,比如点击DOB文本框时,文本框直接消失在ScrollView上方视图下方,求解决方法。
视图加载代码
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) ); }
键盘通知处理代码
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; }); }
视图扩展类代码
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; } }
解决方法
1. 修正滚动位置计算逻辑
原代码未考虑ScrollView在父视图中的偏移,导致顶部文本框滚动时被上方视图遮挡。修改KeyboardWillShowNotification方法:
protected virtual void KeyboardWillShowNotification(NSNotification notification) { UIView activeView = KeyboardGetActiveView(); if (activeView == null) return; UIScrollView scrollView = activeView.FindSuperviewOfType(typeof(UIScrollView)) as UIScrollView; if (scrollView == null) return; RectangleF keyboardBounds = (RectangleF)UIKeyboard.BoundsFromNotification(notification); var keyboardHeight = keyboardBounds.Size.Height; UIEdgeInsets contentInsets = new UIEdgeInsets(0.0f, 0.0f, keyboardHeight, 0.0f); scrollView.ContentInset = contentInsets; scrollView.ScrollIndicatorInsets = contentInsets; // 计算输入框在ScrollView内的本地坐标 var activeFieldFrameInScroll = scrollView.ConvertRectFromView(activeView.Frame, activeView.Superview); // 输入框底部的全局位置 var activeFieldBottom = activeFieldFrameInScroll.Y + activeFieldFrameInScroll.Height + scrollView.ContentOffset.Y; // 键盘顶部相对于ScrollView的位置 var keyboardTopInScroll = scrollView.Frame.Height - keyboardHeight; // 若输入框底部超过键盘顶部,调整滚动偏移 if (activeFieldBottom > keyboardTopInScroll) { var offsetY = activeFieldBottom - keyboardTopInScroll + 10; // 10为额外间距,避免紧贴键盘 scrollView.SetContentOffset(new PointF(0, scrollView.ContentOffset.Y + offsetY), true); } }
2. 修复FindSuperviewOfType扩展方法
原方法因stopAt参数限制,无法正确找到ScrollView。修改扩展方法:
public static UIView FindSuperviewOfType(this UIView view, Type type) { var current = view.Superview; while (current != null) { if (type.IsAssignableFrom(current.GetType())) { return current; } current = current.Superview; } return null; }
3. 确保ScrollView的ContentSize正确
移除手动设置的ContentSize,在ScrollView的约束末尾添加底部关联约束,让ScrollView自动计算内容尺寸:
txtZip.AtBottomOf(scrollView, padding)
4. 可选:设置ScrollView顶部内边距
如果上方视图固定遮挡ScrollView顶部区域,添加顶部内边距避免内容被遮挡:
scrollView.ContentInset = new UIEdgeInsets(20, 0, 0, 0); // 20替换为实际遮挡高度
内容的提问来源于stack exchange,提问作者Prescott Chartier
相关产品推荐
相关产品推荐

