Forum Xamarin.iOS

Move textfield when keyboard appers

CristianLehuedeCristianLehuede CLMember ✭✭
edited October 2013 in Xamarin.iOS

Hello!
I'm really new to Xamarin and I've come to an issue that is out of my league.
I've read and copied this code to my project
http://forums.xamarin.com/discussion/comment/23235/#Comment_23235
But I'm not seeing any changes!, I addeda UIScrollView on the interface builder but now I'm unable to click on any of my textfields.
This is my code

`public partial class LoginScreen : UIViewController
{
WaitingScreen waitingScreen;
public LoginScreen () : base ("LoginScreen", null)
{
}

    public override void DidReceiveMemoryWarning ()
    {
        // Releases the view if it doesn't have a superview.
        base.DidReceiveMemoryWarning ();

        // Release any cached data, images, etc that aren't in use.
    }

    public override void ViewDidLoad ()
    {
        this.RegistrarButton.TouchUpInside += (sender, e) => {
        if(waitingScreen==null)
            {
                waitingScreen=new WaitingScreen();
                this.NavigationController.PushViewController(waitingScreen,true);
            }

        };
        var g = new UITapGestureRecognizer(() => View.EndEditing(true));
        View.AddGestureRecognizer(g);
        base.ViewDidLoad ();
        if (HandlesKeyboardNotifications()) {
            RegisterForKeyboardNotifications();
        }

        // Perform any additional setup after loading the view, typically from a nib.
    }



    #region Keyboard adjust

    /// <summary>
    /// Set this field to any view inside the scroll view to center this view instead of the current responder
    /// </summary>
    protected UIView ViewToCenterOnKeyboardShown;
    public virtual bool HandlesKeyboardNotifications()
    {
        return false;
    }

    protected virtual void RegisterForKeyboardNotifications()
    {
        NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillHideNotification, OnKeyboardNotification);
        NSNotificationCenter.DefaultCenter.AddObserver(UIKeyboard.WillShowNotification, OnKeyboardNotification);
    }

    /// <summary>
    /// Gets the UIView that represents the "active" user input control (e.g. textfield, or button under a text field)
    /// </summary>
    /// <returns>
    /// A <see cref="UIView"/>
    /// </returns>
    protected virtual UIView KeyboardGetActiveView()
    {
        return View.FindFirstResponder();
    }

    private void OnKeyboardNotification (NSNotification notification)
    {
        if (!IsViewLoaded) return;

        //Check if the keyboard is becoming visible
        var visible = notification.Name == UIKeyboard.WillShowNotification;

        //Start an animation, using values from the keyboard
        UIView.BeginAnimations ("AnimateForKeyboard");
        UIView.SetAnimationBeginsFromCurrentState (true);
        UIView.SetAnimationDuration (UIKeyboard.AnimationDurationFromNotification (notification));
        UIView.SetAnimationCurve ((UIViewAnimationCurve)UIKeyboard.AnimationCurveFromNotification (notification));

        //Pass the notification, calculating keyboard height, etc.
        bool landscape = InterfaceOrientation == UIInterfaceOrientation.LandscapeLeft || InterfaceOrientation == UIInterfaceOrientation.LandscapeRight;
        var keyboardFrame = visible
            ? UIKeyboard.FrameEndFromNotification(notification)
                : UIKeyboard.FrameBeginFromNotification(notification);

        OnKeyboardChanged (visible, landscape ? keyboardFrame.Width : keyboardFrame.Height);

        //Commit the animation
        UIView.CommitAnimations ();
    }

    /// <summary>
    /// Override this method to apply custom logic when the keyboard is shown/hidden
    /// </summary>
    /// <param name='visible'>
    /// If the keyboard is visible
    /// </param>
    /// <param name='keyboardHeight'>
    /// Calculated height of the keyboard (width not generally needed here)
    /// </param>
    protected virtual void OnKeyboardChanged (bool visible, float keyboardHeight)
    {
        var activeView = ViewToCenterOnKeyboardShown ?? KeyboardGetActiveView();
        if (activeView == null)
            return;

        var scrollView = activeView.FindSuperviewOfType(View, typeof(UIScrollView)) as UIScrollView;
        if (scrollView == null)
            return;

        if (!visible)
            RestoreScrollPosition(scrollView);
        else
            CenterViewInScroll(activeView, scrollView, keyboardHeight);
    }

    protected virtual void CenterViewInScroll(UIView viewToCenter, UIScrollView scrollView, float keyboardHeight)
    {
        var contentInsets = new UIEdgeInsets(0.0f, 0.0f, keyboardHeight, 0.0f);
        scrollView.ContentInset = contentInsets;
        scrollView.ScrollIndicatorInsets = contentInsets;

        // Position of the active field relative isnside the scroll view
        RectangleF relativeFrame = viewToCenter.Superview.ConvertRectToView(viewToCenter.Frame, scrollView);

        bool landscape = InterfaceOrientation == UIInterfaceOrientation.LandscapeLeft || InterfaceOrientation == UIInterfaceOrientation.LandscapeRight;
        var spaceAboveKeyboard = (landscape ? scrollView.Frame.Width : scrollView.Frame.Height) - keyboardHeight;

        // Move the active field to the center of the available space
        var offset = relativeFrame.Y - (spaceAboveKeyboard - viewToCenter.Frame.Height) / 2;
        scrollView.ContentOffset = new PointF(0, offset);
    }

    protected virtual void RestoreScrollPosition(UIScrollView scrollView)
    {
        scrollView.ContentInset = UIEdgeInsets.Zero;
        scrollView.ScrollIndicatorInsets = UIEdgeInsets.Zero;
    }

    /// <summary>
    /// Call it to force dismiss keyboard when background is tapped
    /// </summary>
    protected void DismissKeyboardOnBackgroundTap()
    {
        // Add gesture recognizer to hide keyboard
        var tap = new UITapGestureRecognizer { CancelsTouchesInView = false };
        tap.AddTarget(() => View.EndEditing(true));
        View.AddGestureRecognizer(tap);
    }

    #endregion


}
public static class ViewExtensions
{
    /// <summary>
    /// Find the first responder in the <paramref name="view"/>'s subview hierarchy
    /// </summary>
    /// <param name="view">
    /// A <see cref="UIView"/>
    /// </param>
    /// <returns>
    /// A <see cref="UIView"/> that is the first responder or null if there is no first responder
    /// </returns>
    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;
    }

    /// <summary>
    /// Find the first Superview of the specified type (or descendant of)
    /// </summary>
    /// <param name="view">
    /// A <see cref="UIView"/>
    /// </param>
    /// <param name="stopAt">
    /// A <see cref="UIView"/> that indicates where to stop looking up the superview hierarchy
    /// </param>
    /// <param name="type">
    /// A <see cref="Type"/> to look for, this should be a UIView or descendant type
    /// </param>
    /// <returns>
    /// A <see cref="UIView"/> if it is found, otherwise null
    /// </returns>
    public static UIView FindSuperviewOfType(this UIView view, UIView stopAt, Type type)
    {
        if (view.Superview != null)
        {
            if (type.IsAssignableFrom(view.Superview.GetType()))
            {
                return view.Superview;
            }

            if (view.Superview != stopAt)
                return view.Superview.FindSuperviewOfType(stopAt, type);
        }

        return null;
    }
}

}
`

What I'm missing?
EDIT: this is the photo of my xib
http://postimg.org/image/ocyozzyl7/

Posts

  • devnldevnl NLUniversity ✭✭

    Shouldnt this be returning true? When returning false the keyboard events are never attached it seems.

    public virtual bool HandlesKeyboardNotifications()
    {
        return false;
    }
    
  • sezamuzsezamuz BSMember ✭✭

    in my old project i'm use this:
    public static class KeyboardAppearHelper
    {
    public delegate void addActionDelegate ();

            public static void WillAppear(this UIView view, ref NSObject obs1, ref NSObject obs2, addActionDelegate forobs1, addActionDelegate forobs2 )
            {
                obs1 = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.DidShowNotification, delegate (NSNotification n) {
    
                    var kbdRect = UIKeyboard.FrameEndFromNotification (n);
                    var duration = UIKeyboard.AnimationDurationFromNotification (n);
                    var frame = view.Frame;
                    frame.Height -= kbdRect.Height;
                    UIView.BeginAnimations ("ResizeForKeyboard");
                    UIView.SetAnimationDuration (duration);
                    view.Frame = frame;
                    UIView.CommitAnimations ();
    
                    if (forobs1 != null){ forobs1();}
    
                });
    
                obs2 = NSNotificationCenter.DefaultCenter.AddObserver (UIKeyboard.WillHideNotification, delegate (NSNotification n) {
    
                    var kbdRect = UIKeyboard.FrameEndFromNotification (n);
                    var duration = UIKeyboard.AnimationDurationFromNotification (n);
                    var frame = view.Frame;
                    frame.Height += kbdRect.Height;
                    UIView.BeginAnimations ("ResizeForKeyboard");
                    UIView.SetAnimationDuration (duration);
                    view.Frame = frame;
                    UIView.CommitAnimations ();
    
                    if (forobs2 != null){ forobs2();}
    
                });
            }
    
    
            public static void DidDisappear (this UIView view, ref NSObject obs1, ref NSObject obs2)
            {
                NSNotificationCenter.DefaultCenter.RemoveObserver (obs1);
                NSNotificationCenter.DefaultCenter.RemoveObserver (obs2);
            }
        } 
    
  • sezamuzsezamuz BSMember ✭✭

    but in this time i'l be used ContentInset or etc. field (UIEdgeInsets type) for this

  • CristianLehuedeCristianLehuede CLMember ✭✭

    @sezamuz Would you mind showing me your code? I'm really stuck with this.

  • sezamuzsezamuz BSMember ✭✭

    Cristian sorry but is my old code and after many changes its incomplete and now is don't compile, i am illustrate idea but not concrete solution.
    see any fields with UIEdgeInsets type in you root container view such as ContentInsets or Insets or etc

  • GuillermoGutierrezGuillermoGutierrez ESMember ✭✭✭

    Hey @CristianLehuede, devnl is right, you need to return true in HandlesKeyboardNotifications method in order to enable keyboard notifications. If you don't get it to work could you provide a working sample to reproduce the issue?

    I've recently improved this code to include other features, but this has worked for me for several months.

  • devnldevnl NLUniversity ✭✭

    Also, don't forget to actually add a scrollview as the base of your view.

  • CristianLehuedeCristianLehuede CLMember ✭✭

    Ok, so I've found another code and tryed to merge it with the one from @GuillermoGutierrez
    It's almost working! Except I'm unable to find how to convert the keyboard to a rectangleF as to know when to move it because there is a method I use which is deprectaed.
    This is my current code

    `private void KeyBoardUpNotification(NSNotification notification)
    {

            // get the keyboard size
            RectangleF r = UIKeyboard.BoundsFromNotification (notification); //THIS METHOD IS DEPRECATED SO I'M GETTING A RECTANGLE WITH EVERYTHING SET TO 0
    
            // Find what opened the keyboard
            foreach (UIView view in this.View.Subviews) {
                if (view.IsFirstResponder)
                    activeview = view;
            }
    
            // Bottom of the controller = initial position + height + offset      
            bottom = (activeview.Frame.Y + activeview.Frame.Height + offset);
    
            // Calculate how far we need to scroll
            scroll_amount = (r.Height - (View.Frame.Size.Height - bottom)) ;
            scroll_amount = -(scroll_amount);
            // Perform the scrolling
            if (scroll_amount > 0) {
                moveViewUp = true;
                ScrollTheView (moveViewUp);
            } else {
                moveViewUp = false;
            }
    
        }
        private void KeyBoardDownNotification(NSNotification notification)
        {
            if(moveViewUp){ScrollTheView(false);}
        }
        private void ScrollTheView(bool move)
        {
    
            // scroll the view up or down
            UIView.BeginAnimations (string.Empty, System.IntPtr.Zero);
            UIView.SetAnimationDuration (0.3);
    
            RectangleF frame = View.Frame;
    
            if (move) {
                frame.Y -= scroll_amount;
            } else {
                frame.Y += scroll_amount;
                scroll_amount = 0;
            }
    
            View.Frame = frame;
            UIView.CommitAnimations();
        }
        protected void DismissKeyboardOnBackgroundTap ()
        {
            // Add gesture recognizer to hide keyboard
            var tap = new UITapGestureRecognizer { CancelsTouchesInView = false };
            tap.AddTarget (() => View.EndEditing (true));
            View.AddGestureRecognizer (tap);
        }`
    

    Any ideas how to fix the line where I get the keyboard size and convert it into rectangleF?

  • devnldevnl NLUniversity ✭✭
    edited October 2013

    I just took the code you have in your first post and got it to work by:

    • Adding a scrollview and adding all your controls to that.
    • Setting ViewToCenterOnKeyboardShown to the view you want to center on in ViewDidLoad.
    • Make HandlesKeyboardNotifications return true instead of false.

    I didn't use any code from the other posts. I think you can get the frame by using something like:

    UIKeyboard.FrameEndFromNotification(notification)
    

    Or

    UIKeyboard.FrameBeginFromNotification(notification)
    
  • CristianLehuedeCristianLehuede CLMember ✭✭

    @devnl
    I'm really new to Xamarin and I got a little lost when you say
    Setting ViewToCenterOnKeyboardShown to the view you want to center on in ViewDidLoad.
    In my ViewDidLoad I don't declare any view. All of those are setted in the AppDelegate.cs where I declare

    `public override bool FinishedLaunching (UIApplication app, NSDictionary options)
    {
    // create a new window instance based on the screen size

            window = new UIWindow (UIScreen.MainScreen.Bounds);
    
            var rootNavigationController = new UINavigationController();
            LoginScreen pantallaInicio = new LoginScreen ();
            rootNavigationController.PushViewController (pantallaInicio, false);
            UIRemoteNotificationType notificationTypes = UIRemoteNotificationType.Alert;
            UIApplication.SharedApplication.RegisterForRemoteNotificationTypes(notificationTypes);
            // If you have defined a root view controller, set it here:
            // window.RootViewController = myViewController;
            window.RootViewController = rootNavigationController;
            // make the window visible
            window.MakeKeyAndVisible ();
    
            return true;
        }`
    

    I'd hope if you could help me.
    Thanks

  • devnldevnl NLUniversity ✭✭

    I think I'm kind of missing what your actual original problem is now. Is it still that none of your textboxes can be entered? Or is it a problem with the keyboard not showing or the view not shifting when the keyboard is shown (which is what most of the code you've posted is for)?

  • CristianLehuedeCristianLehuede CLMember ✭✭

    @devnl
    The view not shifting when the keyboard is shown.
    I've uploaded my solution to see if you or someone can help me.
    https://mega.co.nz/#!cgYkWQwS!VRwSjgtFx6EWZZWc7IFY0lqPZRwKukz5NDkj9aUaC90
    Thanks for everything but I've literally tried everything.

  • devnldevnl NLUniversity ✭✭

    @CristianLehuede - I personally always create my interface in code, so I don't use the interface builder and XIBs, but from what I can see I did the following to fix it:

    • Changed the Testing variable in codebehind back to ViewToCenterOnKeyboardShown.
    • Added the following in ViewDidLoad:

      ViewToCenterOnKeyboardShown = this.contrasena2Text;
      

    In the XIB file currently the hierarchy is:

    Scrollview
    ---- View

    I changed that to

    View
    ---- Scrollview

    Like I said, I don't use the Interface Builder, so I had to change stuff around in the actual XIB file XML. It might not be entirely correct anymore, but it does what you want it to do. In your own project I would advise switching the View and Scrollview around in the XIB editor instead of what I did in the actual XML file.

    Here's the result:
    https://mega.co.nz/#!3NlC3bzQ!cgl2ql2TQM7aWbBSmx5oQzEk6Nsa7FQloAlpT9nBbDc

  • GuillermoGutierrezGuillermoGutierrez ESMember ✭✭✭

    Hey @devnl, setting ViewToCenterOnKeyboardShown is not really required, it's used to override the default behavior of centering the first responder, for example when you want to center a full modal view instead of the login textfields individually.

    I extracted the code from my internal libraries and here you can find the newest version of this component. It moves UIScrollView related methods to an extension so they can be used from anywhere else and calculates the intersect between the keyboard frame and the UIScrollView to avoid scrolling more than required. It also fixes an issue when trying to center a textfield inside a UITableViewCell.

    Cheers!

  • CristianLehuedeCristianLehuede CLMember ✭✭

    Thanks to both of you! Now my code is working beatifully! I plan on making a tutorial for people who had the same problem and teach them how to fix this.
    Thanks!

  • sezamuzsezamuz BSMember ✭✭
    edited November 2013

    psl put here you right and full solution for other users )

  • Adnan.2174Adnan.2174 USMember

    The above code of managing keyboard does not work properly. Within function protected virtual void

    OnKeyboardChanged (bool visible, float keyboardHeight)
                    {
                        var activeView = ViewToCenterOnKeyboardShown ?? KeyboardGetActiveView();
                        if (activeView == null)
                            return;
    
                        var scrollView = activeView.FindSuperviewOfType(View, typeof(UIScrollView)) as UIScrollView;
                        if (scrollView == null)
                            return;
            .....
            }
    

    scrollview is null. Managing the view is thought to be by giving some input to a textField. Does anyone had the same problem??

  • When it comes to editable fields I always recommend using a UITableViewController. Arrange your layout as cells. It automatically scrolls to the field being edited if it gets overlapped by the keyboard. And this also works with fields which have a custom InputField, while the method you have been struggling with, does not.

Sign In or Register to comment.