You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
750 lines
32 KiB
750 lines
32 KiB
// |
|
// DAKeyboardControl.m |
|
// DAKeyboardControlExample |
|
// |
|
// Created by Daniel Amitay on 7/14/12. |
|
// Copyright (c) 2012 Daniel Amitay. All rights reserved. |
|
// |
|
|
|
#import "DAKeyboardControl.h" |
|
#import <objc/runtime.h> |
|
|
|
|
|
static inline UIViewAnimationOptions AnimationOptionsForCurve(UIViewAnimationCurve curve) |
|
{ |
|
return curve << 16; |
|
} |
|
|
|
static char UIViewKeyboardTriggerOffset; |
|
static char UIViewKeyboardDidMoveFrameBasedBlock; |
|
static char UIViewKeyboardDidMoveConstraintBasedBlock; |
|
static char UIViewKeyboardActiveInput; |
|
static char UIViewKeyboardActiveView; |
|
static char UIViewKeyboardPanRecognizer; |
|
static char UIViewPreviousKeyboardRect; |
|
static char UIViewIsPanning; |
|
static char UIViewKeyboardOpened; |
|
|
|
@interface UIView (DAKeyboardControl_Internal) <UIGestureRecognizerDelegate> |
|
|
|
@property (nonatomic) DAKeyboardDidMoveBlock frameBasedKeyboardDidMoveBlock; |
|
@property (nonatomic) DAKeyboardDidMoveBlock constraintBasedKeyboardDidMoveBlock; |
|
@property (nonatomic, strong) UIResponder *keyboardActiveInput; |
|
@property (nonatomic, strong) UIView *keyboardActiveView; |
|
@property (nonatomic, strong) UIPanGestureRecognizer *keyboardPanRecognizer; |
|
@property (nonatomic) CGRect previousKeyboardRect; |
|
@property (nonatomic, getter = isPanning) BOOL panning; |
|
@property (nonatomic, getter = isKeyboardOpened) BOOL keyboardOpened; |
|
@end |
|
|
|
@implementation UIView (DAKeyboardControl) |
|
@dynamic keyboardTriggerOffset; |
|
|
|
+ (void)load |
|
{ |
|
// Swizzle the 'addSubview:' method to ensure that all input fields |
|
// have a valid inputAccessoryView upon addition to the view heirarchy |
|
SEL originalSelector = @selector(addSubview:); |
|
SEL swizzledSelector = @selector(swizzled_addSubview:); |
|
Method originalMethod = class_getInstanceMethod(self, originalSelector); |
|
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); |
|
class_addMethod(self, |
|
originalSelector, |
|
class_getMethodImplementation(self, originalSelector), |
|
method_getTypeEncoding(originalMethod)); |
|
class_addMethod(self, |
|
swizzledSelector, |
|
class_getMethodImplementation(self, swizzledSelector), |
|
method_getTypeEncoding(swizzledMethod)); |
|
method_exchangeImplementations(originalMethod, swizzledMethod); |
|
} |
|
|
|
#pragma mark - Public Methods |
|
|
|
- (void)addKeyboardPanningWithActionHandler:(DAKeyboardDidMoveBlock)actionHandler |
|
{ |
|
[self addKeyboardControl:YES frameBasedActionHandler:actionHandler constraintBasedActionHandler:0]; |
|
} |
|
|
|
- (void)addKeyboardPanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveFrameBasesBlock constraintBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveConstraintBasesBlock |
|
{ |
|
[self addKeyboardControl:YES frameBasedActionHandler:didMoveFrameBasesBlock constraintBasedActionHandler:didMoveConstraintBasesBlock]; |
|
} |
|
|
|
- (void)addKeyboardNonpanningWithActionHandler:(DAKeyboardDidMoveBlock)actionHandler |
|
{ |
|
[self addKeyboardControl:NO frameBasedActionHandler:actionHandler constraintBasedActionHandler:0]; |
|
} |
|
|
|
- (void)addKeyboardNonpanningWithFrameBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveFrameBasesBlock |
|
constraintBasedActionHandler:(DAKeyboardDidMoveBlock)didMoveConstraintBasesBlock |
|
{ |
|
[self addKeyboardControl:NO frameBasedActionHandler:didMoveFrameBasesBlock constraintBasedActionHandler:didMoveConstraintBasesBlock]; |
|
} |
|
|
|
- (void)addKeyboardControl:(BOOL)panning frameBasedActionHandler:(DAKeyboardDidMoveBlock)frameBasedActionHandler constraintBasedActionHandler:(DAKeyboardDidMoveBlock)constraintBasedActionHandler |
|
{ |
|
#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_7_0) |
|
if (panning && [self respondsToSelector:@selector(setKeyboardDismissMode:)]) { |
|
[(UIScrollView *)self setKeyboardDismissMode:UIScrollViewKeyboardDismissModeInteractive]; |
|
} else { |
|
self.panning = panning; |
|
} |
|
#else |
|
self.panning = panning; |
|
#endif |
|
self.frameBasedKeyboardDidMoveBlock = frameBasedActionHandler; |
|
self.constraintBasedKeyboardDidMoveBlock = constraintBasedActionHandler; |
|
|
|
// Register for text input notifications |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(responderDidBecomeActive:) |
|
name:UITextFieldTextDidBeginEditingNotification |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(responderDidBecomeActive:) |
|
name:UITextViewTextDidBeginEditingNotification |
|
object:nil]; |
|
|
|
// Register for keyboard notifications |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(inputKeyboardWillShow:) |
|
name:UIKeyboardWillShowNotification |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(inputKeyboardDidShow) |
|
name:UIKeyboardDidShowNotification |
|
object:nil]; |
|
|
|
// For the sake of 4.X compatibility |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(inputKeyboardWillChangeFrame:) |
|
name:@"UIKeyboardWillChangeFrameNotification" |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(inputKeyboardDidChangeFrame) |
|
name:@"UIKeyboardDidChangeFrameNotification" |
|
object:nil]; |
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(inputKeyboardWillHide:) |
|
name:UIKeyboardWillHideNotification |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] addObserver:self |
|
selector:@selector(inputKeyboardDidHide) |
|
name:UIKeyboardDidHideNotification |
|
object:nil]; |
|
} |
|
|
|
- (CGRect)keyboardFrameInView |
|
{ |
|
if (self.keyboardActiveView) |
|
{ |
|
CGRect keyboardFrameInView = [self convertRect:self.keyboardActiveView.frame |
|
fromView:self.keyboardActiveView.superview]; |
|
return keyboardFrameInView; |
|
} |
|
else |
|
{ |
|
CGRect keyboardFrameInView = CGRectMake(0.0f, |
|
[[UIScreen mainScreen] bounds].size.height, |
|
0.0f, |
|
0.0f); |
|
return keyboardFrameInView; |
|
} |
|
} |
|
|
|
- (void)removeKeyboardControl |
|
{ |
|
// Unregister for text input notifications |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:UITextFieldTextDidBeginEditingNotification |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:UITextViewTextDidBeginEditingNotification |
|
object:nil]; |
|
|
|
// Unregister for keyboard notifications |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:UIKeyboardWillShowNotification |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:UIKeyboardDidShowNotification |
|
object:nil]; |
|
|
|
// For the sake of 4.X compatibility |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:@"UIKeyboardWillChangeFrameNotification" |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:@"UIKeyboardDidChangeFrameNotification" |
|
object:nil]; |
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:UIKeyboardWillHideNotification |
|
object:nil]; |
|
[[NSNotificationCenter defaultCenter] removeObserver:self |
|
name:UIKeyboardDidHideNotification |
|
object:nil]; |
|
|
|
// Unregister any gesture recognizer |
|
[self removeGestureRecognizer:self.keyboardPanRecognizer]; |
|
|
|
// Release a few properties |
|
self.frameBasedKeyboardDidMoveBlock = nil; |
|
self.keyboardActiveInput = nil; |
|
self.keyboardActiveView = nil; |
|
self.keyboardPanRecognizer = nil; |
|
} |
|
|
|
- (void)hideKeyboard |
|
{ |
|
if (self.keyboardActiveView) |
|
{ |
|
self.keyboardActiveView.hidden = YES; |
|
self.keyboardActiveView.userInteractionEnabled = NO; |
|
[self.keyboardActiveInput resignFirstResponder]; |
|
} |
|
} |
|
|
|
#pragma mark - Input Notifications |
|
|
|
- (void)responderDidBecomeActive:(NSNotification *)notification |
|
{ |
|
// Grab the active input, it will be used to find the keyboard view later on |
|
self.keyboardActiveInput = notification.object; |
|
if (!self.keyboardActiveInput.inputAccessoryView) |
|
{ |
|
UITextField *textField = (UITextField *)self.keyboardActiveInput; |
|
if ([textField respondsToSelector:@selector(setInputAccessoryView:)]) |
|
{ |
|
UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero]; |
|
nullView.backgroundColor = [UIColor clearColor]; |
|
textField.inputAccessoryView = nullView; |
|
} |
|
self.keyboardActiveInput = (UIResponder *)textField; |
|
// Force the keyboard active view reset |
|
[self inputKeyboardDidShow]; |
|
} |
|
} |
|
|
|
#pragma mark - Keyboard Notifications |
|
|
|
- (void)inputKeyboardWillShow:(NSNotification *)notification |
|
{ |
|
CGRect keyboardEndFrameWindow; |
|
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardEndFrameWindow]; |
|
|
|
double keyboardTransitionDuration; |
|
[[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&keyboardTransitionDuration]; |
|
|
|
UIViewAnimationCurve keyboardTransitionAnimationCurve; |
|
[[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&keyboardTransitionAnimationCurve]; |
|
|
|
self.keyboardActiveView.hidden = NO; |
|
self.keyboardOpened = YES; |
|
|
|
CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:nil]; |
|
|
|
BOOL constraintBasedKeyboardDidMoveBlockCalled = self.constraintBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView); |
|
if (constraintBasedKeyboardDidMoveBlockCalled) |
|
self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, YES, NO); |
|
|
|
[UIView animateWithDuration:keyboardTransitionDuration |
|
delay:0.0f |
|
options:AnimationOptionsForCurve(keyboardTransitionAnimationCurve) | UIViewAnimationOptionBeginFromCurrentState |
|
animations:^{ |
|
if (constraintBasedKeyboardDidMoveBlockCalled) |
|
[self layoutIfNeeded]; |
|
if (self.frameBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView)) |
|
self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, YES, NO); |
|
} |
|
completion:^(__unused BOOL finished){ |
|
if (self.panning && !self.keyboardPanRecognizer) |
|
{ |
|
// Register for gesture recognizer calls |
|
self.keyboardPanRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self |
|
action:@selector(panGestureDidChange:)]; |
|
[self.keyboardPanRecognizer setMinimumNumberOfTouches:1]; |
|
[self.keyboardPanRecognizer setDelegate:self]; |
|
[self.keyboardPanRecognizer setCancelsTouchesInView:NO]; |
|
[self addGestureRecognizer:self.keyboardPanRecognizer]; |
|
} |
|
}]; |
|
} |
|
|
|
- (void)inputKeyboardDidShow |
|
{ |
|
// Grab the keyboard view |
|
self.keyboardActiveView = self.keyboardActiveInput.inputAccessoryView.superview; |
|
self.keyboardActiveView.hidden = NO; |
|
|
|
// If the active keyboard view could not be found (UITextViews...), try again |
|
if (!self.keyboardActiveView) |
|
{ |
|
// Find the first responder on subviews and look re-assign first responder to it |
|
self.keyboardActiveInput = [self recursiveFindFirstResponder:self]; |
|
self.keyboardActiveView = self.keyboardActiveInput.inputAccessoryView.superview; |
|
self.keyboardActiveView.hidden = NO; |
|
} |
|
} |
|
|
|
- (void)inputKeyboardWillChangeFrame:(NSNotification *)notification |
|
{ |
|
CGRect keyboardEndFrameWindow; |
|
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardEndFrameWindow]; |
|
|
|
double keyboardTransitionDuration; |
|
[[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&keyboardTransitionDuration]; |
|
|
|
UIViewAnimationCurve keyboardTransitionAnimationCurve; |
|
[[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&keyboardTransitionAnimationCurve]; |
|
|
|
CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:nil]; |
|
|
|
BOOL constraintBasedKeyboardDidMoveBlockCalled = self.constraintBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView); |
|
if (constraintBasedKeyboardDidMoveBlockCalled) |
|
self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); |
|
|
|
[UIView animateWithDuration:keyboardTransitionDuration |
|
delay:0.0f |
|
options:AnimationOptionsForCurve(keyboardTransitionAnimationCurve) | UIViewAnimationOptionBeginFromCurrentState |
|
animations:^{ |
|
if (constraintBasedKeyboardDidMoveBlockCalled) |
|
[self layoutIfNeeded]; |
|
|
|
if (self.frameBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView)) |
|
self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); |
|
} |
|
completion:nil]; |
|
} |
|
|
|
- (void)inputKeyboardDidChangeFrame |
|
{ |
|
// Nothing to see here |
|
} |
|
|
|
- (void)inputKeyboardWillHide:(NSNotification *)notification |
|
{ |
|
CGRect keyboardEndFrameWindow; |
|
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue: &keyboardEndFrameWindow]; |
|
|
|
double keyboardTransitionDuration; |
|
[[notification.userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&keyboardTransitionDuration]; |
|
|
|
UIViewAnimationCurve keyboardTransitionAnimationCurve; |
|
[[notification.userInfo valueForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&keyboardTransitionAnimationCurve]; |
|
|
|
CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:nil]; |
|
|
|
BOOL constraintBasedKeyboardDidMoveBlockCalled = self.constraintBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView); |
|
if (constraintBasedKeyboardDidMoveBlockCalled) |
|
self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, YES); |
|
|
|
[UIView animateWithDuration:keyboardTransitionDuration |
|
delay:0.0f |
|
options:AnimationOptionsForCurve(keyboardTransitionAnimationCurve) | UIViewAnimationOptionBeginFromCurrentState |
|
animations:^{ |
|
if (constraintBasedKeyboardDidMoveBlockCalled) |
|
[self layoutIfNeeded]; |
|
|
|
if (self.frameBasedKeyboardDidMoveBlock && !CGRectIsNull(keyboardEndFrameView)) |
|
self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, YES); |
|
} |
|
completion:^(__unused BOOL finished){ |
|
// Remove gesture recognizer when keyboard is not showing |
|
[self removeGestureRecognizer:self.keyboardPanRecognizer]; |
|
self.keyboardPanRecognizer = nil; |
|
}]; |
|
} |
|
|
|
- (void)inputKeyboardDidHide |
|
{ |
|
self.keyboardActiveView.hidden = NO; |
|
self.keyboardActiveView.userInteractionEnabled = YES; |
|
self.keyboardActiveView = nil; |
|
self.keyboardActiveInput = nil; |
|
self.keyboardOpened = NO; |
|
} |
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath |
|
ofObject:(id)object |
|
change:(__unused NSDictionary *)change |
|
context:(__unused void *)context |
|
{ |
|
if([keyPath isEqualToString:@"frame"] && object == self.keyboardActiveView) |
|
{ |
|
CGRect keyboardEndFrameWindow = [[object valueForKeyPath:keyPath] CGRectValue]; |
|
CGRect keyboardEndFrameView = [self convertRect:keyboardEndFrameWindow fromView:self.keyboardActiveView.superview]; |
|
|
|
if (CGRectEqualToRect(keyboardEndFrameView, self.previousKeyboardRect)) return; |
|
|
|
if (!self.keyboardActiveView.hidden && !CGRectIsNull(keyboardEndFrameView)) |
|
{ |
|
if (self.frameBasedKeyboardDidMoveBlock) |
|
self.frameBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); |
|
if (self.constraintBasedKeyboardDidMoveBlock) |
|
{ |
|
self.constraintBasedKeyboardDidMoveBlock(keyboardEndFrameView, NO, NO); |
|
[self layoutIfNeeded]; |
|
} |
|
} |
|
|
|
self.previousKeyboardRect = keyboardEndFrameView; |
|
} |
|
} |
|
|
|
#pragma mark - Touches Management |
|
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer |
|
{ |
|
if (gestureRecognizer == self.keyboardPanRecognizer || otherGestureRecognizer == self.keyboardPanRecognizer) |
|
{ |
|
return YES; |
|
} |
|
else |
|
{ |
|
return NO; |
|
} |
|
} |
|
|
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch |
|
{ |
|
if (gestureRecognizer == self.keyboardPanRecognizer) |
|
{ |
|
// Don't allow panning if inside the active input (unless SELF is a UITextView and the receiving view) |
|
return (![touch.view isFirstResponder] || ([self isKindOfClass:[UITextView class]] && [self isEqual:touch.view])); |
|
} |
|
else |
|
{ |
|
return YES; |
|
} |
|
} |
|
|
|
- (void)panGestureDidChange:(UIPanGestureRecognizer *)gesture |
|
{ |
|
if(!self.keyboardActiveView || !self.keyboardActiveInput || self.keyboardActiveView.hidden) |
|
{ |
|
self.keyboardActiveInput = [self recursiveFindFirstResponder:self]; |
|
self.keyboardActiveView = self.keyboardActiveInput.inputAccessoryView.superview; |
|
self.keyboardActiveView.hidden = NO; |
|
} |
|
else |
|
{ |
|
self.keyboardActiveView.hidden = NO; |
|
} |
|
|
|
CGFloat keyboardViewHeight = self.keyboardActiveView.bounds.size.height; |
|
CGFloat keyboardWindowHeight = self.keyboardActiveView.superview.bounds.size.height; |
|
CGPoint touchLocationInKeyboardWindow = [gesture locationInView:self.keyboardActiveView.superview]; |
|
|
|
// If touch is inside trigger offset, then disable keyboard input |
|
if (touchLocationInKeyboardWindow.y > keyboardWindowHeight - keyboardViewHeight - self.keyboardTriggerOffset) |
|
{ |
|
self.keyboardActiveView.userInteractionEnabled = NO; |
|
} |
|
else |
|
{ |
|
self.keyboardActiveView.userInteractionEnabled = YES; |
|
} |
|
|
|
switch (gesture.state) |
|
{ |
|
case UIGestureRecognizerStateBegan: |
|
{ |
|
// For the duration of this gesture, do not recognize more touches than |
|
// it started with |
|
gesture.maximumNumberOfTouches = gesture.numberOfTouches; |
|
} |
|
break; |
|
case UIGestureRecognizerStateChanged: |
|
{ |
|
CGRect newKeyboardViewFrame = self.keyboardActiveView.frame; |
|
newKeyboardViewFrame.origin.y = touchLocationInKeyboardWindow.y + self.keyboardTriggerOffset; |
|
// Bound the keyboard to the bottom of the screen |
|
newKeyboardViewFrame.origin.y = MIN(newKeyboardViewFrame.origin.y, keyboardWindowHeight); |
|
newKeyboardViewFrame.origin.y = MAX(newKeyboardViewFrame.origin.y, keyboardWindowHeight - keyboardViewHeight); |
|
|
|
// Only update if the frame has actually changed |
|
if (newKeyboardViewFrame.origin.y != self.keyboardActiveView.frame.origin.y) |
|
{ |
|
[UIView animateWithDuration:0.0f |
|
delay:0.0f |
|
options:UIViewAnimationOptionTransitionNone | UIViewAnimationOptionBeginFromCurrentState |
|
animations:^{ |
|
[self.keyboardActiveView setFrame:newKeyboardViewFrame]; |
|
/* Unnecessary now, due to KVO on self.keyboardActiveView |
|
CGRect newKeyboardViewFrameInView = [self convertRect:newKeyboardViewFrame |
|
fromView:self.keyboardActiveView.window]; |
|
if (self.frameBasedKeyboardDidMoveBlock) |
|
self.frameBasedKeyboardDidMoveBlock(newKeyboardViewFrameInView); |
|
*/ |
|
} |
|
completion:nil]; |
|
} |
|
} |
|
break; |
|
case UIGestureRecognizerStateEnded: |
|
case UIGestureRecognizerStateCancelled: |
|
{ |
|
CGFloat thresholdHeight = keyboardWindowHeight - keyboardViewHeight - self.keyboardTriggerOffset + 44.0f; |
|
CGPoint velocity = [gesture velocityInView:self.keyboardActiveView]; |
|
BOOL shouldRecede; |
|
|
|
if (touchLocationInKeyboardWindow.y < thresholdHeight || velocity.y < 0) |
|
shouldRecede = NO; |
|
else |
|
shouldRecede = YES; |
|
|
|
// If the keyboard has only been pushed down 44 pixels or has been |
|
// panned upwards let it pop back up; otherwise, let it drop down |
|
CGRect newKeyboardViewFrame = self.keyboardActiveView.frame; |
|
newKeyboardViewFrame.origin.y = (!shouldRecede ? keyboardWindowHeight - keyboardViewHeight : keyboardWindowHeight); |
|
|
|
[UIView animateWithDuration:0.25f |
|
delay:0.0f |
|
options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState |
|
animations:^{ |
|
[self.keyboardActiveView setFrame:newKeyboardViewFrame]; |
|
/* Unnecessary now, due to KVO on self.keyboardActiveView |
|
CGRect newKeyboardViewFrameInView = [self convertRect:newKeyboardViewFrame |
|
fromView:self.keyboardActiveView.window]; |
|
if (self.frameBasedKeyboardDidMoveBlock) |
|
self.frameBasedKeyboardDidMoveBlock(newKeyboardViewFrameInView); |
|
*/ |
|
} |
|
completion:^(__unused BOOL finished){ |
|
[[self keyboardActiveView] setUserInteractionEnabled:!shouldRecede]; |
|
if (shouldRecede) |
|
{ |
|
[self hideKeyboard]; |
|
} |
|
}]; |
|
|
|
// Set the max number of touches back to the default |
|
gesture.maximumNumberOfTouches = NSUIntegerMax; |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
#pragma mark - Internal Methods |
|
|
|
- (UIView *)recursiveFindFirstResponder:(UIView *)view |
|
{ |
|
if ([view isFirstResponder]) |
|
{ |
|
return view; |
|
} |
|
UIView *found = nil; |
|
for (UIView *v in view.subviews) |
|
{ |
|
found = [self recursiveFindFirstResponder:v]; |
|
if (found) |
|
{ |
|
break; |
|
} |
|
} |
|
return found; |
|
} |
|
|
|
- (void)swizzled_addSubview:(UIView *)subview |
|
{ |
|
if (!subview.inputAccessoryView) |
|
{ |
|
if ([subview isKindOfClass:[UITextField class]]) |
|
{ |
|
UITextField *textField = (UITextField *)subview; |
|
if ([textField respondsToSelector:@selector(setInputAccessoryView:)]) |
|
{ |
|
UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero]; |
|
nullView.backgroundColor = [UIColor clearColor]; |
|
textField.inputAccessoryView = nullView; |
|
} |
|
} |
|
else if ([subview isKindOfClass:[UITextView class]]) { |
|
UITextView *textView = (UITextView *)subview; |
|
if ([textView respondsToSelector:@selector(setInputAccessoryView:)] && [textView respondsToSelector:@selector(isEditable)] && textView.isEditable) |
|
{ |
|
UIView *nullView = [[UIView alloc] initWithFrame:CGRectZero]; |
|
nullView.backgroundColor = [UIColor clearColor]; |
|
textView.inputAccessoryView = nullView; |
|
} |
|
} |
|
} |
|
[self swizzled_addSubview:subview]; |
|
} |
|
|
|
#pragma mark - Property Methods |
|
|
|
-(CGRect)previousKeyboardRect { |
|
id previousRectValue = objc_getAssociatedObject(self, &UIViewPreviousKeyboardRect); |
|
if (previousRectValue) |
|
return [previousRectValue CGRectValue]; |
|
|
|
return CGRectZero; |
|
} |
|
|
|
-(void)setPreviousKeyboardRect:(CGRect)previousKeyboardRect { |
|
[self willChangeValueForKey:@"previousKeyboardRect"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewPreviousKeyboardRect, |
|
[NSValue valueWithCGRect:previousKeyboardRect], |
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
|
[self didChangeValueForKey:@"previousKeyboardRect"]; |
|
} |
|
|
|
- (DAKeyboardDidMoveBlock)frameBasedKeyboardDidMoveBlock |
|
{ |
|
return objc_getAssociatedObject(self, |
|
&UIViewKeyboardDidMoveFrameBasedBlock); |
|
} |
|
|
|
- (void)setFrameBasedKeyboardDidMoveBlock:(DAKeyboardDidMoveBlock)frameBasedKeyboardDidMoveBlock |
|
{ |
|
[self willChangeValueForKey:@"frameBasedKeyboardDidMoveBlock"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewKeyboardDidMoveFrameBasedBlock, |
|
frameBasedKeyboardDidMoveBlock, |
|
OBJC_ASSOCIATION_COPY); |
|
[self didChangeValueForKey:@"frameBasedKeyboardDidMoveBlock"]; |
|
} |
|
|
|
- (DAKeyboardDidMoveBlock)constraintBasedKeyboardDidMoveBlock |
|
{ |
|
return objc_getAssociatedObject(self, |
|
&UIViewKeyboardDidMoveConstraintBasedBlock); |
|
} |
|
|
|
- (void)setConstraintBasedKeyboardDidMoveBlock:(DAKeyboardDidMoveBlock)constraintBasedKeyboardDidMoveBlock |
|
{ |
|
[self willChangeValueForKey:@"constraintBasedKeyboardDidMoveBlock"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewKeyboardDidMoveConstraintBasedBlock, |
|
constraintBasedKeyboardDidMoveBlock, |
|
OBJC_ASSOCIATION_COPY); |
|
[self didChangeValueForKey:@"constraintBasedKeyboardDidMoveBlock"]; |
|
} |
|
|
|
- (CGFloat)keyboardTriggerOffset |
|
{ |
|
NSNumber *keyboardTriggerOffsetNumber = objc_getAssociatedObject(self, |
|
&UIViewKeyboardTriggerOffset); |
|
return [keyboardTriggerOffsetNumber floatValue]; |
|
} |
|
|
|
- (void)setKeyboardTriggerOffset:(CGFloat)keyboardTriggerOffset |
|
{ |
|
[self willChangeValueForKey:@"keyboardTriggerOffset"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewKeyboardTriggerOffset, |
|
[NSNumber numberWithFloat:keyboardTriggerOffset], |
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
|
[self didChangeValueForKey:@"keyboardTriggerOffset"]; |
|
} |
|
|
|
- (BOOL)isPanning |
|
{ |
|
NSNumber *isPanningNumber = objc_getAssociatedObject(self, |
|
&UIViewIsPanning); |
|
return [isPanningNumber boolValue]; |
|
} |
|
|
|
- (void)setPanning:(BOOL)panning |
|
{ |
|
[self willChangeValueForKey:@"panning"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewIsPanning, |
|
@(panning), |
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
|
[self didChangeValueForKey:@"panning"]; |
|
} |
|
|
|
- (UIResponder *)keyboardActiveInput |
|
{ |
|
return objc_getAssociatedObject(self, |
|
&UIViewKeyboardActiveInput); |
|
} |
|
|
|
- (void)setKeyboardActiveInput:(UIResponder *)keyboardActiveInput |
|
{ |
|
[self willChangeValueForKey:@"keyboardActiveInput"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewKeyboardActiveInput, |
|
keyboardActiveInput, |
|
OBJC_ASSOCIATION_RETAIN); |
|
[self didChangeValueForKey:@"keyboardActiveInput"]; |
|
} |
|
|
|
- (UIView *)keyboardActiveView |
|
{ |
|
return objc_getAssociatedObject(self, |
|
&UIViewKeyboardActiveView); |
|
} |
|
|
|
- (void)setKeyboardActiveView:(UIView *)keyboardActiveView |
|
{ |
|
[self willChangeValueForKey:@"keyboardActiveView"]; |
|
[self.keyboardActiveView removeObserver:self |
|
forKeyPath:@"frame"]; |
|
if (keyboardActiveView) |
|
{ |
|
[keyboardActiveView addObserver:self |
|
forKeyPath:@"frame" |
|
options:0 |
|
context:NULL]; |
|
} |
|
objc_setAssociatedObject(self, |
|
&UIViewKeyboardActiveView, |
|
keyboardActiveView, |
|
OBJC_ASSOCIATION_RETAIN); |
|
[self didChangeValueForKey:@"keyboardActiveView"]; |
|
} |
|
|
|
- (UIPanGestureRecognizer *)keyboardPanRecognizer |
|
{ |
|
return objc_getAssociatedObject(self, |
|
&UIViewKeyboardPanRecognizer); |
|
} |
|
|
|
- (void)setKeyboardPanRecognizer:(UIPanGestureRecognizer *)keyboardPanRecognizer |
|
{ |
|
[self willChangeValueForKey:@"keyboardPanRecognizer"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewKeyboardPanRecognizer, |
|
keyboardPanRecognizer, |
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
|
[self didChangeValueForKey:@"keyboardPanRecognizer"]; |
|
} |
|
|
|
- (BOOL)isKeyboardOpened |
|
{ |
|
return [objc_getAssociatedObject(self, |
|
&UIViewKeyboardOpened) boolValue]; |
|
} |
|
|
|
- (void)setKeyboardOpened:(BOOL)keyboardOpened |
|
{ |
|
[self willChangeValueForKey:@"keyboardOpened"]; |
|
objc_setAssociatedObject(self, |
|
&UIViewKeyboardOpened, |
|
@(keyboardOpened), |
|
OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
|
[self didChangeValueForKey:@"keyboardOpened"]; |
|
} |
|
|
|
- (BOOL)keyboardWillRecede |
|
{ |
|
CGFloat keyboardViewHeight = self.keyboardActiveView.bounds.size.height; |
|
CGFloat keyboardWindowHeight = self.keyboardActiveView.superview.bounds.size.height; |
|
CGPoint touchLocationInKeyboardWindow = [self.keyboardPanRecognizer locationInView:self.keyboardActiveView.superview]; |
|
|
|
CGFloat thresholdHeight = keyboardWindowHeight - keyboardViewHeight - self.keyboardTriggerOffset + 44.0f; |
|
CGPoint velocity = [self.keyboardPanRecognizer velocityInView:self.keyboardActiveView]; |
|
|
|
return touchLocationInKeyboardWindow.y >= thresholdHeight && velocity.y >= 0; |
|
} |
|
|
|
@end |