You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 18:05:54