Commit 7597b958ff8048902c85e111ff941a72265a1432
1 parent
f35b3f9a70
Exists in
master
and in
1 other branch
fix bug baseviewcontroller
Showing 16 changed files with 822 additions and 822 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/TPKeyboardAvoidingCollectionView.h
- LifeLog/LifeLog/TPKeyboardAvoidingCollectionView.m
- LifeLog/LifeLog/TPKeyboardAvoidingScrollView.h
- LifeLog/LifeLog/TPKeyboardAvoidingScrollView.m
- LifeLog/LifeLog/TPKeyboardAvoidingTableView.h
- LifeLog/LifeLog/TPKeyboardAvoidingTableView.m
- LifeLog/LifeLog/UIScrollView+TPKeyboardAvoidingAdditions.h
- LifeLog/LifeLog/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 |
LifeLog/LifeLog/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/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/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/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/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/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/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/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 |