Autolayout for ScrollView + Keyboard Handling in iOS

Today I’m writing an article about a common issue in iOS: autolayout with scroll view and handle keyboard event. This is a very simple problem but also very itchy for many developers. Hence, I’ll introduce my solution in this post.

The scenario: I have a view with some TextFields. Some textfields are at the bottom of the screen, so when I want to text something, the keyboard will show and cover the textfields -> can’t text anymore.

Solution: When the keyboard show, move the textfield up to some distance base on keyboard’s height. Since we move up textfield, all controls need to move and some will exceed the top of the screen. Wanna see things in the top area -> scroll to the top -> scroll view appears.

So here the details: we have a scroll view cover all area in the screen. Then we have a content view, where we put all the controls in. The content view will be child of scroll view.

First, add the scroll view to controller:

Add scrollView to controller. Set constraint to top, bottom, leading and trailing of the screen.

After set constraints for scrollView, we will put a view inside scrollView.

Put a contentView inside scrollView.

We will set some constraint for contentView. First, set top, bottom, leading and trailing of contentView to scrollView. Then, we set contentView to be equal to scrollView in width and height. The size of contentView is also the contentSize of scrollView. At first, contentView’s height is equal to scrollView’s height, so it can’t be scrolled. After that, if we add some controls to contentView and they exceed the size of screen, we just need to modify the height constraint of contentView -> scrollView is scrollable.

So, now we add some TextFields to contentView.

Add 3 UITextFields to contentView.

I will add 3 TextFields, A, B and C to contentView. For demo purpose, I set some simple constraints to make sure A and B will be above the keyboard and C will be below the keyboard.

Let’s run the app now

Left: before input. Right: after input

The left screen is before we start to input something. In the right screen, after keyboard was shown, TextField C will be hidden. Our target is to move the TextField if keyboard hides it.

First, we need to set delegate for 3 TextFields.

// Set textfield delegate
textFieldA.delegate = self
textFieldB.delegate = self
textFieldC.delegate = self

We will need 2 actions from UITextFieldDelegate: textFieldShouldBeginEditing and textFieldShouldReturn. The former is to detect when we about to input something. The latter is to hide the keyboard when user press return key.

We need a variable to hold the current “active” TextField. I will call it activeField. We also need to log the offset of scrollView before we start our modification to it.

func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
activeField = textField
lastOffset = self.scrollView.contentOffset
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
activeField?.resignFirstResponder()
activeField = nil
return true
}

Now, we need to know when keyboard will show. Add 2 observers for keyboard: UIKeyboardWillShow and UIKeyboardWillHide.

func keyboardWillShow(notification: NSNotification) {
if keyboardHeight != nil {
return
}
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
keyboardHeight = keyboardSize.height
// so increase contentView's height by keyboard height
UIView.animate(withDuration: 0.3, animations: {
self.constraintContentHeight.constant += self.keyboardHeight
})
// move if keyboard hide input field
let distanceToBottom = self.scrollView.frame.size.height - (activeField?.frame.origin.y)! - (activeField?.frame.size.height)!
let collapseSpace = keyboardHeight - distanceToBottom
if collapseSpace < 0 {
// no collapse
return
}
// set new offset for scroll view
UIView.animate(withDuration: 0.3, animations: {
// scroll to the position above keyboard 10 points
self.scrollView.contentOffset = CGPoint(x: self.lastOffset.x, y: collapseSpace + 10)
})
}
}

When the keyboard was shown, it will take up a space equal to its height. We want to see what was hidden by keyboard, so we need to increase the height of contentView by keyboardHeight.

Then, we need to check whether the current active field is covered by keyboard. It’s quite easy. First, calculate the space from that textField to the bottom of screen. If this distance is bigger than keyboardHeight, we don’t need to do anything. Otherwise, we move the scrollView to see the TextField. We call collapseSpace is the space keyboard exceeds the TextField. To see activeField, move scrollView offset by that collapseSpace (here I plus some extra space to make the TextField a little higher than keyboard).

TextField C is moved higher than keyboard

We get the result here. After pressing return, keyboard will be hidden. We have to change the scrollView back to its last state.

func keyboardWillHide(notification: NSNotification) {
UIView.animate(withDuration: 0.3) {
self.constraintContentHeight.constant -= self.keyboardHeight
self.scrollView.contentOffset = self.lastOffset
}
keyboardHeight = nil
}

This is just a simple demo. In real life, the scenario may become more complicated and we need to make a little bit different modification for this solution.

Checkout full source code: https://github.com/dzungnguyen1993/KeyboardHandling

If you read to the end of this post, thank you for spending your time! Have a good night!