Commit f35b3f9a70a903bfec0345c8e625e2e11dc77a2d
1 parent
256868a43c
Exists in
master
and in
1 other branch
fix bug baseviewcontroller
Showing 8 changed files with 822 additions and 0 deletions Side-by-side Diff
- LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingCollectionView.h
- LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingCollectionView.m
- LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingScrollView.h
- LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingScrollView.m
- LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingTableView.h
- LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingTableView.m
- LifeLog/LifeLog/TPKeyboardAvoiding/UIScrollView+TPKeyboardAvoidingAdditions.h
- LifeLog/LifeLog/TPKeyboardAvoiding/UIScrollView+TPKeyboardAvoidingAdditions.m
LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingCollectionView.h
| 1 | +// | |
| 2 | +// TPKeyboardAvoidingCollectionView.h | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel & The CocoaBots. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import <UIKit/UIKit.h> | |
| 10 | +#import "UIScrollView+TPKeyboardAvoidingAdditions.h" | |
| 11 | + | |
| 12 | +@interface TPKeyboardAvoidingCollectionView : UICollectionView <UITextFieldDelegate, UITextViewDelegate> | |
| 13 | +- (BOOL)focusNextTextField; | |
| 14 | +- (void)scrollToActiveTextField; | |
| 15 | +@end |
LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingCollectionView.m
| 1 | +// | |
| 2 | +// TPKeyboardAvoidingCollectionView.m | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel & The CocoaBots. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import "TPKeyboardAvoidingCollectionView.h" | |
| 10 | + | |
| 11 | +@interface TPKeyboardAvoidingCollectionView () <UITextFieldDelegate, UITextViewDelegate> | |
| 12 | +@end | |
| 13 | + | |
| 14 | +@implementation TPKeyboardAvoidingCollectionView | |
| 15 | + | |
| 16 | +#pragma mark - Setup/Teardown | |
| 17 | + | |
| 18 | +- (void)setup { | |
| 19 | + if ( [self hasAutomaticKeyboardAvoidingBehaviour] ) return; | |
| 20 | + | |
| 21 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(TPKeyboardAvoiding_keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil]; | |
| 22 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(TPKeyboardAvoiding_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; | |
| 23 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollToActiveTextField) name:UITextViewTextDidBeginEditingNotification object:nil]; | |
| 24 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollToActiveTextField) name:UITextFieldTextDidBeginEditingNotification object:nil]; | |
| 25 | +} | |
| 26 | + | |
| 27 | +-(id)initWithFrame:(CGRect)frame { | |
| 28 | + if ( !(self = [super initWithFrame:frame]) ) return nil; | |
| 29 | + [self setup]; | |
| 30 | + return self; | |
| 31 | +} | |
| 32 | + | |
| 33 | +- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { | |
| 34 | + if ( !(self = [super initWithFrame:frame collectionViewLayout:layout]) ) return nil; | |
| 35 | + [self setup]; | |
| 36 | + return self; | |
| 37 | +} | |
| 38 | + | |
| 39 | +-(void)awakeFromNib { | |
| 40 | + [super awakeFromNib]; | |
| 41 | + [self setup]; | |
| 42 | +} | |
| 43 | + | |
| 44 | +-(void)dealloc { | |
| 45 | + [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 46 | +#if !__has_feature(objc_arc) | |
| 47 | + [super dealloc]; | |
| 48 | +#endif | |
| 49 | +} | |
| 50 | + | |
| 51 | + | |
| 52 | +-(BOOL)hasAutomaticKeyboardAvoidingBehaviour { | |
| 53 | + if ( [[[UIDevice currentDevice] systemVersion] integerValue] >= 9 | |
| 54 | + && [self.delegate isKindOfClass:[UICollectionViewController class]] ) { | |
| 55 | + // Theory: It looks like iOS 9's collection views automatically avoid the keyboard. As usual | |
| 56 | + // Apple have totally failed to document this anywhere, so this is just a guess. | |
| 57 | + return YES; | |
| 58 | + } | |
| 59 | + | |
| 60 | + return NO; | |
| 61 | +} | |
| 62 | + | |
| 63 | +-(void)setFrame:(CGRect)frame { | |
| 64 | + [super setFrame:frame]; | |
| 65 | + [self TPKeyboardAvoiding_updateContentInset]; | |
| 66 | +} | |
| 67 | + | |
| 68 | +-(void)setContentSize:(CGSize)contentSize { | |
| 69 | + if (CGSizeEqualToSize(contentSize, self.contentSize)) { | |
| 70 | + // Prevent triggering contentSize when it's already the same that | |
| 71 | + // cause weird infinte scrolling and locking bug | |
| 72 | + return; | |
| 73 | + } | |
| 74 | + [super setContentSize:contentSize]; | |
| 75 | + [self TPKeyboardAvoiding_updateContentInset]; | |
| 76 | +} | |
| 77 | + | |
| 78 | +- (BOOL)focusNextTextField { | |
| 79 | + return [self TPKeyboardAvoiding_focusNextTextField]; | |
| 80 | + | |
| 81 | +} | |
| 82 | +- (void)scrollToActiveTextField { | |
| 83 | + return [self TPKeyboardAvoiding_scrollToActiveTextField]; | |
| 84 | +} | |
| 85 | + | |
| 86 | +#pragma mark - Responders, events | |
| 87 | + | |
| 88 | +-(void)willMoveToSuperview:(UIView *)newSuperview { | |
| 89 | + [super willMoveToSuperview:newSuperview]; | |
| 90 | + if ( !newSuperview ) { | |
| 91 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) object:self]; | |
| 92 | + } | |
| 93 | +} | |
| 94 | + | |
| 95 | +- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { | |
| 96 | + [[self TPKeyboardAvoiding_findFirstResponderBeneathView:self] resignFirstResponder]; | |
| 97 | + [super touchesEnded:touches withEvent:event]; | |
| 98 | +} | |
| 99 | + | |
| 100 | +-(BOOL)textFieldShouldReturn:(UITextField *)textField { | |
| 101 | + if ( ![self focusNextTextField] ) { | |
| 102 | + [textField resignFirstResponder]; | |
| 103 | + } | |
| 104 | + return YES; | |
| 105 | +} | |
| 106 | + | |
| 107 | +-(void)layoutSubviews { | |
| 108 | + [super layoutSubviews]; | |
| 109 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) object:self]; | |
| 110 | + [self performSelector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) withObject:self afterDelay:0.1]; | |
| 111 | +} | |
| 112 | + | |
| 113 | +@end |
LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingScrollView.h
| 1 | +// | |
| 2 | +// TPKeyboardAvoidingScrollView.h | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import <UIKit/UIKit.h> | |
| 10 | +#import "UIScrollView+TPKeyboardAvoidingAdditions.h" | |
| 11 | + | |
| 12 | +@interface TPKeyboardAvoidingScrollView : UIScrollView <UITextFieldDelegate, UITextViewDelegate> | |
| 13 | +- (void)contentSizeToFit; | |
| 14 | +- (BOOL)focusNextTextField; | |
| 15 | +- (void)scrollToActiveTextField; | |
| 16 | +@end |
LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingScrollView.m
| 1 | +// | |
| 2 | +// TPKeyboardAvoidingScrollView.m | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import "TPKeyboardAvoidingScrollView.h" | |
| 10 | + | |
| 11 | +@interface TPKeyboardAvoidingScrollView () <UITextFieldDelegate, UITextViewDelegate> | |
| 12 | +@end | |
| 13 | + | |
| 14 | +@implementation TPKeyboardAvoidingScrollView | |
| 15 | + | |
| 16 | +#pragma mark - Setup/Teardown | |
| 17 | + | |
| 18 | +- (void)setup { | |
| 19 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(TPKeyboardAvoiding_keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil]; | |
| 20 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(TPKeyboardAvoiding_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; | |
| 21 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollToActiveTextField) name:UITextViewTextDidBeginEditingNotification object:nil]; | |
| 22 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollToActiveTextField) name:UITextFieldTextDidBeginEditingNotification object:nil]; | |
| 23 | +} | |
| 24 | + | |
| 25 | +-(id)initWithFrame:(CGRect)frame { | |
| 26 | + if ( !(self = [super initWithFrame:frame]) ) return nil; | |
| 27 | + [self setup]; | |
| 28 | + return self; | |
| 29 | +} | |
| 30 | + | |
| 31 | +-(void)awakeFromNib { | |
| 32 | + [super awakeFromNib]; | |
| 33 | + [self setup]; | |
| 34 | +} | |
| 35 | + | |
| 36 | +-(void)dealloc { | |
| 37 | + [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 38 | +#if !__has_feature(objc_arc) | |
| 39 | + [super dealloc]; | |
| 40 | +#endif | |
| 41 | +} | |
| 42 | + | |
| 43 | +-(void)setFrame:(CGRect)frame { | |
| 44 | + [super setFrame:frame]; | |
| 45 | + [self TPKeyboardAvoiding_updateContentInset]; | |
| 46 | +} | |
| 47 | + | |
| 48 | +-(void)setContentSize:(CGSize)contentSize { | |
| 49 | + [super setContentSize:contentSize]; | |
| 50 | + [self TPKeyboardAvoiding_updateFromContentSizeChange]; | |
| 51 | +} | |
| 52 | + | |
| 53 | +- (void)contentSizeToFit { | |
| 54 | + self.contentSize = [self TPKeyboardAvoiding_calculatedContentSizeFromSubviewFrames]; | |
| 55 | +} | |
| 56 | + | |
| 57 | +- (BOOL)focusNextTextField { | |
| 58 | + return [self TPKeyboardAvoiding_focusNextTextField]; | |
| 59 | + | |
| 60 | +} | |
| 61 | +- (void)scrollToActiveTextField { | |
| 62 | + return [self TPKeyboardAvoiding_scrollToActiveTextField]; | |
| 63 | +} | |
| 64 | + | |
| 65 | +#pragma mark - Responders, events | |
| 66 | + | |
| 67 | +-(void)willMoveToSuperview:(UIView *)newSuperview { | |
| 68 | + [super willMoveToSuperview:newSuperview]; | |
| 69 | + if ( !newSuperview ) { | |
| 70 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) object:self]; | |
| 71 | + } | |
| 72 | +} | |
| 73 | + | |
| 74 | +- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { | |
| 75 | + [[self TPKeyboardAvoiding_findFirstResponderBeneathView:self] resignFirstResponder]; | |
| 76 | + [super touchesEnded:touches withEvent:event]; | |
| 77 | +} | |
| 78 | + | |
| 79 | +-(BOOL)textFieldShouldReturn:(UITextField *)textField { | |
| 80 | + if ( ![self focusNextTextField] ) { | |
| 81 | + [textField resignFirstResponder]; | |
| 82 | + } | |
| 83 | + return YES; | |
| 84 | +} | |
| 85 | + | |
| 86 | +-(void)layoutSubviews { | |
| 87 | + [super layoutSubviews]; | |
| 88 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) object:self]; | |
| 89 | + [self performSelector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) withObject:self afterDelay:0.1]; | |
| 90 | +} | |
| 91 | + | |
| 92 | +@end |
LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingTableView.h
| 1 | +// | |
| 2 | +// TPKeyboardAvoidingTableView.h | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import <UIKit/UIKit.h> | |
| 10 | +#import "UIScrollView+TPKeyboardAvoidingAdditions.h" | |
| 11 | + | |
| 12 | +@interface TPKeyboardAvoidingTableView : UITableView <UITextFieldDelegate, UITextViewDelegate> | |
| 13 | +- (BOOL)focusNextTextField; | |
| 14 | +- (void)scrollToActiveTextField; | |
| 15 | +@end |
LifeLog/LifeLog/TPKeyboardAvoiding/TPKeyboardAvoidingTableView.m
| 1 | +// | |
| 2 | +// TPKeyboardAvoidingTableView.m | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import "TPKeyboardAvoidingTableView.h" | |
| 10 | + | |
| 11 | +@interface TPKeyboardAvoidingTableView () <UITextFieldDelegate, UITextViewDelegate> | |
| 12 | +@end | |
| 13 | + | |
| 14 | +@implementation TPKeyboardAvoidingTableView | |
| 15 | + | |
| 16 | +#pragma mark - Setup/Teardown | |
| 17 | + | |
| 18 | +- (void)setup { | |
| 19 | + if ( [self hasAutomaticKeyboardAvoidingBehaviour] ) return; | |
| 20 | + | |
| 21 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(TPKeyboardAvoiding_keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil]; | |
| 22 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(TPKeyboardAvoiding_keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; | |
| 23 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollToActiveTextField) name:UITextViewTextDidBeginEditingNotification object:nil]; | |
| 24 | + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollToActiveTextField) name:UITextFieldTextDidBeginEditingNotification object:nil]; | |
| 25 | +} | |
| 26 | + | |
| 27 | +-(id)initWithFrame:(CGRect)frame { | |
| 28 | + if ( !(self = [super initWithFrame:frame]) ) return nil; | |
| 29 | + [self setup]; | |
| 30 | + return self; | |
| 31 | +} | |
| 32 | + | |
| 33 | +-(id)initWithFrame:(CGRect)frame style:(UITableViewStyle)withStyle { | |
| 34 | + if ( !(self = [super initWithFrame:frame style:withStyle]) ) return nil; | |
| 35 | + [self setup]; | |
| 36 | + return self; | |
| 37 | +} | |
| 38 | + | |
| 39 | +-(void)awakeFromNib { | |
| 40 | + [super awakeFromNib]; | |
| 41 | + [self setup]; | |
| 42 | +} | |
| 43 | + | |
| 44 | +-(void)dealloc { | |
| 45 | + [[NSNotificationCenter defaultCenter] removeObserver:self]; | |
| 46 | +#if !__has_feature(objc_arc) | |
| 47 | + [super dealloc]; | |
| 48 | +#endif | |
| 49 | +} | |
| 50 | + | |
| 51 | +-(BOOL)hasAutomaticKeyboardAvoidingBehaviour { | |
| 52 | + if ( [self.delegate isKindOfClass:[UITableViewController class]] ) { | |
| 53 | + // Theory: Apps built using the iOS 8.3 SDK (probably: older SDKs not tested) seem to handle keyboard | |
| 54 | + // avoiding automatically with UITableViewController. This doesn't seem to be documented anywhere | |
| 55 | + // by Apple, so results obtained only empirically. | |
| 56 | + return YES; | |
| 57 | + } | |
| 58 | + | |
| 59 | + return NO; | |
| 60 | +} | |
| 61 | + | |
| 62 | +-(void)setFrame:(CGRect)frame { | |
| 63 | + [super setFrame:frame]; | |
| 64 | + if ( [self hasAutomaticKeyboardAvoidingBehaviour] ) return; | |
| 65 | + [self TPKeyboardAvoiding_updateContentInset]; | |
| 66 | +} | |
| 67 | + | |
| 68 | +-(void)setContentSize:(CGSize)contentSize { | |
| 69 | + if ( [self hasAutomaticKeyboardAvoidingBehaviour] ) { | |
| 70 | + [super setContentSize:contentSize]; | |
| 71 | + return; | |
| 72 | + } | |
| 73 | + if (CGSizeEqualToSize(contentSize, self.contentSize)) { | |
| 74 | + // Prevent triggering contentSize when it's already the same | |
| 75 | + // this cause table view to scroll to top on contentInset changes | |
| 76 | + return; | |
| 77 | + } | |
| 78 | + [super setContentSize:contentSize]; | |
| 79 | + [self TPKeyboardAvoiding_updateContentInset]; | |
| 80 | +} | |
| 81 | + | |
| 82 | +- (BOOL)focusNextTextField { | |
| 83 | + return [self TPKeyboardAvoiding_focusNextTextField]; | |
| 84 | + | |
| 85 | +} | |
| 86 | +- (void)scrollToActiveTextField { | |
| 87 | + return [self TPKeyboardAvoiding_scrollToActiveTextField]; | |
| 88 | +} | |
| 89 | + | |
| 90 | +#pragma mark - Responders, events | |
| 91 | + | |
| 92 | +-(void)willMoveToSuperview:(UIView *)newSuperview { | |
| 93 | + [super willMoveToSuperview:newSuperview]; | |
| 94 | + if ( !newSuperview ) { | |
| 95 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) object:self]; | |
| 96 | + } | |
| 97 | +} | |
| 98 | + | |
| 99 | +- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { | |
| 100 | + [[self TPKeyboardAvoiding_findFirstResponderBeneathView:self] resignFirstResponder]; | |
| 101 | + [super touchesEnded:touches withEvent:event]; | |
| 102 | +} | |
| 103 | + | |
| 104 | +-(BOOL)textFieldShouldReturn:(UITextField *)textField { | |
| 105 | + if ( ![self focusNextTextField] ) { | |
| 106 | + [textField resignFirstResponder]; | |
| 107 | + } | |
| 108 | + return YES; | |
| 109 | +} | |
| 110 | + | |
| 111 | +-(void)layoutSubviews { | |
| 112 | + [super layoutSubviews]; | |
| 113 | + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) object:self]; | |
| 114 | + [self performSelector:@selector(TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:) withObject:self afterDelay:0.1]; | |
| 115 | +} | |
| 116 | + | |
| 117 | +@end |
LifeLog/LifeLog/TPKeyboardAvoiding/UIScrollView+TPKeyboardAvoidingAdditions.h
| 1 | +// | |
| 2 | +// UIScrollView+TPKeyboardAvoidingAdditions.h | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import <UIKit/UIKit.h> | |
| 10 | + | |
| 11 | +@interface UIScrollView (TPKeyboardAvoidingAdditions) | |
| 12 | +- (BOOL)TPKeyboardAvoiding_focusNextTextField; | |
| 13 | +- (void)TPKeyboardAvoiding_scrollToActiveTextField; | |
| 14 | + | |
| 15 | +- (void)TPKeyboardAvoiding_keyboardWillShow:(NSNotification*)notification; | |
| 16 | +- (void)TPKeyboardAvoiding_keyboardWillHide:(NSNotification*)notification; | |
| 17 | +- (void)TPKeyboardAvoiding_updateContentInset; | |
| 18 | +- (void)TPKeyboardAvoiding_updateFromContentSizeChange; | |
| 19 | +- (void)TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:(UIView*)view; | |
| 20 | +- (UIView*)TPKeyboardAvoiding_findFirstResponderBeneathView:(UIView*)view; | |
| 21 | +-(CGSize)TPKeyboardAvoiding_calculatedContentSizeFromSubviewFrames; | |
| 22 | +@end |
LifeLog/LifeLog/TPKeyboardAvoiding/UIScrollView+TPKeyboardAvoidingAdditions.m
| 1 | +// | |
| 2 | +// UIScrollView+TPKeyboardAvoidingAdditions.m | |
| 3 | +// TPKeyboardAvoiding | |
| 4 | +// | |
| 5 | +// Created by Michael Tyson on 30/09/2013. | |
| 6 | +// Copyright 2015 A Tasty Pixel. All rights reserved. | |
| 7 | +// | |
| 8 | + | |
| 9 | +#import "UIScrollView+TPKeyboardAvoidingAdditions.h" | |
| 10 | +#import "TPKeyboardAvoidingScrollView.h" | |
| 11 | +#import <objc/runtime.h> | |
| 12 | + | |
| 13 | +static const CGFloat kCalculatedContentPadding = 10; | |
| 14 | +static const CGFloat kMinimumScrollOffsetPadding = 20; | |
| 15 | + | |
| 16 | +static NSString * const kUIKeyboardAnimationDurationUserInfoKey = @"UIKeyboardAnimationDurationUserInfoKey"; | |
| 17 | + | |
| 18 | +static const int kStateKey; | |
| 19 | + | |
| 20 | +#define _UIKeyboardFrameEndUserInfoKey (&UIKeyboardFrameEndUserInfoKey != NULL ? UIKeyboardFrameEndUserInfoKey : @"UIKeyboardBoundsUserInfoKey") | |
| 21 | + | |
| 22 | +@interface TPKeyboardAvoidingState : NSObject | |
| 23 | +@property (nonatomic, assign) UIEdgeInsets priorInset; | |
| 24 | +@property (nonatomic, assign) UIEdgeInsets priorScrollIndicatorInsets; | |
| 25 | +@property (nonatomic, assign) BOOL keyboardVisible; | |
| 26 | +@property (nonatomic, assign) CGRect keyboardRect; | |
| 27 | +@property (nonatomic, assign) CGSize priorContentSize; | |
| 28 | +@property (nonatomic, assign) BOOL priorPagingEnabled; | |
| 29 | +@property (nonatomic, assign) BOOL ignoringNotifications; | |
| 30 | +@property (nonatomic, assign) BOOL keyboardAnimationInProgress; | |
| 31 | +@property (nonatomic, assign) CGFloat animationDuration; | |
| 32 | +@end | |
| 33 | + | |
| 34 | +@implementation UIScrollView (TPKeyboardAvoidingAdditions) | |
| 35 | + | |
| 36 | +- (TPKeyboardAvoidingState*)keyboardAvoidingState { | |
| 37 | + TPKeyboardAvoidingState *state = objc_getAssociatedObject(self, &kStateKey); | |
| 38 | + if ( !state ) { | |
| 39 | + state = [[TPKeyboardAvoidingState alloc] init]; | |
| 40 | + objc_setAssociatedObject(self, &kStateKey, state, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| 41 | +#if !__has_feature(objc_arc) | |
| 42 | + [state release]; | |
| 43 | +#endif | |
| 44 | + } | |
| 45 | + return state; | |
| 46 | +} | |
| 47 | + | |
| 48 | +- (void)TPKeyboardAvoiding_keyboardWillShow:(NSNotification*)notification { | |
| 49 | + NSDictionary *info = [notification userInfo]; | |
| 50 | + TPKeyboardAvoidingState *state = self.keyboardAvoidingState; | |
| 51 | + | |
| 52 | + state.animationDuration = [[info objectForKey:kUIKeyboardAnimationDurationUserInfoKey] doubleValue]; | |
| 53 | + | |
| 54 | + CGRect keyboardRect = [self convertRect:[[info objectForKey:_UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:nil]; | |
| 55 | + if (CGRectIsEmpty(keyboardRect)) { | |
| 56 | + return; | |
| 57 | + } | |
| 58 | + | |
| 59 | + if ( state.ignoringNotifications ) { | |
| 60 | + return; | |
| 61 | + } | |
| 62 | + | |
| 63 | + state.keyboardRect = keyboardRect; | |
| 64 | + | |
| 65 | + if ( !state.keyboardVisible ) { | |
| 66 | + state.priorInset = self.contentInset; | |
| 67 | + state.priorScrollIndicatorInsets = self.scrollIndicatorInsets; | |
| 68 | + state.priorPagingEnabled = self.pagingEnabled; | |
| 69 | + } | |
| 70 | + | |
| 71 | + state.keyboardVisible = YES; | |
| 72 | + self.pagingEnabled = NO; | |
| 73 | + | |
| 74 | + if ( [self isKindOfClass:[TPKeyboardAvoidingScrollView class]] ) { | |
| 75 | + state.priorContentSize = self.contentSize; | |
| 76 | + | |
| 77 | + if ( CGSizeEqualToSize(self.contentSize, CGSizeZero) ) { | |
| 78 | + // Set the content size, if it's not set. Do not set content size explicitly if auto-layout | |
| 79 | + // is being used to manage subviews | |
| 80 | + self.contentSize = [self TPKeyboardAvoiding_calculatedContentSizeFromSubviewFrames]; | |
| 81 | + } | |
| 82 | + } | |
| 83 | + | |
| 84 | + // Delay until a future run loop such that the cursor position is available in a text view | |
| 85 | + // In other words, it's not available (specifically, the prior cursor position is returned) when the first keyboard position change notification fires | |
| 86 | + // NOTE: Unfortunately, using dispatch_async(main_queue) did not result in a sufficient-enough delay | |
| 87 | + // for the text view's current cursor position to be available | |
| 88 | + dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)); | |
| 89 | + dispatch_after(delay, dispatch_get_main_queue(), ^{ | |
| 90 | + | |
| 91 | + // Shrink view's inset by the keyboard's height, and scroll to show the text field/view being edited | |
| 92 | + [UIView beginAnimations:nil context:NULL]; | |
| 93 | + | |
| 94 | + [UIView setAnimationDelegate:self]; | |
| 95 | + [UIView setAnimationWillStartSelector:@selector(keyboardViewAppear:context:)]; | |
| 96 | + [UIView setAnimationDidStopSelector:@selector(keyboardViewDisappear:finished:context:)]; | |
| 97 | + | |
| 98 | + [UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; | |
| 99 | + [UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]]; | |
| 100 | + | |
| 101 | + UIView *firstResponder = [self TPKeyboardAvoiding_findFirstResponderBeneathView:self]; | |
| 102 | + if ( firstResponder ) { | |
| 103 | + | |
| 104 | + self.contentInset = [self TPKeyboardAvoiding_contentInsetForKeyboard]; | |
| 105 | + | |
| 106 | + CGFloat viewableHeight = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom; | |
| 107 | + [self setContentOffset:CGPointMake(self.contentOffset.x, | |
| 108 | + [self TPKeyboardAvoiding_idealOffsetForView:firstResponder | |
| 109 | + withViewingAreaHeight:viewableHeight]) | |
| 110 | + animated:NO]; | |
| 111 | + } | |
| 112 | + | |
| 113 | + self.scrollIndicatorInsets = self.contentInset; | |
| 114 | + [self layoutIfNeeded]; | |
| 115 | + | |
| 116 | + [UIView commitAnimations]; | |
| 117 | + }); | |
| 118 | +} | |
| 119 | + | |
| 120 | +- (void)keyboardViewAppear:(NSString *)animationID context:(void *)context { | |
| 121 | + self.keyboardAvoidingState.keyboardAnimationInProgress = true; | |
| 122 | +} | |
| 123 | + | |
| 124 | +- (void)keyboardViewDisappear:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { | |
| 125 | + if (finished.boolValue) { | |
| 126 | + self.keyboardAvoidingState.keyboardAnimationInProgress = false; | |
| 127 | + } | |
| 128 | +} | |
| 129 | + | |
| 130 | +- (void)TPKeyboardAvoiding_keyboardWillHide:(NSNotification*)notification { | |
| 131 | + CGRect keyboardRect = [self convertRect:[[[notification userInfo] objectForKey:_UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:nil]; | |
| 132 | + if (CGRectIsEmpty(keyboardRect) && !self.keyboardAvoidingState.keyboardAnimationInProgress) { | |
| 133 | + return; | |
| 134 | + } | |
| 135 | + | |
| 136 | + TPKeyboardAvoidingState *state = self.keyboardAvoidingState; | |
| 137 | + | |
| 138 | + if ( state.ignoringNotifications ) { | |
| 139 | + return; | |
| 140 | + } | |
| 141 | + | |
| 142 | + if ( !state.keyboardVisible ) { | |
| 143 | + return; | |
| 144 | + } | |
| 145 | + | |
| 146 | + state.keyboardRect = CGRectZero; | |
| 147 | + state.keyboardVisible = NO; | |
| 148 | + | |
| 149 | + // Restore dimensions to prior size | |
| 150 | + [UIView beginAnimations:nil context:NULL]; | |
| 151 | + [UIView setAnimationCurve:[[[notification userInfo] objectForKey:UIKeyboardAnimationCurveUserInfoKey] intValue]]; | |
| 152 | + [UIView setAnimationDuration:[[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue]]; | |
| 153 | + | |
| 154 | + if ( [self isKindOfClass:[TPKeyboardAvoidingScrollView class]] ) { | |
| 155 | + self.contentSize = state.priorContentSize; | |
| 156 | + } | |
| 157 | + | |
| 158 | + self.contentInset = state.priorInset; | |
| 159 | + self.scrollIndicatorInsets = state.priorScrollIndicatorInsets; | |
| 160 | + self.pagingEnabled = state.priorPagingEnabled; | |
| 161 | + [self layoutIfNeeded]; | |
| 162 | + [UIView commitAnimations]; | |
| 163 | +} | |
| 164 | + | |
| 165 | +- (void)TPKeyboardAvoiding_updateContentInset { | |
| 166 | + TPKeyboardAvoidingState *state = self.keyboardAvoidingState; | |
| 167 | + if ( state.keyboardVisible ) { | |
| 168 | + self.contentInset = [self TPKeyboardAvoiding_contentInsetForKeyboard]; | |
| 169 | + } | |
| 170 | +} | |
| 171 | + | |
| 172 | +- (void)TPKeyboardAvoiding_updateFromContentSizeChange { | |
| 173 | + TPKeyboardAvoidingState *state = self.keyboardAvoidingState; | |
| 174 | + if ( state.keyboardVisible ) { | |
| 175 | + state.priorContentSize = self.contentSize; | |
| 176 | + self.contentInset = [self TPKeyboardAvoiding_contentInsetForKeyboard]; | |
| 177 | + } | |
| 178 | +} | |
| 179 | + | |
| 180 | +#pragma mark - Utilities | |
| 181 | + | |
| 182 | +- (BOOL)TPKeyboardAvoiding_focusNextTextField { | |
| 183 | + UIView *firstResponder = [self TPKeyboardAvoiding_findFirstResponderBeneathView:self]; | |
| 184 | + if ( !firstResponder ) { | |
| 185 | + return NO; | |
| 186 | + } | |
| 187 | + | |
| 188 | + UIView *view = [self TPKeyboardAvoiding_findNextInputViewAfterView:firstResponder beneathView:self]; | |
| 189 | + | |
| 190 | + if ( view ) { | |
| 191 | + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{ | |
| 192 | + TPKeyboardAvoidingState *state = self.keyboardAvoidingState; | |
| 193 | + state.ignoringNotifications = YES; | |
| 194 | + [view becomeFirstResponder]; | |
| 195 | + state.ignoringNotifications = NO; | |
| 196 | + }); | |
| 197 | + return YES; | |
| 198 | + } | |
| 199 | + | |
| 200 | + return NO; | |
| 201 | +} | |
| 202 | + | |
| 203 | +-(void)TPKeyboardAvoiding_scrollToActiveTextField { | |
| 204 | + TPKeyboardAvoidingState *state = self.keyboardAvoidingState; | |
| 205 | + | |
| 206 | + if ( !state.keyboardVisible ) return; | |
| 207 | + | |
| 208 | + UIView *firstResponder = [self TPKeyboardAvoiding_findFirstResponderBeneathView:self]; | |
| 209 | + if ( !firstResponder ) { | |
| 210 | + return; | |
| 211 | + } | |
| 212 | + // Ignore any keyboard notification that occur while we scroll | |
| 213 | + // (seems to be an iOS 9 bug that causes jumping text in UITextField) | |
| 214 | + state.ignoringNotifications = YES; | |
| 215 | + | |
| 216 | + CGFloat visibleSpace = self.bounds.size.height - self.contentInset.top - self.contentInset.bottom; | |
| 217 | + | |
| 218 | + CGPoint idealOffset | |
| 219 | + = CGPointMake(self.contentOffset.x, | |
| 220 | + [self TPKeyboardAvoiding_idealOffsetForView:firstResponder | |
| 221 | + withViewingAreaHeight:visibleSpace]); | |
| 222 | + | |
| 223 | + // Ordinarily we'd use -setContentOffset:animated:YES here, but it interferes with UIScrollView | |
| 224 | + // behavior which automatically ensures that the first responder is within its bounds | |
| 225 | + [UIView animateWithDuration:state.animationDuration animations:^{ | |
| 226 | + self.contentOffset = idealOffset; | |
| 227 | + } completion:^(BOOL finished) { | |
| 228 | + state.ignoringNotifications = NO; | |
| 229 | + }]; | |
| 230 | +} | |
| 231 | + | |
| 232 | +#pragma mark - Helpers | |
| 233 | + | |
| 234 | +- (UIView*)TPKeyboardAvoiding_findFirstResponderBeneathView:(UIView*)view { | |
| 235 | + // Search recursively for first responder | |
| 236 | + for ( UIView *childView in view.subviews ) { | |
| 237 | + if ( [childView respondsToSelector:@selector(isFirstResponder)] && [childView isFirstResponder] ) return childView; | |
| 238 | + UIView *result = [self TPKeyboardAvoiding_findFirstResponderBeneathView:childView]; | |
| 239 | + if ( result ) return result; | |
| 240 | + } | |
| 241 | + return nil; | |
| 242 | +} | |
| 243 | + | |
| 244 | +- (UIView*)TPKeyboardAvoiding_findNextInputViewAfterView:(UIView*)priorView beneathView:(UIView*)view { | |
| 245 | + UIView * candidate = nil; | |
| 246 | + [self TPKeyboardAvoiding_findNextInputViewAfterView:priorView beneathView:view bestCandidate:&candidate]; | |
| 247 | + return candidate; | |
| 248 | +} | |
| 249 | + | |
| 250 | +- (void)TPKeyboardAvoiding_findNextInputViewAfterView:(UIView*)priorView beneathView:(UIView*)view bestCandidate:(UIView**)bestCandidate { | |
| 251 | + // Search recursively for input view below/to right of priorTextField | |
| 252 | + CGRect priorFrame = [self convertRect:priorView.frame fromView:priorView.superview]; | |
| 253 | + CGRect candidateFrame = *bestCandidate ? [self convertRect:(*bestCandidate).frame fromView:(*bestCandidate).superview] : CGRectZero; | |
| 254 | + CGFloat bestCandidateHeuristic = [self TPKeyboardAvoiding_nextInputViewHeuristicForViewFrame:candidateFrame]; | |
| 255 | + | |
| 256 | + for ( UIView *childView in view.subviews ) { | |
| 257 | + if ( [self TPKeyboardAvoiding_viewIsValidKeyViewCandidate:childView] ) { | |
| 258 | + CGRect frame = [self convertRect:childView.frame fromView:view]; | |
| 259 | + | |
| 260 | + // Use a heuristic to evaluate candidates | |
| 261 | + CGFloat heuristic = [self TPKeyboardAvoiding_nextInputViewHeuristicForViewFrame:frame]; | |
| 262 | + | |
| 263 | + // Find views beneath, or to the right. For those views that match, choose the view closest to the top left | |
| 264 | + if ( childView != priorView | |
| 265 | + && ((fabs(CGRectGetMinY(frame) - CGRectGetMinY(priorFrame)) < FLT_EPSILON && CGRectGetMinX(frame) > CGRectGetMinX(priorFrame)) | |
| 266 | + || CGRectGetMinY(frame) > CGRectGetMinY(priorFrame)) | |
| 267 | + && (!*bestCandidate || heuristic > bestCandidateHeuristic) ) { | |
| 268 | + | |
| 269 | + *bestCandidate = childView; | |
| 270 | + bestCandidateHeuristic = heuristic; | |
| 271 | + } | |
| 272 | + } else { | |
| 273 | + [self TPKeyboardAvoiding_findNextInputViewAfterView:priorView beneathView:childView bestCandidate:bestCandidate]; | |
| 274 | + } | |
| 275 | + } | |
| 276 | +} | |
| 277 | + | |
| 278 | +- (CGFloat)TPKeyboardAvoiding_nextInputViewHeuristicForViewFrame:(CGRect)frame { | |
| 279 | + return (-frame.origin.y * 1000.0) // Prefer elements closest to top (most important) | |
| 280 | + + (-frame.origin.x); // Prefer elements closest to left | |
| 281 | +} | |
| 282 | + | |
| 283 | +- (BOOL)TPKeyboardAvoiding_viewHiddenOrUserInteractionNotEnabled:(UIView *)view { | |
| 284 | + while ( view ) { | |
| 285 | + if ( view.hidden || !view.userInteractionEnabled ) { | |
| 286 | + return YES; | |
| 287 | + } | |
| 288 | + view = view.superview; | |
| 289 | + } | |
| 290 | + return NO; | |
| 291 | +} | |
| 292 | + | |
| 293 | +- (BOOL)TPKeyboardAvoiding_viewIsValidKeyViewCandidate:(UIView *)view { | |
| 294 | + if ( [self TPKeyboardAvoiding_viewHiddenOrUserInteractionNotEnabled:view] ) return NO; | |
| 295 | + | |
| 296 | + if ( [view isKindOfClass:[UITextField class]] && ((UITextField*)view).enabled ) { | |
| 297 | + return YES; | |
| 298 | + } | |
| 299 | + | |
| 300 | + if ( [view isKindOfClass:[UITextView class]] && ((UITextView*)view).isEditable ) { | |
| 301 | + return YES; | |
| 302 | + } | |
| 303 | + | |
| 304 | + return NO; | |
| 305 | +} | |
| 306 | + | |
| 307 | +- (void)TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:(UIView*)view { | |
| 308 | + for ( UIView *childView in view.subviews ) { | |
| 309 | + if ( ([childView isKindOfClass:[UITextField class]] || [childView isKindOfClass:[UITextView class]]) ) { | |
| 310 | + [self TPKeyboardAvoiding_initializeView:childView]; | |
| 311 | + } else { | |
| 312 | + [self TPKeyboardAvoiding_assignTextDelegateForViewsBeneathView:childView]; | |
| 313 | + } | |
| 314 | + } | |
| 315 | +} | |
| 316 | + | |
| 317 | +-(CGSize)TPKeyboardAvoiding_calculatedContentSizeFromSubviewFrames { | |
| 318 | + | |
| 319 | + BOOL wasShowingVerticalScrollIndicator = self.showsVerticalScrollIndicator; | |
| 320 | + BOOL wasShowingHorizontalScrollIndicator = self.showsHorizontalScrollIndicator; | |
| 321 | + | |
| 322 | + self.showsVerticalScrollIndicator = NO; | |
| 323 | + self.showsHorizontalScrollIndicator = NO; | |
| 324 | + | |
| 325 | + CGRect rect = CGRectZero; | |
| 326 | + for ( UIView *view in self.subviews ) { | |
| 327 | + rect = CGRectUnion(rect, view.frame); | |
| 328 | + } | |
| 329 | + rect.size.height += kCalculatedContentPadding; | |
| 330 | + | |
| 331 | + self.showsVerticalScrollIndicator = wasShowingVerticalScrollIndicator; | |
| 332 | + self.showsHorizontalScrollIndicator = wasShowingHorizontalScrollIndicator; | |
| 333 | + | |
| 334 | + return rect.size; | |
| 335 | +} | |
| 336 | + | |
| 337 | + | |
| 338 | +- (UIEdgeInsets)TPKeyboardAvoiding_contentInsetForKeyboard { | |
| 339 | + TPKeyboardAvoidingState *state = self.keyboardAvoidingState; | |
| 340 | + UIEdgeInsets newInset = self.contentInset; | |
| 341 | + CGRect keyboardRect = state.keyboardRect; | |
| 342 | + newInset.bottom = keyboardRect.size.height - MAX((CGRectGetMaxY(keyboardRect) - CGRectGetMaxY(self.bounds)), 0); | |
| 343 | + return newInset; | |
| 344 | +} | |
| 345 | + | |
| 346 | +-(CGFloat)TPKeyboardAvoiding_idealOffsetForView:(UIView *)view withViewingAreaHeight:(CGFloat)viewAreaHeight { | |
| 347 | + CGSize contentSize = self.contentSize; | |
| 348 | + __block CGFloat offset = 0.0; | |
| 349 | + | |
| 350 | + CGRect subviewRect = [view convertRect:view.bounds toView:self]; | |
| 351 | + | |
| 352 | + __block CGFloat padding = 0.0; | |
| 353 | + | |
| 354 | + void(^centerViewInViewableArea)() = ^ { | |
| 355 | + // Attempt to center the subview in the visible space | |
| 356 | + padding = (viewAreaHeight - subviewRect.size.height) / 2; | |
| 357 | + | |
| 358 | + // But if that means there will be less than kMinimumScrollOffsetPadding | |
| 359 | + // pixels above the view, then substitute kMinimumScrollOffsetPadding | |
| 360 | + if (padding < kMinimumScrollOffsetPadding ) { | |
| 361 | + padding = kMinimumScrollOffsetPadding; | |
| 362 | + } | |
| 363 | + | |
| 364 | + // Ideal offset places the subview rectangle origin "padding" points from the top of the scrollview. | |
| 365 | + // If there is a top contentInset, also compensate for this so that subviewRect will not be placed under | |
| 366 | + // things like navigation bars. | |
| 367 | + offset = subviewRect.origin.y - padding - self.contentInset.top; | |
| 368 | + }; | |
| 369 | + | |
| 370 | + // If possible, center the caret in the visible space. Otherwise, center the entire view in the visible space. | |
| 371 | + if ([view conformsToProtocol:@protocol(UITextInput)]) { | |
| 372 | + UIView <UITextInput> *textInput = (UIView <UITextInput>*)view; | |
| 373 | + UITextPosition *caretPosition = [textInput selectedTextRange].start; | |
| 374 | + if (caretPosition) { | |
| 375 | + CGRect caretRect = [self convertRect:[textInput caretRectForPosition:caretPosition] fromView:textInput]; | |
| 376 | + | |
| 377 | + // Attempt to center the cursor in the visible space | |
| 378 | + // pixels above the view, then substitute kMinimumScrollOffsetPadding | |
| 379 | + padding = (viewAreaHeight - caretRect.size.height) / 2; | |
| 380 | + | |
| 381 | + // But if that means there will be less than kMinimumScrollOffsetPadding | |
| 382 | + // pixels above the view, then substitute kMinimumScrollOffsetPadding | |
| 383 | + if (padding < kMinimumScrollOffsetPadding ) { | |
| 384 | + padding = kMinimumScrollOffsetPadding; | |
| 385 | + } | |
| 386 | + | |
| 387 | + // Ideal offset places the subview rectangle origin "padding" points from the top of the scrollview. | |
| 388 | + // If there is a top contentInset, also compensate for this so that subviewRect will not be placed under | |
| 389 | + // things like navigation bars. | |
| 390 | + offset = caretRect.origin.y - padding - self.contentInset.top; | |
| 391 | + } else { | |
| 392 | + centerViewInViewableArea(); | |
| 393 | + } | |
| 394 | + } else { | |
| 395 | + centerViewInViewableArea(); | |
| 396 | + } | |
| 397 | + | |
| 398 | + // Constrain the new contentOffset so we can't scroll past the bottom. Note that we don't take the bottom | |
| 399 | + // inset into account, as this is manipulated to make space for the keyboard. | |
| 400 | + CGFloat maxOffset = contentSize.height - viewAreaHeight - self.contentInset.top; | |
| 401 | + if (offset > maxOffset) { | |
| 402 | + offset = maxOffset; | |
| 403 | + } | |
| 404 | + | |
| 405 | + // Constrain the new contentOffset so we can't scroll past the top, taking contentInsets into account | |
| 406 | + if ( offset < -self.contentInset.top ) { | |
| 407 | + offset = -self.contentInset.top; | |
| 408 | + } | |
| 409 | + | |
| 410 | + return offset; | |
| 411 | +} | |
| 412 | + | |
| 413 | +- (void)TPKeyboardAvoiding_initializeView:(UIView*)view { | |
| 414 | + if ( [view isKindOfClass:[UITextField class]] | |
| 415 | + && ((UITextField*)view).returnKeyType == UIReturnKeyDefault | |
| 416 | + && (![(UITextField*)view delegate] || [(UITextField*)view delegate] == (id<UITextFieldDelegate>)self) ) { | |
| 417 | + [(UITextField*)view setDelegate:(id<UITextFieldDelegate>)self]; | |
| 418 | + UIView *otherView = [self TPKeyboardAvoiding_findNextInputViewAfterView:view beneathView:self]; | |
| 419 | + | |
| 420 | + if ( otherView ) { | |
| 421 | + ((UITextField*)view).returnKeyType = UIReturnKeyNext; | |
| 422 | + } else { | |
| 423 | + ((UITextField*)view).returnKeyType = UIReturnKeyDone; | |
| 424 | + } | |
| 425 | + } | |
| 426 | +} | |
| 427 | + | |
| 428 | +@end | |
| 429 | + | |
| 430 | + | |
| 431 | +@implementation TPKeyboardAvoidingState | |
| 432 | +@end |