Ad

Our DNA is written in Swift
Jump

Calculating Area Covered by Keyboard

If you show something that contains scrollable content, i.e. UITableView, UIScrollView etc. then you want to make an adjustment when the keyboard shows so that the user can still scroll to the entire content. He wouldn’t be able to do so if you didn’t do anything.

I’ve seen several approaches to this so far, but they often hard code a certain position of the view or sizes. Like assuming that the covered view always reaches towards the bottom of the screen or always has a certain amount of space taken away from it by the status bar, navigation bar and possibly toolbar.

The whole thing gets even more complicated by the fact the the coordinate system of the app’s window is always in portrait even though your app rotates. So is the frame of the keyboard which you can get from an info dictionary in several notifications. I’ll show you the most universally working method I was able to come up with.

The first part is obvious, you want to subscribe to two notifications, one for when the keyboard has fully animated in and one for when the keyboard will go away. I’ve seen people mess with the frame of the covered view, but I generally found this to be disturbing the contents, in the least causing an unnecessary redraw because of the bounds change.

Instead I prefer to set the content inset of the scroll view. This does not affect the contents, but permits for additional scroll distance so that the content at the bottom edge can be scrolled into the visible area above the keyboard. The inset for the scroll indicator is separate but needs the same treatment.

Note that all this is best done in your UIScrollView subclass.

NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(keyboardDidShow:) 
       name:UIKeyboardDidShowNotification object:nil];
[center addObserver:self selector:@selector(keyboardWillHide:) 
       name:UIKeyboardWillHideNotification object:nil];

And of course we remove the observer in the dealloc.

- (void)dealloc
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[super dealloc];
}

When the keyboard is hidden we don’t want any insets, so we reset them to zero.

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

And now the good part, calculating the correct covered area of the view, regardless of where it is positioned on screen. Yet another complication derives from the fact that if we use the scrollview’s coordinate system then we get different results depending on the content size.

So instead we need to use the superview’s coordinate system for transforming to and from the window coordinate system. This is necessary because the keyboard is mounted on the window and thus also has window coordinates.

- (void)keyboardDidShow:(NSNotification *)notification
{
	// keyboard frame is in window coordinates
	NSDictionary *userInfo = [notification userInfo];
	CGRect keyboardFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
 
	// convert own frame to window coordinates, frame is in superview's coordinates
	CGRect ownFrame = [self.window convertRect:self.frame fromView:self.superview];
 
	// calculate the area of own frame that is covered by keyboard
	CGRect coveredFrame = CGRectIntersection(ownFrame, keyboardFrame);
 
	// now this might be rotated, so convert it back
	coveredFrame = [self.window convertRect:coveredFrame toView:self.superview];
 
	// set inset to make up for covered array at bottom
	self.contentInset = UIEdgeInsetsMake(0, 0, coveredFrame.size.height, 0);
	self.scrollIndicatorInsets = self.contentInset;
}

We use the handy CGRectIntersection function to get a rectangle that is common to both the keyboard and view frame. Since there might have been a rotation transform somewhere in between, we also need to convert back to our original superview’s coordinate system. And that’s it, now coveredFrame gives us exactly the area of our view that is covered and thus the hight is also the bottom inset we need to apply.


Categories: Recipes

8 Comments »

  1. Will this technique also work with the new split keyboard mode coming soon?

  2. This have to be extended for a split keyboard. This code only works with the current keyboard on iPhone and iPad

  3. Just wanted to say thank you for doing the hard work for me 🙂

    I love elegant solutions like this…

    Tag

  4. I love elegant solutions myself. 😉

    Please Flattr, Retweet and otherwise share things you like on this blog here.

  5. I’m studying IOS and I would love to try your solution but I struggle with the problems
    “Property ‘contentInset’ not found on object of type ‘View1 *’ ”
    “Property ‘scrollIndicatorInsets’ not found on object of type ‘View1 *’ ”
    e così anche per ‘scrollIndicatorInsets’.

    Could you post some source?

  6. contentInset and scrollIndicatorInsets are only available on UIScrollViews.