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 | + | |