As iOS Devices rely on Soft Keyboard as its input source, keyboard appears and disappears frequently during the Application Lifetime. When keyboard appearance and disappearance is not handled properly, often times it will break the user experience in using the app. Views in focus are hidden or even worse submit button is obscured by the keyboard, preventing the user from continue. As handling keyboard is one of the common issue iOS developers often encounter, there is already some resources in the Internet covering this topic, e.g. Apple official documentation or Tutsplus

Improving from the above sources, this time I would like to cover specifically keyboard handling for iOS Chat Application. Unlike keyboard handling covered in the normal cases, keyboard handling in iOS Chat Application is different. Normally, any chat application consists of the following two major parts:

As the result, handling keyboard in chat application requires not only to re-adjust the AutoLayout of the Input Message View but also to re-adjust the Content Offset of the Messages List View.

Problem

Before we start, let's first take a look at 4 different cases that we need to consider when handling keyboard appearance and disappearance.

  1. Messages fills all the screen and user is currently scrolled to the bottom of message list. Appearance of keyboard should push both the input message view and message list view.
  2. Messages doesn't fill all the screen and appearance of keyboard will not hide any message. Appearance of keyboard should push the input message view but should not push the message list view.
  3. Messages doesn't fill all the screen and appearance of keyboard will hide some of the message. Appearance of keyboard should push both the input message view and message list view. However message list view should not be pushed further than the bottom part of the last message.
  4. Messages fill all the screen and user is currently scrolled to the middle of the message list. Appearance of keyboard should push the input message view here. Then how about the message list view? By the time this blog is written, I observe that there are different behaviours among popular messaging apps in the App Store to handle this case. For example, LINE doesn't push the message list view while Whatsapp does. Both of these behaviour have their own strong points. Personally, as an user, I prefer the latter behaviour since it gives smoother user experience by following user expectation. The rest of this article will assume the latter behaviour. Please leave a comment below if you have other opinion about this. I would love to know. :)

Solution

So, here comes the exciting part of the article, solution!. I'm sure most of you who bump into this article are trying to find solution for the above problems. So without delaying any further let's start.

Add Observer

To start with, we need to notify our view controller when the keyboard appears and disappears. You may notice that in Apple official documentation or Tutsplus , observer for UIKeyboardDidShowNotification and UIKeyboardWillHideNotification is used. This works flawlessly for iOS7 below. However, note that with the introduction of QuickType keyboard in iOS8, size of keyboard can change dynamically when it already appears. When the size of keyboard dynamically change, observer for UIKeyboardDidShowNotification and UIKeyboardWillHideNotification will not be able to notice the change.

Therefore, we are going to improvise by using observer for UIKeyboardWillChangeFrameNotification.

First, register the observer in viewDidAppear

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // Add keyboard observer
    [self addKeyboardObserver];
}

- (void)addKeyboardObserver {
    // add observer for keyboard
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardWillChange:)
                                                 name:UIKeyboardWillChangeFrameNotification
                                               object:nil];
}

This will cause keyboardWillChange function be called whenever the size of the keyboard changes.

Calculation

keyboardWillChange is the meat of the solution, it calculates the differences in keyboard size and perform the push animation. Before we write keyboardWillChange function, we need to do some setup first. We will need reference to layout constraint between the content view. Let's hook the layout constraint from the bottom of the content view to the bottom of input message view. (Make sure the direction of the constraint is right! Otherwise you will see opposite effect when the keyboard appears)

@property (weak, nonatomic) IBOutlet NSLayoutConstraint *layoutConstraintContentViewBottomWithSendMessageViewBottom;

So now, let's write keyboardWillChange function. Let's walk through it step by step.

  1. First let's setup the function structure
    - (void)keyboardWillChange:(NSNotification *)notification {
    
    }
    
  2. From the notification object, extract the animation duration and animation option of the keyboard. By using, these value to animate the change of AutoLayout and Content Offset, the animation of the view and the keyboard will looks smooth and appears to be integral.
    CGRect keyboardEndFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];  
    UIViewAnimationCurve animationCurve = [[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];  
    UIViewAnimationOptions animationOptions = animationCurve | (animationCurve << 16); // Convert animation curve to animation option  
    NSTimeInterval animationDuration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];  
    
  3. Calculate the new bottom constraint. The new bottom constraint should equal to the size of the area covered keyboard, so we can calculate it by subtracting screen height with the y location of the keyboard.
    // Get the final location of the keyboard
    CGPoint keyboardEndFrameLocation = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].origin;
    
    // Calculate the new bottom constraint, which is equal to the area covered by the keyboard
    CGRect screen = [UIScreen mainScreen].bounds;  
    CGFloat newBottomConstraint = (screen.size.height-keyboardEndFrameLocation.y);  
    
  4. Calculate the new bottom constraint. The new bottom constraint should equal to the size of the area covered keyboard, so we can calculate it by subtracting screen height with the y location of the keyboard.
    // Get the final location of the keyboard
    CGPoint keyboardEndFrameLocation = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].origin;
    
    // Calculate the new bottom constraint, which is equal to the area covered by the keyboard
    CGRect screen = [UIScreen mainScreen].bounds;  
    CGFloat newBottomConstraint = (screen.size.height-keyboardEndFrameLocation.y);  
    
  5. Keep old y content offset and height. We will use it to calculate the the new y content offset later on.
    // Keep old y content offset and height before they change
    CGFloat oldYContentOffset = self.tableViewMessageList.contentOffset.y;  
    CGFloat oldTableViewHeight = self.tableViewMessageList.bounds.size.height;  
    
  6. Define animation, 2 things are done here
    1. Set the new bottom constraint
    2. Calculate the new content offset. The new y content offset is equal to the old y content offset subtracted with the difference of message list view's height. Also we need to ensure that the new content offset is still within the permitted range.
    // Define animation
    void (^animationBlock)() = ^(void){  
        // Set the new bottom constraint
        self.layoutConstraintContentViewBottomWithSendMessageViewBottom.constant = newBottomConstraint;
        // Request layout with the new bottom constraint
        [self.view layoutIfNeeded];
    
        // Set the new content offset
    
        // Calculate the new y content offset
        CGFloat newTableViewHeight = self.tableViewMessageList.bounds.size.height;
        CGFloat tableViewHeightDifference = newTableViewHeight - oldTableViewHeight;
        CGFloat newYContentOffset = oldYContentOffset - tableViewHeightDifference;
    
        // Prevent new y content offset from exceeding max, i.e. the bottommost part of the UITableView
        CGFloat contentSizeHeight = self.tableViewMessageList.contentSize.height;
        CGFloat possibleBottommostYContentOffset = contentSizeHeight - newTableViewHeight;
        newYContentOffset = MIN(newYContentOffset, possibleBottommostYContentOffset);
    
        // Prevent new y content offset from exceeding min, i.e. the topmost part of the UITableView
        CGFloat possibleTopmostYContentOffset = 0;
        newYContentOffset = MAX(possibleTopmostYContentOffset, newYContentOffset);
    
        // Create new content offset
        CGPoint newTableViewContentOffset = CGPointMake(self.tableViewMessageList.contentOffset.x, newYContentOffset);
        self.tableViewMessageList.contentOffset = newTableViewContentOffset;
    
    };
    

  7. And finally, start the animation!

    // Start animation
    [UIView animateWithDuration:animationDuration delay:0 options:animationOptions animations:animationBlock completion:nil];
    


Remove Observer

Lastly, don't forget to unregister the observer in viewDidDisappear

- (void)viewDidDisappear:(BOOL)animated {
    // Remove observer
    [self removeKeyboardObserver];
    [super viewDidDisappear:animated];   
}

- (void)removeKeyboardObserver{
    // remove observer for keyboard
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:UIKeyboardWillChangeFrameNotification
                                                  object:nil];
}

And that's the end of the tutorial. Now you have a Chat Application with optimised keyboard handling! You can pull the complete code here. Thanks a lot for reading, if you have any feedback or comments, feel free to leave a comment below. Happy programming :)