Xamarin.forms: ScrollView height exceeds actual size when On screen keyboard layout in the View in iOS platform.

Created on 4 May 2018  ·  7Comments  ·  Source: xamarin/Xamarin.Forms

Hi Team,

Description

In iOS platform, we have noticed a weird behavior while scrolling the list items when keyboard layout on the View. We have tried to load a ScrollView with several child elements including ListView. We have Entry element inside the ItemTemplate of ListView. While focusing the Entry element, the On screen keyboard comes into the View and pushes the content upwards. This creates a blank space between keyboard and ListView. Sometimes it doesn't actually push the View above the keyboard. At that time while we scroll the list upwards with keyboard enabled, we have noticed the space again. We have attached the issue replicating sample along with the Video cast to showcase this issue.

Steps to Reproduce

  1. Run the attached sample in iOS simulators or devices.
  2. Focus the entry element to layout the keyboard in the View.
  3. Scroll the list upwards, until you have noticed some white space between ListView and the Keyboard.

Expected Behavior

ScrollView height should be calculated properly when keyboard layout in the View.

Actual Behavior

When keyboard layout in the View, there is a blank space between ListView and Keyboard when scrolled to the end.

Basic Information

  • Version with issue: Xamarin.Forms (v2.5.1.527436)
  • Platform Target Frameworks: iOS

Reproduction Link

ListViewIssue.zip

3 excellent-report inactive iOS 🍎 needs-info ❓ bug up-for-grabs

Most helpful comment

Hi, this issue affects layout on our apps on iPhone X, and other shiny-iPhone-with-notch like XR, XS, XS Max. It breaks what design and UX team does.

All 7 comments

Hi Team,

Can anyone please let me know the progress of this issue fix?

Thanks.

Hi Team,

Any update on this?

Hi, this issue affects layout on our apps on iPhone X, and other shiny-iPhone-with-notch like XR, XS, XS Max. It breaks what design and UX team does.

Hi,
Any update? I have the same issue. I tried it also on the Xamarin.Forms 3.6.0.539721 and 4.2.0.608146-pre1. And I had the same issue.

I found one solution. This is temporary solution, I'm still waiting fixes in the framework :-)
I didn't use ScrollView control, I used just ListView with his own scroll, because two scrolls work incorrect.

I used custom KeyboardRender from here with some fixes. You should add this class in the IOS project:

[assembly: ExportRenderer(typeof(Page), typeof(KeyboardRender))]
namespace MyApp.iOS.Renderers
{
    [Preserve(AllMembers = true)]
    public class KeyboardRender : PageRenderer
    {
        NSObject _keyboardShowObserver;
        NSObject _keyboardHideObserver;
        private bool _pageWasShiftedUp;
        private double _activeViewBottom;
        private bool _isKeyboardShown;
        private double _viewHeight;
        public KeyboardRender() { }

        public new static void Init()
        {
            var now = DateTime.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;
            _viewHeight = pageFrame.Height;
            var newHeight = pageFrame.Height + CalculateShiftByAmount(pageFrame.Height, keyboardHeight, activeViewBottom);

            Element.LayoutTo(new Rectangle(pageFrame.X, pageFrame.Y,
               pageFrame.Width, newHeight));

            _pageWasShiftedUp = true;
        }

        private void ShiftPageDown(nfloat keyboardHeight, double activeViewBottom)
        {
            var pageFrame = Element.Bounds;

            var newHeight = activeViewBottom;

            Element.LayoutTo(new Rectangle(pageFrame.X, pageFrame.Y,
             pageFrame.Width, _viewHeight));

            _pageWasShiftedUp = false;
        }

        private double CalculateShiftByAmount(double pageHeight, nfloat keyboardHeight, double activeViewBottom)
        {
            return (pageHeight - activeViewBottom) - keyboardHeight;
        }
    }

    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 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;
        }

        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;
        }
    }

}

This issue doesn't seem to have had any activity in a long time. We're working on prioritizing issues and resolving them as quickly as we can. To help us get through the list, we would appreciate an update from you to let us know if this is still affecting you on the latest version of Xamarin.Forms, since it's possible that we may have resolved this as part of another related or duplicate issue. If we don't see any new activity on this issue in the next 30 days, we'll evaluate whether this issue should be closed. Thank you!

Since we haven't heard from you in more than 30 days, we hope this issue is no longer affecting you. If it is, please reopen this issue and provide the requested information so that we can look into it further. Thank you!

Was this page helpful?
0 / 5 - 0 ratings