2
votes

I am working on an Xamarin.Forms project specifically for the iOS platform. I have an Editor control and a Button control next to each other. When I focus the editor, enter some text, and click the button it appears the command is not being fired but rather the keyboard is simply closing. I then have to tap the add button again for the command to be fired.

<StackLayout Orientation="Horizontal">
    <Editor HorizontalOptions="FillAndExpand"
            Text="{Binding EditorText}"/>
    <Button Text="Add" 
            Command="{Binding AddCommand}"/>
</StackLayout>

I have tried creating a custom renderer that prevents the keyboard from closing initially and then close it after a delay. That allows the command to be fired, but I am stuck with the keyboard being open.

public class KeyboardEditorRenderer : EditorRenderer
{
    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == VisualElement.IsFocusedProperty.PropertyName)
        {
            if (Control != null)
            {
                Control.ShouldEndEditing = (UITextView textField) =>
                {
                    Task.Delay(10).ContinueWith(_ =>
                    {
                        // THIS DOES NOT WORK
                        textField.EndEditing(true);
                    });

                    return false;
                };
            }
        }

        base.OnElementPropertyChanged(sender, e);
    }
}

My ideal solution is that you are able to enter text, tap the add button, and the keyboard closes and the command executes simultaneously. Any ideas on how to achieve this?

EDIT: It turns out the problem is with the custom renderer I use for the page. The custom renderer resizes the page when the keyboard appears so that it does not cover my editor field.

public class KeyboardPageRenderer : PageRenderer
{
    private bool keyboardShowing;
    private NSObject keyboardWillShow;
    private NSObject keyboardWillHide;
    private double duration;
    private UIViewAnimationCurve curve;

    public override void ViewDidLoad()
    {
        base.ViewDidLoad();

        this.keyboardWillShow = UIKeyboard.Notifications.ObserveWillShow(this.KeyboardWillShow);
        this.keyboardWillHide = UIKeyboard.Notifications.ObserveWillHide(this.KeyboardWillHide);
    }

    public override void ViewDidDisappear(bool animated)
    {
        base.ViewDidDisappear(animated);

        this.keyboardWillShow.Dispose();
        this.keyboardWillHide.Dispose();
    }

    private void KeyboardWillShow(object sender, UIKeyboardEventArgs args)
    {
        if (!this.keyboardShowing)
        {
            this.keyboardShowing = true;

            var keyboardFrame = UIKeyboard.FrameBeginFromNotification(args.Notification);

            this.duration = args.AnimationDuration;
            this.curve = args.AnimationCurve;

            this.ScrollTheView(true, keyboardFrame.Height);
        }
    }

    private void KeyboardWillHide(object sender, UIKeyboardEventArgs args)
    {
        if (this.keyboardShowing)
        {
            this.keyboardShowing = false;

            var keyboardFrame = UIKeyboard.FrameBeginFromNotification(args.Notification);

            this.duration = args.AnimationDuration;
            this.curve = args.AnimationCurve;

            this.ScrollTheView(false, keyboardFrame.Height);
        }
    }

    private void ScrollTheView(bool scale, nfloat scrollAmount)
    {
        UIView.BeginAnimations(string.Empty, IntPtr.Zero);
        UIView.SetAnimationDuration(this.duration);
        UIView.SetAnimationCurve(this.curve);

        var frame = View.Frame;

        // Assumes the page belongs to a tabbed view. 
        // This does not scale to pages that do not have one.
        UITabBarController tabBarController = new UITabBarController();
        nfloat tabHeight = tabBarController.TabBar.Frame.Size.Height;

        scrollAmount -= tabHeight;

        if (scale)
        {
            frame.Y -= scrollAmount;
        }
        else
        {
            frame.Y += scrollAmount;
        }

        View.Frame = frame;
        UIView.CommitAnimations();
    }
}
1
Mike when I try the Xaml on a test project on iOS, without the custom renderer, my Command is fired. This is on a real device. I tap the editor, type some text, then tap the button. The keyboard disappears, and the command fires (I log the text from the bound EditorText).Damian
You are right. It clears up my problem to actually being that my content page uses a custom renderer as well. The custom renderer is used to resize the page when the keyboard shows or else the keyboard overlaps the editor since it is at the bottom. I'll add that custom renderer to my question.Mike Richards

1 Answers

0
votes

There is two issues in your approach

  • After Task.Delay(10), you are not on the UI thread anymore, which means you have to use Device.BeginInvokeOnMainThread in order to access UI elements.
  • Control.ShouldEndEditing must be cleared before you call EndEditing

A working solution would look like this:

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged(sender, e);

    if (Element == null || Control == null)
        return;

    VisualElement element = Element as VisualElement;
    if (element == null)
        return;

    if (e.PropertyName == VisualElement.IsFocusedProperty.PropertyName && element.IsFocused == false)
    {
        Control.ShouldEndEditing = (UITextView control) =>
        {
            Device.BeginInvokeOnMainThread(() =>
            {
                control.ShouldEndEditing = null;
                control.EndEditing(true);
            });

            // prevent the keyboard from closing
            return false;
        };
    }
}