iOS : Scroll up to reveal focussed textfield when keyboard shows up

10 May, 2015 01:05 AM
iOS has soft keyboard and it covers the screen by overlaying on it when it comes up for text input. If the input receiving UI element like UITextField or UITextView is in the bottom half of the screen, it would be hidden by the keyboard and the text entered cannot be seen. So it is desirable to scroll the content up so that the focussed textfield becomes visible and hence the content entered too. There can be a case when the content hidden might not be an input field (might be a label or a button) and it might be desirable to access them via scroll. Below, I have illustrated an easier and cleaner way to achieve this when using story board and auto layout. The example does two things - 

1. Makes the content scrollable when keyboard shows up
2. Brings the text field in focus to visible area above the keyboard

I have illustrated the concept using a login form example that would look as below -

Scrollview containing login form
ScrollView For Login

 When keyboard is shown
Scrollview with login form (keyboard showing)

The second figure above shows that the password field is still visible when keyboard is shown and the form has moved up slightly ("login" label is overlapping with status bar at the top, indicating the scroll up).

How to achieve this - 

1. Setup the view design in storyboard without the scrollview. The view will become a subview of scrollview that we add programatically later. Here is how the storyboard would look (Notice that there is no scrollview).

Storyboard Showing Form Design

2. Add the constraints if required in such a manner as to avoid top and bottom space constraints. This is because, these constraints would stick to parent view and would break when the parent view frame is resized. Alternate would be to edit these constraints programatically if they cannot be absolutely avoided. I am not experimenting with that here. In the design above, both label and the sub-view containing the input elements have constraints specifying horizontal and vertical center alignment, width and height.

3. With the above setup, goto the view controller class and insert a scrollview in viewDidLoad method - 

- (void)viewDidLoad {
       [super viewDidLoad];

       UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
       UIView *mainView = self.view;
       self.view = scrollView;
       [scrollView addSubview:mainView];
       scrollView.contentSize = mainView.frame.size;
       scrollView.backgroundColor = mainView.backgroundColor;
       self.scrollView = scrollView;

To summarise what is happening above - the root view is moved as a sub-view of scrollview which is now made the root view of the controller. The scrollview also takes the frame of the root view and its background color.

NOTE: If the original view can have content height longer than device screen dimensions, then scrollview should be set with device screen size and not the initial view size. Rest remains the same.

4. Now listen to keyboard show and hide notifications in viewDidLoad as follows - 

[[NSNotificationCenter defaultCenter] addObserver:self
name:UIKeyboardDidShowNotification object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
name:UIKeyboardWillHideNotification object:nil];

NOTE: Do not forget to remove notification listeners in dealloc method else if the view controller is deallocated upon say back navigation in case of navigation controller hierarchy, it would still receive the keyboard notifications when keyboard shows up in an another scenario and crash the app.

5. In the selectors called for the above notifications, get the keyboard height and adjust the scrollview insets as below -

- (void) keyboardWillShow:(NSNotification *) notification {
       NSDictionary* info = [notification userInfo];
       CGSize kbSize = [[info        objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
       UIEdgeInsets contentInsets = UIEdgeInsetsMake(0, 0, kbSize.height, 0);

       self.scrollView.contentInset = contentInsets;
       self.scrollView.scrollIndicatorInsets = contentInsets;

- (void) keyboardWillHide:(NSNotification *) notification {
       UIEdgeInsets contentInsets = UIEdgeInsetsMake(0, 0, 0, 0);
       self.scrollView.contentInset = contentInsets;
       self.scrollView.scrollIndicatorInsets = contentInsets;

That should be it. Since the root view is scrollable, iOS automatically scrolls the content up to bring the textfield acting as first responder to focus. Using UIKeyboardFrameEndUserInfoKey for getting the keyboard size is the correct way as it accounts for height of keyboard accessory view that shows word & spelling suggestions in all iOS keyboards now. The solution presented here is tested on iOS 7.1 and above.