Skip to content

Commit

Permalink
More robustness when frame or content size changes; Disable TPKeyboar…
Browse files Browse the repository at this point in the history
…dAvoidingTableView on iOS 4.3+
  • Loading branch information
michaeltyson committed Aug 19, 2011
1 parent d6ebc02 commit 33d145f
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 84 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ For use with `UITableViewController` classes, drop `TPKeyboardAvoidingTableView.

For non-UITableViewControllers, drop the `TPKeyboardAvoidingScrollView.m` and `TPKeyboardAvoidingScrollView.h` source files into your project, pop a `UIScrollView` into your view controller's xib, set the scroll view's class to `TPKeyboardAvoidingScrollView`, and put all your controls within that scroll view. You can also create it programmatically, without using a xib - just use the TPKeyboardAvoidingScrollView as your top-level view.

Notes
-----

On iOS 4.3 and up, UITableView automatically supports moving out of the way of the keyboard. Thus, using TPKeyboardAvoidingTableView on iOS 4.3+ environments will have no effect -- in fact, if iOS 4.3 or up is detected, the init methods for TPKeyboardAvoidingTableView will return a UITableView instead.

These classes currently adjust the contentInset parameter to avoid content moving beneath the keyboard. This is done, as opposed to adjusting the frame, in order to work around an iOS bug that results in a jerky animation where the view jumps upwards, before settling down. In order to facilitate this workaround, the contentSize is maintained to be at least same size as the view's frame.

Licence
-------

Expand Down
6 changes: 4 additions & 2 deletions TPKeyboardAvoidingScrollView.h
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
//

@interface TPKeyboardAvoidingScrollView : UIScrollView {
UIEdgeInsets priorInset;
BOOL _keyboardVisible;
UIEdgeInsets _priorInset;
BOOL _keyboardVisible;
CGRect _keyboardRect;
CGSize _originalContentSize;
}

- (void)adjustOffsetToIdealIfNeeded;
Expand Down
95 changes: 62 additions & 33 deletions TPKeyboardAvoidingScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@

@interface TPKeyboardAvoidingScrollView ()
- (UIView*)findFirstResponderBeneathView:(UIView*)view;
- (UIEdgeInsets)contentInsetForKeyboard;
- (CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space;
- (CGRect)keyboardRect;
@end

@implementation TPKeyboardAvoidingScrollView
Expand Down Expand Up @@ -39,58 +41,71 @@ -(void)dealloc {
[super dealloc];
}

-(void)setFrame:(CGRect)frame {
[super setFrame:frame];

CGSize contentSize = _originalContentSize;
contentSize.width = MAX(contentSize.width, self.frame.size.width);
contentSize.height = MAX(contentSize.height, self.frame.size.height);
[super setContentSize:contentSize];

if ( _keyboardVisible ) {
self.contentInset = [self contentInsetForKeyboard];
}
}

-(void)setContentSize:(CGSize)contentSize {
_originalContentSize = contentSize;

contentSize.width = MAX(contentSize.width, self.frame.size.width);
contentSize.height = MAX(contentSize.height, self.frame.size.height);
[super setContentSize:contentSize];

if ( _keyboardVisible ) {
self.contentInset = [self contentInsetForKeyboard];
}
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[[self findFirstResponderBeneathView:self] resignFirstResponder];
[super touchesEnded:touches withEvent:event];
}

- (void)keyboardWillShow:(NSNotification*)notification {
_keyboardRect = [[[notification userInfo] objectForKey:_UIKeyboardFrameEndUserInfoKey] CGRectValue];
_keyboardVisible = YES;

UIView *firstResponder = [self findFirstResponderBeneathView:self];
if ( !firstResponder ) {
// No child view is the first responder - nothing to do here
return;
}

priorInset = self.contentInset;

// Use this view's coordinate system
CGRect keyboardBounds = [self convertRect:[[[notification userInfo] objectForKey:_UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:nil];
CGRect screenBounds = [self convertRect:[UIScreen mainScreen].bounds fromView:nil];
if ( keyboardBounds.origin.y == 0 ) keyboardBounds.origin = CGPointMake(0, screenBounds.size.height - keyboardBounds.size.height);

CGFloat spaceAboveKeyboard = keyboardBounds.origin.y - self.bounds.origin.y;

UIEdgeInsets newInset = self.contentInset;

newInset.bottom = keyboardBounds.size.height -
((keyboardBounds.origin.y+keyboardBounds.size.height)
- (self.bounds.origin.y+self.bounds.size.height));


CGFloat offset = [self idealOffsetForView:firstResponder withSpace:spaceAboveKeyboard];
_priorInset = self.contentInset;

// Shrink view's inset by the keyboard's height, and scroll to show the text field/view being edited
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]];
self.contentInset = newInset;


[self setContentOffset:CGPointMake(self.contentOffset.x, offset) animated:YES];

self.contentInset = [self contentInsetForKeyboard];
[self setContentOffset:CGPointMake(self.contentOffset.x,
[self idealOffsetForView:firstResponder withSpace:[self keyboardRect].origin.y - self.bounds.origin.y])
animated:YES];

[UIView commitAnimations];
_keyboardVisible = true;
}

- (void)keyboardWillHide:(NSNotification*)notification {

_keyboardRect = CGRectZero;
_keyboardVisible = NO;

// Restore dimensions to prior size
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]];
[UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]];
self.contentInset = priorInset;
self.contentInset = _priorInset;
[UIView commitAnimations];
_keyboardVisible = false;
}

- (UIView*)findFirstResponderBeneathView:(UIView*)view {
Expand All @@ -103,14 +118,19 @@ - (UIView*)findFirstResponderBeneathView:(UIView*)view {
return nil;
}

- (UIEdgeInsets)contentInsetForKeyboard {
UIEdgeInsets newInset = self.contentInset;
CGRect keyboardRect = [self keyboardRect];
newInset.bottom = keyboardRect.size.height - ((keyboardRect.origin.y+keyboardRect.size.height) - (self.bounds.origin.y+self.bounds.size.height));
return newInset;
}

-(CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space
{
-(CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space {

//Convert the rect to get the view's distance from the top of the scrollView.
// Convert the rect to get the view's distance from the top of the scrollView.
CGRect rect = [view convertRect:view.bounds toView:self];

//set starting offset to that point
// Set starting offset to that point
CGFloat offset = rect.origin.y;


Expand All @@ -133,10 +153,10 @@ -(CGFloat)idealOffsetForView:(UIView *)view withSpace:(CGFloat)space
return offset;
}

-(void)adjustOffsetToIdealIfNeeded
{
//only do this if the keyboard is already visible
if (!_keyboardVisible) return;
-(void)adjustOffsetToIdealIfNeeded {
// Only do this if the keyboard is already visible
if ( !_keyboardVisible ) return;

CGFloat visibleSpace = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom;

Expand All @@ -145,4 +165,13 @@ -(void)adjustOffsetToIdealIfNeeded
[self setContentOffset:idealOffset animated:YES];
}

- (CGRect)keyboardRect {
CGRect keyboardRect = [self convertRect:_keyboardRect fromView:nil];
if ( keyboardRect.origin.y == 0 ) {
CGRect screenBounds = [self convertRect:[UIScreen mainScreen].bounds fromView:nil];
keyboardRect.origin = CGPointMake(0, screenBounds.size.height - keyboardRect.size.height);
}
return keyboardRect;
}

@end
8 changes: 4 additions & 4 deletions TPKeyboardAvoidingTableView.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
// Copyright 2011 A Tasty Pixel. All rights reserved.
//

#import <UIKit/UIKit.h>


@interface TPKeyboardAvoidingTableView : UITableView {
CGRect priorFrame;
UIEdgeInsets _priorInset;
BOOL _keyboardVisible;
CGRect _keyboardRect;
}

- (void)adjustOffsetToIdealIfNeeded;
@end
Loading

0 comments on commit 33d145f

Please sign in to comment.