Commit f35b3f9a70a903bfec0345c8e625e2e11dc77a2d

Authored by phong
1 parent 256868a43c

fix bug baseviewcontroller

Showing 8 changed files with 822 additions and 0 deletions Side-by-side Diff

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