【问题标题】:How do I keep the keyboard from covering my UI instead of resizing it?如何防止键盘覆盖我的 UI 而不是调整它的大小?
【发布时间】:2015-09-19 06:30:50
【问题描述】:

在 iOS 中,当根节点为 ScrollView 时,Xamarin.Forms 会在键盘出现时调整屏幕大小。但是当根节点不是ScrollView 时,键盘会隐藏部分 UI。你如何防止这种情况发生?

【问题讨论】:

    标签: ios xamarin.forms


    【解决方案1】:

    解决此问题的方法是使用自定义渲染器,该渲染器侦听键盘出现并在键盘出现时添加填充。

    在您的 PCL 项目中,KeyboardResizingAwareContentPage.cs:

    using Xamarin.Forms;
    
    public class KeyboardResizingAwareContentPage : ContentPage {
        public bool CancelsTouchesInView = true;
    }
    

    在您的 iOS 项目中,IosKeyboardFixPageRenderer.cs:

    using Foundation;
    using MyProject.iOS.Renderers;
    using UIKit;
    using Xamarin.Forms;
    using Xamarin.Forms.Platform.iOS;
    
    [assembly: ExportRenderer(typeof(KeyboardResizingAwareContentPage), typeof(IosKeyboardFixPageRenderer))]
    
    namespace MyProject.iOS.Renderers {
        public class IosKeyboardFixPageRenderer : PageRenderer {
            NSObject observerHideKeyboard;
            NSObject observerShowKeyboard;
    
            public override void ViewDidLoad()
            {
                base.ViewDidLoad();
    
                var cp = Element as KeyboardResizingAwareContentPage;
                if (cp != null && !cp.CancelsTouchesInView) {
                    foreach (var g in View.GestureRecognizers) {
                        g.CancelsTouchesInView = false;
                    }
                }
            }
    
            public override void ViewWillAppear(bool animated)
            {
                base.ViewWillAppear(animated);
    
                observerHideKeyboard = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, OnKeyboardNotification);
                observerShowKeyboard = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, OnKeyboardNotification);
            }
    
            public override void ViewWillDisappear(bool animated)
            {
                base.ViewWillDisappear(animated);
    
                NSNotificationCenter.DefaultCenter.RemoveObserver(observerHideKeyboard);
                NSNotificationCenter.DefaultCenter.RemoveObserver(observerShowKeyboard);
            }
    
            void OnKeyboardNotification(NSNotification notification)
            {
                if (!IsViewLoaded) return;
    
                var frameBegin = UIKeyboard.FrameBeginFromNotification(notification);
                var frameEnd = UIKeyboard.FrameEndFromNotification(notification);
    
                var page = Element as ContentPage;
                if (page != null && !(page.Content is ScrollView)) {
                    var padding = page.Padding;
                    page.Padding = new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom + frameBegin.Top - frameEnd.Top);
                }
            }
        }
    }
    

    【讨论】:

    • 结果很好,但是如何隐藏键盘呢?因为如果我在键盘外点击不会关闭...
    • @Fran_gg7 使用 ResignFirstResponder()。所以“激活键盘的控件” ResignFirstResponder();
    • Xamarin Forms 应该在页面上有一个手势识别器,它会自动为您调用 ResignFirstResponder。有时您不想要这种行为,这就是 CancelsTouchesInView 布尔值的用途;将其设置为 false 以取消 Xamarin Forms 的默认行为。
    • 我来自 Xamarin 论坛。我真的很感激你的一段代码。 :)
    • @AnthonyMills 这个解决方案很棒,但是有一个问题。如果其中一个条目被标记为 IsPassword,则页面滚动不规律:)
    【解决方案2】:

    我发现KeyboardOverlap plugin 比 Anthony 的解决方案效果更好。

    我就是这样使用它的:

    1. 创建自定义渲染器
    public class KeyboardResizingAwareContentPage : ContentPage
    {
    }
    
    1. 在 iOS 上实现自定义渲染器。这是paulpatarinski代码的重要部分:
    [Preserve (AllMembers = true)]
    public class KeyboardOverlapRenderer : PageRenderer
    {
        NSObject _keyboardShowObserver;
        NSObject _keyboardHideObserver;
        private bool _pageWasShiftedUp;
        private double _activeViewBottom;
        private bool _isKeyboardShown;
    
        public static void Init ()
        {
            var now = DateTime.Now;
            Debug.WriteLine ("Keyboard Overlap plugin initialized {0}", now);
        }
    
        public override void ViewWillAppear (bool animated)
        {
            base.ViewWillAppear (animated);
    
            var page = Element as ContentPage;
    
            if (page != null) {
                var contentScrollView = page.Content as ScrollView;
    
                if (contentScrollView != null)
                    return;
    
                RegisterForKeyboardNotifications ();
            }
        }
    
        public override void ViewWillDisappear (bool animated)
        {
            base.ViewWillDisappear (animated);
    
            UnregisterForKeyboardNotifications ();
        }
    
        void RegisterForKeyboardNotifications ()
        {
            if (_keyboardShowObserver == null)
                _keyboardShowObserver = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillShowNotification, OnKeyboardShow);
            if (_keyboardHideObserver == null)
                _keyboardHideObserver = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillHideNotification, OnKeyboardHide);
        }
    
        void UnregisterForKeyboardNotifications ()
        {
            _isKeyboardShown = false;
            if (_keyboardShowObserver != null) {
                NSNotificationCenter.DefaultCenter.RemoveObserver (_keyboardShowObserver);
                _keyboardShowObserver.Dispose ();
                _keyboardShowObserver = null;
            }
    
            if (_keyboardHideObserver != null) {
                NSNotificationCenter.DefaultCenter.RemoveObserver (_keyboardHideObserver);
                _keyboardHideObserver.Dispose ();
                _keyboardHideObserver = null;
            }
        }
    
        protected virtual void OnKeyboardShow (NSNotification notification)
        {
            if (!IsViewLoaded || _isKeyboardShown)
                return;
    
            _isKeyboardShown = true;
            var activeView = View.FindFirstResponder ();
    
            if (activeView == null)
                return;
    
            var keyboardFrame = UIKeyboard.FrameEndFromNotification (notification);
            var isOverlapping = activeView.IsKeyboardOverlapping (View, keyboardFrame);
    
            if (!isOverlapping)
                return;
    
            if (isOverlapping) {
                _activeViewBottom = activeView.GetViewRelativeBottom (View);
                ShiftPageUp (keyboardFrame.Height, _activeViewBottom);
            }
        }
    
        private void OnKeyboardHide (NSNotification notification)
        {
            if (!IsViewLoaded)
                return;
    
            _isKeyboardShown = false;
            var keyboardFrame = UIKeyboard.FrameEndFromNotification (notification);
    
            if (_pageWasShiftedUp) {
                ShiftPageDown (keyboardFrame.Height, _activeViewBottom);
            }
        }
    
        private void ShiftPageUp (nfloat keyboardHeight, double activeViewBottom)
        {
            var pageFrame = Element.Bounds;
    
            var newY = pageFrame.Y + CalculateShiftByAmount (pageFrame.Height, keyboardHeight, activeViewBottom);
    
            Element.LayoutTo (new Rectangle (pageFrame.X, newY,
                pageFrame.Width, pageFrame.Height));
    
            _pageWasShiftedUp = true;
        }
    
        private void ShiftPageDown (nfloat keyboardHeight, double activeViewBottom)
        {
            var pageFrame = Element.Bounds;
    
            var newY = pageFrame.Y - CalculateShiftByAmount (pageFrame.Height, keyboardHeight, activeViewBottom);
    
            Element.LayoutTo (new Rectangle (pageFrame.X, newY,
                pageFrame.Width, pageFrame.Height));
    
            _pageWasShiftedUp = false;
        }
    
        private double CalculateShiftByAmount (double pageHeight, nfloat keyboardHeight, double activeViewBottom)
        {
            return (pageHeight - activeViewBottom) - keyboardHeight;
        }
    }
    

    还有缺少的扩展:

    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>
        /// Returns the new view Bottom (Y + Height) coordinates relative to the rootView
        /// </summary>
        /// <returns>The view relative bottom.</returns>
        /// <param name="view">View.</param>
        /// <param name="rootView">Root view.</param>
        public static double GetViewRelativeBottom (this UIView view, UIView rootView)
        {
            var viewRelativeCoordinates = rootView.ConvertPointFromView (view.Frame.Location, view);
            var activeViewRoundedY = Math.Round (viewRelativeCoordinates.Y, 2);
    
            return activeViewRoundedY + view.Frame.Height;
        }
    
        /// <summary>
        /// Determines if the UIView is overlapped by the keyboard
        /// </summary>
        /// <returns><c>true</c> if is keyboard overlapping the specified activeView rootView keyboardFrame; otherwise, <c>false</c>.</returns>
        /// <param name="activeView">Active view.</param>
        /// <param name="rootView">Root view.</param>
        /// <param name="keyboardFrame">Keyboard frame.</param>
        public static bool IsKeyboardOverlapping (this UIView activeView, UIView rootView, CGRect keyboardFrame)
        {
            var activeViewBottom = activeView.GetViewRelativeBottom (rootView);
            var pageHeight = rootView.Frame.Height;
            var keyboardHeight = keyboardFrame.Height;
    
            var isOverlapping = activeViewBottom >= (pageHeight - keyboardHeight);
    
            return isOverlapping;
        }
    }
    
    1. 使用自定义页面渲染器
    public partial class LoginPage : KeyboardResizingAwareContentPage
    {
        public LoginPage()
        {
            // your content
            // note: you have to use base.Navigation.PushAsync(), base.DisplayAlert(), ...
        }
    }
    
    <?xml version="1.0" encoding="utf-8" ?>
    <renderer:KeyboardResizingAwareContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="App.Pages.LoginPage"
         xmlns:renderer="clr-namespace:App.CustomRenderers;assembly=App">
        <!-- your content -->
    </renderer:KeyboardResizingAwareContentPage>
    

    这一切归功于保罗!谢谢!

    【讨论】:

    • 这个解决方案也会触发(来自生产日志):System.ObjectDisposedException:无法访问已处置的对象。对象名称:'IosKeyboardFixPageRenderer'。
    • _isKeyboardShown = false;应该在方法 UnregisterForKeyboardNotifications 的末尾移动
    【解决方案3】:

    Xamarin Forum question 讨论它。

    此外,如果您希望在 Android 中使用 Xamarin / Forms,您可以在主 Activity 中进行设置:

    [Activity(WindowSoftInputMode = Android.Views.SoftInput.AdjustResize)]
    public class MainActivity
        ...
    

    【讨论】:

      【解决方案4】:

      对于Android,在MainActivityLoadApplication(new App());之后添加以下代码

      App.Current.On<Xamarin.Forms.PlatformConfiguration.Android>().
      UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-16
        • 1970-01-01
        • 2020-08-22
        • 1970-01-01
        • 2021-02-19
        • 1970-01-01
        相关资源
        最近更新 更多