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
1 year ago
|
//
|
||
|
// 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
|