4

I want to get the height of the Keyboard on Android and iOS (Xamarin Forms).

When Screen display with Portrait mode and Landscape mode, how to get height value?

Orientations

I have found references with Swift on iOS:

What is the height of iPhone's onscreen keyboard?

How to get height of Keyboard?

But can provide me with the source code on Xamarin

And how to get the height of the Keyboard on Android?

Please help me!

Huu Bao Nguyen
  • 1,051
  • 2
  • 14
  • 40

2 Answers2

5

For iOS

There's two ways of doing this!

First Way: Implement it for one UIViewController, if your app is small, as follows:

Step 1: Add fields for the show and hide observers:

private NSObject _keyboardObserverWillShow;
private NSObject _keyboardObserverWillHide;

Step 2: Update the ViewDidLoad and ViewDidUnload overrides to add/remove the observers to whenever the keyboard is displayed or hidden:

public override void ViewDidLoad() {
    base.ViewDidLoad ();
    _keyboardObserverWillShow = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.DidShowNotification, KeyboardDidShowNotification);
    _keyboardObserverWillHide = NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, KeyboardWillHideNotification);
}
    
public override void ViewDidUnload() {
    base.ViewDidUnload();
    NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardObserverWillShow);
    NSNotificationCenter.DefaultCenter.RemoveObserver(_keyboardObserverWillHide);
}

Step 3: Then you populate the functions KeyboardDidShowNotification & KeyboardWillHideNotification that execute for each observer. It's fairly long and would take me a while to explain each part of, but you can ask me in the comments. It goes as follows:

private void KeyboardWillHideNotification (NSNotification notification) 
{
    UIView activeView = View.FindFirstResponder();
    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;
    });
}   

private void KeyboardDidShowNotification (NSNotification notification) 
{
    UIView activeView = View.FindFirstResponder();
    if (activeView == null)
        return;

    ((UITextField)activeView).ShowDoneButtonOnKeyboard();

    UIScrollView scrollView = activeView.FindSuperviewOfType(this.View, typeof(UIScrollView)) as UIScrollView;
    if (scrollView == null)
        return;
    
    RectangleF keyboardBounds = 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(this.View.Frame.Location, new SizeF(this.View.Frame.Width, this.View.Frame.Size.Height - keyboardBounds.Size.Height));
    
    RectangleF activeFieldAbsoluteFrame = 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, activeFieldAbsoluteFrame.Location.Y + activeFieldAbsoluteFrame.Height + scrollView.ContentOffset.Y - viewRectAboveKeyboard.Height);
        scrollView.SetContentOffset(scrollPoint, true);
    }
}

Second Way: Create a keyboard handler that you can use for each page through your BaseViewController

For Android:

You can follow the solutions mentioned in the stackOverflow Xamarin solution here((Activity)Forms.Context).Window.SetSoftInputMode(SoftInput.AdjustPan);. Some times that doesn’t work, so you can use this to fix it. PS: Didn’t copy these answers because it doesn’t make sense to rewrite an answer.

Saamer
  • 4,687
  • 1
  • 13
  • 55
  • Thank you for the support for `iOS`! How to get the height of the `Keyboard` on `Android`? – Huu Bao Nguyen Oct 12 '19 at 05:37
  • 1
    You are basically looking for "Android screen resize when keyboard appears". Here's one solution https://stackoverflow.com/a/31452896/11104068 and here's another https://stackoverflow.com/questions/26835061/android-windowsoftinputmode-adjustresize-still-panning/27208118 – Saamer Oct 12 '19 at 05:41
  • I'm glad that made sense to you! Happy coding :) – Saamer Oct 12 '19 at 05:50
  • 1
    Thanks so much for this answer! Big help for me! – jnel899 Mar 24 '20 at 13:35
  • 1
    Downvoted because the Android implementation is missing but was asked by the original topic creator. – jfmg Aug 21 '20 at 08:33
  • @jfmg I understand, :) OP asked for both iOS & Android, and the Android implementation is already done in the links i posted in the comments – Saamer Aug 21 '20 at 13:24
  • @Saamer sorry but I think I was a little bit frustrated when I found this solution but missed the Android solution. The solutions you mentioned don’t seem to – jfmg Aug 22 '20 at 14:07
  • 1
    @Saamer Sorry for the downvote but I think it’s important to always give a full answer to the questions on StackOverflow. So if you just add the Android solution to your answer, I’ll take away the downvote. – jfmg Aug 22 '20 at 14:13
  • @jfmg Added, there you go :) – Saamer Jun 02 '21 at 17:59
3

To get the height of the keyboard programmatically in a Xamarin.Forms project, you will need to register a platform service.

The interface

namespace YourCoreProject.Services
{
    using System.Reactive.Subjects;

    /// <summary>
    /// A service to interact with the software keyboard.
    /// </summary>
    public interface IKeyboardInteractionService
    {
        /// <summary>
        /// Gets a <see cref="Subject{T}" /> which notifies about changes of the keyboard height.
        /// </summary>
        public Subject<float> KeyboardHeightChanged { get; }
    }
}

Please note: I have used a Subject (contained in the System.Reactive.Subjects namespace) to notify about the keyboard height change but you could also use an event, if this concept fits better in your project. If you stay with the Subject, you may need to install the System.Reactive NuGet package.

iOS implementation

namespace YourProject.iOS.Services
{
    using System.Reactive.Subjects;
    using UIKit;

    /// <inheritdoc cref="IKeyboardInteractionService" />
    public class KeyboardInteractionService : IKeyboardInteractionService
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="KeyboardInteractionService" /> class.
        /// </summary>
        public KeyboardInteractionService()
        {
            UIKeyboard.Notifications.ObserveWillShow((_, uiKeyboardEventArgs) =>
            {
                var newKeyboardHeight = (float)uiKeyboardEventArgs.FrameEnd.Height;
                this.KeyboardHeightChanged.OnNext(newKeyboardHeight);
            });

            UIKeyboard.Notifications.ObserveWillHide((_, uiKeyboardEventArgs) =>
            {
                this.KeyboardHeightChanged.OnNext(0);
            });
        }

        /// <inheritdoc cref="IKeyboardInteractionService.KeyboardHeightChanged" />
        public Subject<float> KeyboardHeightChanged { get; } = new Subject<float>();
    } 
}

Android implementation

namespace YourProject.Droid.Services
{
    using System;
    using System.Reactive.Subjects;
    using Android.App;
    using Android.Views.InputMethods;
    using Xamarin.Essentials;

    /// <inheritdoc cref="IKeyboardInteractionService" />
    public class KeyboardInteractionService : IKeyboardInteractionService
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="KeyboardInteractionService" /> class.
        /// </summary>
        public KeyboardInteractionService()
        {
            var globalLayoutListener = new GlobalLayoutListener();
            this.KeyboardHeightChanged = globalLayoutListener.KeyboardHeightChanged;
        }

        /// <inheritdoc cref="IKeyboardInteractionService.KeyboardHeightChanged" />
        public Subject<float> KeyboardHeightChanged { get; }
    }
}

On Android you also need a listener. This is the implementation:

namespace YourProject.Droid.Services
{
    using System;
    using System.Reactive.Subjects;
    using Android.App;
    using Android.Content.Res;
    using Android.Graphics;
    using Xamarin.Essentials;
    using static Android.Views.ViewTreeObserver;

    /// <inheritdoc cref="IOnGlobalLayoutListener"/>
    public class GlobalLayoutListener : Java.Lang.Object, IOnGlobalLayoutListener
    {
        private readonly Activity activity;

        /// <summary>
        /// Initializes a new instance of the <see cref="GlobalLayoutListener" /> class.
        /// </summary>
        public GlobalLayoutListener()
        {
            this.activity = Platform.CurrentActivity;

            if (this.activity?.Window?.DecorView?.ViewTreeObserver == null)
            {
                throw new InvalidOperationException($"{this.GetType().FullName}.Constructor: The {nameof(this.activity)} or a follow up variable is null!");
            }

            this.activity.Window.DecorView.ViewTreeObserver.AddOnGlobalLayoutListener(this);
        }

        /// <summary>
        /// Gets a <see cref="Subject{T}" /> which notifies about changes of the keyboard height.
        /// </summary>
        public Subject<float> KeyboardHeightChanged { get; } = new Subject<float>();

        /// <inheritdoc cref="IOnGlobalLayoutListener.OnGlobalLayout" />
        public void OnGlobalLayout()
        {
            if (!this.KeyboardHeightChanged.HasObservers)
            {
                return;
            }

            var screenSize = new Point();
            this.activity.WindowManager?.DefaultDisplay?.GetSize(screenSize);
            var screenHeight = screenSize.Y;

            var rootView = this.activity.FindViewById(Android.Resource.Id.Content);

            if (rootView == null)
            {
                return;
            }

            var screenHeightWithoutKeyboard = new Rect();
            rootView.GetWindowVisibleDisplayFrame(screenHeightWithoutKeyboard);

            int keyboardHeight;

            // Android officially supports display cutouts on devices running Android 9 (API level 28) and higher.
            if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.P)
            {
                var displayCutout = rootView.RootWindowInsets?.DisplayCutout;

                // Displays with a cutout need to be handled different due to the cutout type:
                //
                // Default cutout:
                // A display has no cutout. The screen height can be used used as usual.
                //
                // Corner cutout:
                // A display has a cutout in a corner on the top of the display. The screen height must add the safe area of the top to get the total screen height.
                //
                // Double cutout:
                // A display has a cutout in the middle on the top and in the middle on the bottom of the display.
                // The screen height must add the safe area of the bottom only to get the total screen height.
                // Adding the screen height of the top as well, would lead to false results.
                //
                // Tall cutout:
                // A display has a cutout in the middle on the top of the display. The screen height must add the safe area of the top to get the total screen height.
                keyboardHeight = displayCutout == null ?
                    screenHeight - screenHeightWithoutKeyboard.Bottom :
                    displayCutout.SafeInsetBottom <= 0 ?
                        screenHeight + displayCutout.SafeInsetTop - screenHeightWithoutKeyboard.Bottom :
                        screenHeight + displayCutout.SafeInsetBottom - screenHeightWithoutKeyboard.Bottom;
            }
            else
            {
                keyboardHeight = screenHeight - screenHeightWithoutKeyboard.Bottom;
            }

            var keyboardHeightInDip = keyboardHeight / Resources.System?.DisplayMetrics?.Density ?? 1;

            if (keyboardHeightInDip < 0.0f)
            {
                keyboardHeightInDip = 0.0f;
            }

            this.KeyboardHeightChanged.OnNext(keyboardHeightInDip);
        }
    }
}

Please note: I have used Platform.CurrentActivity (contained in the Xamarin.Essentials namespace) to get the current activity. If you stick with this solution, you may need to install the Xamarin.Essentials NuGet package.

Hope this code helps everyone who is still facing this challenge. Hopefully a Xamarin.Essentials solution will be available in the future :-)

jfmg
  • 2,626
  • 1
  • 24
  • 32
  • 1
    When tabbing through `Entry`s, the height quickly jumps between 0 and 200 (e.g.), so I have added a delay for `height == 0` while `> 0` has an immediate effect. Thanks for the solution! – Martin Jan 28 '21 at 15:20
  • Android's keyboard height calculation isn't accurate, I fixed like this - `var keyboardHeight = rootView.Height - screenSizeWithoutKeyboard.Bottom + screenSizeWithoutKeyboard.Top;` – Niro Jun 15 '21 at 13:16
  • This answer is Obsolete... Beware – user1034912 Aug 02 '21 at 08:05
  • Beware... I have updated the answer and even improved it so that cutouts are taken into consideration as well :-) – jfmg Aug 03 '21 at 15:24