question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

WindowSoftInputModeAdjust.Resize and Pan on iOS

See original GitHub issue

Description

It would be really great to have Property of ContentPage that enables a similar behaviour to WindowSoftInputModeAdjust.Resize and WindowSoftInputModeAdjust.Pan on Android for iOS. I think this would be possible by listening to the keyboard up event on iOS. There are a few tutorials showing this for Xamarin.Forms. This would be useful for any page with Entries or Editors. Moreover, I can’t really think of any scenario where anyone wants their content to be hidden by the keyboard on iOS. When Pan is chosen, the ContentPage should move up entirely, when Resize is chosen the bottom Padding of the Page could be changed. I have created a example. The animation of the keyboard up event could probably be improved (and should be added for the Resize mode), but I could not find an exact function of the iOS keyboard animation and the animation for padding could probably imitated if first the page is translated up and then afterwards the Translation is removed and Padding set.

This code would probably create some issues, if the ResizeMode is set for the whole App on Android and it might not work correctly when the Resize property is updated on runtime. However, this could be a good starting point and works fine in the scenarios I have tested.

Here is my code:

public partial class KeyboardContentPage : ContentPage
{
public static readonly BindableProperty ResizeProperty = BindableProperty.Create(nameof(Resize), typeof(bool), typeof(KeyboardContentPage), null);

        public bool Resize
        {
            get => (bool)GetValue(ResizeProperty);
            set => SetValue(ResizeProperty, value);
        }
    }

on Android:

public partial class KeyboardContentPage : ContentPage
    {
        public KeyboardContentPage()
        {
            if (Resize)
            {
                App.Current.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize);
            }
            else
            {
                App.Current.On<Microsoft.Maui.Controls.PlatformConfiguration.Android>().UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Pan);
            }
        }
    }

on iOS

public partial class KeyboardContentPage : ContentPage
    {
        NSObject _keyboardShowObserver;
        NSObject _keyboardHideObserver;

        public KeyboardContentPage()
        {
            RegisterForKeyboardNotifications();
        }

        ~KeyboardContentPage()
        {
            UnregisterForKeyboardNotifications();
        }

        private Thickness padding;
        private double? translationY;
        private bool lastAnimationType;

        async void OnKeyboardShow(object sender, UIKeyboardEventArgs args)
        {
            NSValue result = (NSValue)args.Notification.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
            CGSize keyboardSize = result.RectangleFValue.Size;

            Easing anim = Easing.SpringIn;

            NFloat bottom;
            try
            {
                UIWindow window = UIApplication.SharedApplication.Delegate.GetWindow();
                bottom = window.SafeAreaInsets.Bottom;
            }
            catch
            {
                bottom = 0;
            }
            var heightChange = (keyboardSize.Height - bottom);
            lastAnimationType = Resize;

            if (Resize)
            {
                padding = this.Padding;
                this.Padding = new Thickness(padding.Left, padding.Top, padding.Right, padding.Bottom + heightChange);
            }
            else
            {
                var duration = (uint)(args.AnimationDuration * 1000);
                translationY = this.Content.TranslationY;
                await this.Content.TranslateTo(0, translationY.Value - heightChange, duration, anim);
            }
        }

        async void OnKeyboardHide(object sender, UIKeyboardEventArgs args)
        {
            if (lastAnimationType)
            {
                this.Padding = padding;
            }
            else
            {
                Easing anim = Easing.CubicIn;
                if (this != null && translationY != null)
                {
                    var duration = (uint)(args.AnimationDuration * 1000);
                    await this.Content.TranslateTo(0, translationY.Value, duration, anim);
                }
            }
        }

        void RegisterForKeyboardNotifications()
        {
            if (_keyboardShowObserver == null)
                _keyboardShowObserver = UIKeyboard.Notifications.ObserveWillShow(OnKeyboardShow);
            if (_keyboardHideObserver == null)
                _keyboardHideObserver = UIKeyboard.Notifications.ObserveWillHide(OnKeyboardHide);
        }
        void UnregisterForKeyboardNotifications()
        {
            if (_keyboardShowObserver != null)
            {
                _keyboardShowObserver.Dispose();
                _keyboardShowObserver = null;
            }

            if (_keyboardHideObserver != null)
            {
                _keyboardHideObserver.Dispose();
                _keyboardHideObserver = null;
            }
        }
    }

Public API Changes

a new Property called ‘Resize’ on ContentPage. Maybe instead of a bool it would make sense to use an enum called ‘ResizeMode’, with ‘Pan’, ‘Resize’ and ‘None’ instead, where ‘None’ leaves the current behaviour (so the keyboard will hide elements on the bootom of the page on iOS).

Intended Use-Case

Move your content up like on Android on any page that uses Keyboards on iOS! Just set your desired Mode in Xaml ContenPage like this: <custom:KeyboardContentPage Resize="True" ..... >

Issue Analytics

  • State:open
  • Created a year ago
  • Comments:12

github_iconTop GitHub Comments

3reactions
borrmanncommented, Jan 18, 2023

@angelru Here is my code. At the moment I only use it in a chat window where the entry is below a collectionview. I can only say it works there. If the entry is within the collectionview or within a scrollview, it may need to be adjusted. However, it shows how to use the height change event of the keyboard.

public partial class KeyboardContentView : ContentView
    {
        NSObject _keyboardShowObserver;
        NSObject _keyboardHeightChangeObserver;
        NSObject _keyboardHideObserver;

        public KeyboardContentView()
        {
            RegisterForKeyboardNotifications();
        }

        ~KeyboardContentView()
        {
            UnregisterForKeyboardNotifications();
        }

        private double? originalTranslationY;
        private bool origTranslationSaved = false;

        private bool IsUpCompleted = false;
        private bool IsDownCompleted = false;

        private void StoreTranslation()
        {
            if (!origTranslationSaved )
            {
                origTranslationSaved = true;
                originalTranslationY = this.Content.TranslationY;
            }
        }
        
        private async Task SetHeight(UIKeyboardEventArgs args)
        {
            StoreTranslation();

            NFloat bottom;
            try
            {
                UIWindow window = UIApplication.SharedApplication.Delegate.GetWindow();
                bottom = window.SafeAreaInsets.Bottom;
            }
            catch
            {
                bottom = 0;
            }


            NSValue result = (NSValue)args.Notification.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
            CGSize keyboardSize = result.RectangleFValue.Size;

            Easing anim = Easing.SpringIn;

            var heightChange = (keyboardSize.Height - bottom);

            var duration = (uint)(args.AnimationDuration * 1000);
            await this.Content.TranslateTo(0, originalTranslationY.Value - heightChange, duration, anim);
        }

        async void OnKeyboardHeightChanged(object sender, UIKeyboardEventArgs args)
        {
            if (IsUpCompleted)
            {
                if (!IsDownCompleted)
                {
                    try
                    {
                        await SetHeight(args);
                    }
                    catch
                    {
                        Debug.WriteLine("Could not resize page");
                    }
                }
            }
        }

        async void OnKeyboardShow(object sender, UIKeyboardEventArgs args)
        {
            if (IsUpCompleted)
            {
                return;
            }
            try
            {
                await SetHeight(args);
                IsDownCompleted = false;
                IsUpCompleted = true;
            }
            catch
            {
                Debug.WriteLine("Could not resize page");
            }
        }

        async void OnKeyboardHide(object sender, UIKeyboardEventArgs args)
        {
            try
            {
                SetOrigPadding();

                IsDownCompleted = true;
                IsUpCompleted = false;

                Easing anim = Easing.CubicIn;
                if (this != null && originalTranslationY != null)
                {
                    var duration = (uint)(args.AnimationDuration * 1000);
                    await this.Content.TranslateTo(0, originalTranslationY.Value, duration, anim);
                }
            }
            catch
            {
                Debug.WriteLine("Could not resize page");
            }            
        }


        void RegisterForKeyboardNotifications()
        {
            if (_keyboardShowObserver == null)
                _keyboardShowObserver = UIKeyboard.Notifications.ObserveWillShow(OnKeyboardShow); 

            if (_keyboardHeightChangeObserver == null)
                _keyboardHeightChangeObserver = UIKeyboard.Notifications.ObserveWillChangeFrame(OnKeyboardHeightChanged);

            if (_keyboardHideObserver == null)
                _keyboardHideObserver = UIKeyboard.Notifications.ObserveWillHide(OnKeyboardHide);
        }
        void UnregisterForKeyboardNotifications()
        {
            if (_keyboardShowObserver != null)
            {
                _keyboardShowObserver.Dispose();
                _keyboardShowObserver = null;
            }

            if (_keyboardHeightChangeObserver != null)
            {
                _keyboardHeightChangeObserver.Dispose();
                _keyboardHeightChangeObserver = null;
            }

            if (_keyboardHideObserver != null)
            {
                _keyboardHideObserver.Dispose();
                _keyboardHideObserver = null;
            }
        }
    }
1reaction
greg84commented, Jul 23, 2023

Many thanks to @borrmann! I was (eventually!) able to come up with a workaround for my app.

Here’s the code I’m using: https://gist.github.com/greg84/0297569ef1052801a384aae9c75800cd

Some comments:

  • Some pages use Shell, so the tab bar height needs to be taken into account when the page is in a Shell (difference between Shell.Current.Height and ContentView.Height).
  • Some pages use ios:Page.UseSafeArea and others don’t, which affects whether the top safe area inset needs to be considered as part of the content height.
  • When a page is using Shell, the Height of the Shell object seems to already take into account bottom safe area inset?
Read more comments on GitHub >

github_iconTop Results From Across the Web

Xamarin Android WindowSoftInput Resize (specific page)
The WindowSoftInputModeAdjust.Pan is the default behaviour of Android when a keyboard is being shown. This way when your page disappears, ...
Read more >
Soft Keyboard Input Mode on Android - Xamarin
The Pan value uses the AdjustPan adjustment option, which doesn't resize the window when an input control has focus. Instead, the contents of ......
Read more >
Accommodating The On Screen Keyboard in Xamarin Forms
If you want the screen to resize you need to implement two pieces of code. First, in the MainActivity set the SoftInputMode to...
Read more >
Android how to adjust for soft keyboard | by Yat Man, Wong
Problem: How to pan up more in full screen mode with AdjustResize · You have a layout that is not resizable and you...
Read more >
Untitled
The beauty and the ugly of animating view on keyboard opens What is the iPhone WindowSoftInputModeAdjust.Resize and Pan on iOS #10662 Loading Application ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found