Scroll editor in Xamarin Forms into view

13,762

Solution 1

To get auto scroll for Editors and Entries with Xamarin.Forms, you usually just have to pack your View, in this case the StackLayout, into a ScrollView:

<ScrollView>
    <StackLayout VerticalOptions="FillAndExpand">
        <Image x:Name="cameraImage" Source="camera.png" />
        <Label Text="Describe the image" />
        <Editor />
        <Button Text="Save" />
    </StackLayout>
</ScrollView>

That's how it's supposed to work, but as of today (June 2014) there's a bug preventing this to work fully with the Editor (it works well with Entries). The issue is known and is worked on.

[UPDATE 2014-11-20]The issue has been addressed, and will be available in the next -pre release of XF 1.3

Solution 2

I just stumbled upon this question when writing a tiny chat application, which basically contains a scrollable message list, a text entry and a send button:

The problem with the previously posted solution is that you'd need to nest two scroll views, which is not recommended by the Xamarin.Forms documentation. To prevent the keyboard from hiding the entry, I found the following hack:

I'm adding a placeholder at the end of the main stack layout. Depending on whether the entry is focussed (i.e. the keyboard is visible or not) the height of the placeholder is set to 0 or the keyboard height.

        // HACK: make entry visible when keyboard open
        var placeholder = new BoxView {
            HeightRequest = 0,
        };
        entry.Focused += (sender, e) => placeholder.HeightRequest = 210;
        entry.Unfocused += (sender, e) => placeholder.HeightRequest = 0;

        Content = new StackLayout {
            VerticalOptions = LayoutOptions.Fill,
            Padding = 5,
            Children = {
                whoTable,
                messageScrollView,
                new StackLayout {
                    Orientation = StackOrientation.Horizontal,
                    VerticalOptions = LayoutOptions.End,
                    HeightRequest = 70,
                    Children = {
                        entry,
                        sendButton,
                    },
                },
                placeholder,
            },
        };

Of course, this is not perfect. Especially the hard-coded keyboard height should be implemented more elegantly. And probably you should apply it on iOS only, not on Android.

Solution 3

Sometimes you cannot put your main view in a scrollview, in those cases you can implement this by handling the keyboard events the iOS project and passing them to the Forms level. Android takes care of it's self.

using System;
using Foundation;
using UIKit;
using RaiseKeyboard.iOS;

[assembly: Xamarin.Forms.Dependency (typeof (KeyboardHelper))]
namespace RaiseKeyboard.iOS
{
    // Raises keyboard changed events containing the keyboard height and
    // whether the keyboard is becoming visible or not
    public class KeyboardHelper : IKeyboardHelper
    {
        public KeyboardHelper() {
            NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, OnKeyboardNotification);
            NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, OnKeyboardNotification);
        }

        public event EventHandler<KeyboardHelperEventArgs> KeyboardChanged;

        private void OnKeyboardNotification (NSNotification notification)
        {
            var visible = notification.Name == UIKeyboard.WillShowNotification;
            var keyboardFrame = visible
                ? UIKeyboard.FrameEndFromNotification(notification)
                : UIKeyboard.FrameBeginFromNotification(notification);
            if (KeyboardChanged != null) {
                KeyboardChanged (this, new KeyboardHelperEventArgs (visible, (float)keyboardFrame.Height));
            }
        }
    }
}

Then at the Forms level:

using System;
using Xamarin.Forms;

namespace RaiseKeyboard
{
    // Provides static access to keyboard events
    public static class KeyboardHelper
    {
        private static IKeyboardHelper keyboardHelper = null;

        public static void Init() {
            if (keyboardHelper == null) {
                keyboardHelper = DependencyService.Get<IKeyboardHelper>();
            }
        }

        public static event EventHandler<KeyboardHelperEventArgs> KeyboardChanged {
            add {
                Init();
                keyboardHelper.KeyboardChanged += value;
            }
            remove {
                Init ();
                keyboardHelper.KeyboardChanged -= value;
            }
        }
    }

    public interface IKeyboardHelper
    {
        event EventHandler<KeyboardHelperEventArgs> KeyboardChanged;
    }

    public class KeyboardHelperEventArgs : EventArgs 
    {
        public readonly bool Visible;
        public readonly float Height;

        public KeyboardHelperEventArgs(bool visible, float height) {
            Visible = visible;
            Height = height;
        }
    }

}

If you are working in a Stacklayout and wish to raise the view above the keyboard you can put a spacer with a height 0 at the bottom of the stack. Then set it to the height of the keyboard when the keyboard changed event is raised.

spacer.HeightRequest = e.Visible ? e.Height : 0;

If you are working with a Listview you can handle this by translating your view by the amount it would have been overlapped by.

bottomOffset = mainStack.Bounds.Bottom - textStack.Bounds.Bottom;
textStack.TranslationY -= e.Visible ? e.Height - bottomOffset : bottomOffset - e.Height;

Listviews have to be handled differently as there height is automatically adjusted by Forms and using a spacer results over correction.

Sample here: https://github.com/naturalistic/raisekeyboard

Solution 4

Expanding on the answer from @Falko, you can check the platform for iOS since Android handles this as expected natively.

I also added this to the page for quick and dirty orientation via this answer.

static bool IsPortrait(Page p) { return p.Width < p.Height; }

Anyway, I understand that Xamarin are adding some solutions for this soon. For now, though...

protected async void Message_Focused(object sender, EventArgs args)
{
    if (Device.OS == TargetPlatform.iOS)
    {
        //TLR: still need a way to determine the iOS keyboard's height first
        //until then, this is a functional hack

        if (IsPortrait(this))
        {
            KeyboardSpacer.HeightRequest = 165;
        }
        else
        {
            KeyboardSpacer.HeightRequest = 114;
        }
    }
}

protected async void Message_Unfocused(object sender, EventArgs args)
{            
    if (Device.OS == TargetPlatform.iOS)
    {
        KeyboardSpacer.HeightRequest = 0;
    }
}
Share:
13,762
Johan Karlsson
Author by

Johan Karlsson

.net programmer working with #xamarin as a Sogeti consultant. MCPD if it counts for anything. Passionate about game development for mobile devices. Creator of WordRoom, a multiplatform (win8, wp, ios, android) competitive wordgame based around Xamarins products and with a Windows Azure backend.

Updated on June 05, 2022

Comments

  • Johan Karlsson
    Johan Karlsson almost 2 years

    Using Xamarin Forms, consider the Xaml below.

    <StackLayout VerticalOptions="FillAndExpand">
       <Image x:Name="cameraImage" Source="camera.png" />
       <Label Text="Describe the image" />
       <Editor />
       <Button Text="Save" />
     </StackLayout>
    

    This renders an image, an editor and a save button. The image is in 4x3 image ratio and covers about a third of the available screen height. The editor is rendered below.

    The problem is that the keyboard covers the Editor in iOS. A standard iOS issue normally.

    The question is: What is the Xamarin Forms way of handling this?

    Thanks

    // Johan