From 24afcbde10cfa567afc4b7b7212ac4c2921eb6ab Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Mon, 11 Jan 2016 19:51:44 -0800 Subject: [PATCH 01/15] Implements an alternative technique for dragging the keyboard down and up on iOS 9, without using any private APIs --- Source/SLKInputAccessoryView.m | 5 +--- Source/SLKTextInputbar.h | 9 ++++++ Source/SLKTextInputbar.m | 51 ++++++++++++++++++++++++++++++++++ Source/SLKTextViewController.h | 3 -- Source/SLKTextViewController.m | 15 ++++++++++ 5 files changed, 76 insertions(+), 7 deletions(-) diff --git a/Source/SLKInputAccessoryView.m b/Source/SLKInputAccessoryView.m index 3546e5b2..baba6d53 100644 --- a/Source/SLKInputAccessoryView.m +++ b/Source/SLKInputAccessoryView.m @@ -20,14 +20,11 @@ @implementation SLKInputAccessoryView - #pragma mark - Super Overrides - (void)willMoveToSuperview:(UIView *)newSuperview { - if (!SLK_IS_IOS9_AND_HIGHER) { - _keyboardViewProxy = newSuperview; - } + _keyboardViewProxy = newSuperview; } @end \ No newline at end of file diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index 1e27ac94..433f391d 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -123,6 +123,15 @@ typedef NS_ENUM(NSUInteger, SLKCounterPosition) { - (void)endTextEdition; +/** + YES if a keyboard screenshot should be shown, replacing the keyboard area. + Since the keyboard is on its own view hierarchy since iOS 9, this is an easy technique to achieve the effect for dragging keyboard being moved together with the text input bar. + + @param show YES if a keyboard screenshot should be show. + */ +- (void)showKeyboardMockup:(BOOL)show; + + #pragma mark - Text Counting ///------------------------------------------------ /// @name Text Counting diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 479296e3..777497cd 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -39,6 +39,7 @@ @interface SLKTextInputbar () @property (nonatomic, strong) NSArray *charCountLabelVCs; @property (nonatomic, strong) UILabel *charCountLabel; +@property (nonatomic, strong) UIView *keyboardMockView; @property (nonatomic) CGPoint previousOrigin; @@ -538,6 +539,56 @@ - (void)slk_updateCounter } +#pragma mark - Keyboard Mockup + +- (void)showKeyboardMockup:(BOOL)show +{ + UIWindow *keyboardWindow = [self keyboardWindow]; + + if (!_keyboardMockView && show) { + + // Takes a snapshot of the keyboard's window + UIView *keyboardSnapshot = [keyboardWindow snapshotViewAfterScreenUpdates:NO]; + + // Shifts the snapshot up to fit to the bottom + CGRect snapshowFrame = keyboardSnapshot.frame; + snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(self.controller.view.frame); + keyboardSnapshot.frame = snapshowFrame; + + CGRect mockframe = self.inputAccessoryView.keyboardViewProxy.frame; + mockframe.origin.y = CGRectGetHeight(self.frame); + + _keyboardMockView = [[UIView alloc] initWithFrame:mockframe]; + _keyboardMockView.backgroundColor = [UIColor clearColor]; + [_keyboardMockView addSubview:keyboardSnapshot]; + + // Adds the mock view to the input bar, so when it moves they are glued together + [self addSubview:_keyboardMockView]; + + // let's delay hidding the keyboard window to avoid noticeable glitches + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + keyboardWindow.hidden = YES; + }); + } + else if (_keyboardMockView && !show) { + + [_keyboardMockView removeFromSuperview]; + _keyboardMockView = nil; + + keyboardWindow.hidden = NO; + } +} + +- (UIWindow *)keyboardWindow +{ + NSArray *array = [[UIApplication sharedApplication] windows]; + + // NOTE: This is risky, since the order may change in the future. + // But it is the only way of looking up for the keyboard window without using private APIs. + return [array lastObject]; +} + + #pragma mark - Notification Events - (void)slk_didChangeTextViewText:(NSNotification *)notification diff --git a/Source/SLKTextViewController.h b/Source/SLKTextViewController.h index 08ec917c..f40d0023 100644 --- a/Source/SLKTextViewController.h +++ b/Source/SLKTextViewController.h @@ -91,9 +91,6 @@ NS_CLASS_AVAILABLE_IOS(7_0) @interface SLKTextViewController : UIViewController /** YES if keyboard can be dismissed gradually with a vertical panning gesture. Default is YES. - - This feature doesn't work on iOS 9 due to no legit alternatives to detect the keyboard view. - Open Radar: http://openradar.appspot.com/radar?id=5021485877952512 */ @property (nonatomic, assign, getter = isKeyboardPanningEnabled) BOOL keyboardPanningEnabled; diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index fb67ac05..5b68e319 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -989,6 +989,13 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture keyboardView.frame = originalFrame; } + // Because the keyboard is on its own view hierarchy since iOS 9, + // we instead show a snapshot of the keyboard and hide it + // to give the illusion that the keyboard is being moved by the user. + if (SLK_IS_IOS9_AND_HIGHER) { + [self.textInputbar showKeyboardMockup:YES]; + } + break; } case UIGestureRecognizerStateChanged: { @@ -1055,6 +1062,10 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture case UIGestureRecognizerStateFailed: { if (!dragging) { + if (SLK_IS_IOS9_AND_HIGHER) { + [self.textInputbar showKeyboardMockup:NO]; + } + break; } @@ -1096,6 +1107,10 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture presenting = NO; self.movingKeyboard = NO; + + if (SLK_IS_IOS9_AND_HIGHER) { + [self.textInputbar showKeyboardMockup:NO]; + } }]; break; From 120fa9425e0477bc8f974f8606b25707de7a994c Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Wed, 13 Jan 2016 15:05:59 -0800 Subject: [PATCH 02/15] Removes experimental code --- Source/SLKTextInputbar.m | 2 +- Source/SLKTextViewController.m | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 777497cd..2675e38c 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -552,7 +552,7 @@ - (void)showKeyboardMockup:(BOOL)show // Shifts the snapshot up to fit to the bottom CGRect snapshowFrame = keyboardSnapshot.frame; - snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(self.controller.view.frame); + snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(self.superview.frame); keyboardSnapshot.frame = snapshowFrame; CGRect mockframe = self.inputAccessoryView.keyboardViewProxy.frame; diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 5b68e319..4b650735 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -19,9 +19,6 @@ #import "UIResponder+SLKAdditions.h" -/** Feature flagged while waiting to implement a more reliable technique. */ -#define SLKBottomPanningEnabled 0 - #define kSLKAlertViewClearTextTag [NSStringFromClass([SLKTextViewController class]) hash] NSString * const SLKKeyboardWillShowNotification = @"SLKKeyboardWillShowNotification"; @@ -949,23 +946,10 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture // Checking the keyboard height constant helps to disable the view constraints update on iPad when the keyboard is undocked. // Checking the keyboard status allows to keep the inputAccessoryView valid when still reacing the bottom of the screen. if (![self.textView isFirstResponder] || (self.keyboardHC.constant == 0 && self.keyboardStatus == SLKKeyboardStatusDidHide)) { -#if SLKBottomPanningEnabled - if ([gesture.view isEqual:self.scrollViewProxy]) { - if (gestureVelocity.y > 0) { - return; - } - else if ((self.isInverted && ![self.scrollViewProxy slk_isAtTop]) || (!self.isInverted && ![self.scrollViewProxy slk_isAtBottom])) { - return; - } - } - - presenting = YES; -#else if ([gesture.view isEqual:_textInputbar] && gestureVelocity.y < 0) { [self presentKeyboard:YES]; } return; -#endif } switch (gesture.state) { From c20293ed4696f222f3eadb9883091edccd9dc6ff Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Wed, 13 Jan 2016 15:38:29 -0800 Subject: [PATCH 03/15] Adds a new swipe gesture to bring the keyboard up --- Source/SLKTextViewController.h | 6 +++- Source/SLKTextViewController.m | 53 ++++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/Source/SLKTextViewController.h b/Source/SLKTextViewController.h index f40d0023..2c87aaac 100644 --- a/Source/SLKTextViewController.h +++ b/Source/SLKTextViewController.h @@ -80,9 +80,12 @@ NS_CLASS_AVAILABLE_IOS(7_0) @interface SLKTextViewController : UIViewController /** A single tap gesture used to dismiss the keyboard. SLKTextViewController is its delegate. */ @property (nonatomic, readonly) UIGestureRecognizer *singleTapGesture; -/** A vertical pan gesture used for bringing the keyboard from the bottom. SLKTextViewController is its delegate. */ +/** A vertical pan gesture used for moving the keyboard up and bottom. SLKTextViewController is its delegate. */ @property (nonatomic, readonly) UIPanGestureRecognizer *verticalPanGesture; +/** A vertical swipe gesture used for bringing the keyboard from the bottom. SLKTextViewController is its delegate. */ +@property (nonatomic, readonly) UISwipeGestureRecognizer *verticalSwipeGesture; + /** YES if animations should have bouncy effects. Default is YES. */ @property (nonatomic, assign) BOOL bounces; @@ -555,6 +558,7 @@ NS_CLASS_AVAILABLE_IOS(7_0) @interface SLKTextViewController : UIViewController /** UIGestureRecognizerDelegate */ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer NS_REQUIRES_SUPER; +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_REQUIRES_SUPER; /** UIAlertViewDelegate */ #ifndef __IPHONE_8_0 diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 4b650735..1f93ae63 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -320,8 +320,12 @@ - (SLKTextInputbar *)textInputbar _verticalPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(slk_didPanTextInputBar:)]; _verticalPanGesture.delegate = self; - [_textInputbar addGestureRecognizer:self.verticalPanGesture]; + + _verticalSwipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(slk_didSwipeTextInputBar:)]; + _verticalSwipeGesture.direction = UISwipeGestureRecognizerDirectionUp; + _verticalSwipeGesture.delegate = self; + [_textInputbar addGestureRecognizer:self.verticalSwipeGesture]; } return _textInputbar; } @@ -906,14 +910,17 @@ - (void)setTextInputbarHidden:(BOOL)hidden animated:(BOOL)animated #pragma mark - Private Methods -- (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture +- (void)slk_didSwipeTextInputBar:(UISwipeGestureRecognizer *)gesture { - // Textinput dragging isn't supported when - if (!self.view.window || !self.keyboardPanningEnabled || - [self ignoreTextInputbarAdjustment] || self.isPresentedInPopover) { - return; + if (gesture.state == UIGestureRecognizerStateEnded) { + if ([gesture.view isEqual:self.textInputbar]) { + [self presentKeyboard:YES]; + } } - +} + +- (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture +{ dispatch_async(dispatch_get_main_queue(), ^{ [self slk_handlePanGestureRecognizer:gesture]; }); @@ -2134,10 +2141,38 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gesture { if ([gesture isEqual:self.singleTapGesture]) { - return [self.textView isFirstResponder] && ![self ignoreTextInputbarAdjustment]; + // Tap to dismiss isn't supported when + if (![self.textView isFirstResponder] && [self ignoreTextInputbarAdjustment]) { + return NO; + } + + return YES; + } + else if ([gesture isEqual:self.verticalSwipeGesture]) { + // TextInput swipping isn't supported when + if (!self.view.window || [self.textView isFirstResponder] || [self ignoreTextInputbarAdjustment] || self.isPresentedInPopover) { + return NO; + } + + return YES; } else if ([gesture isEqual:self.verticalPanGesture]) { - return self.keyboardPanningEnabled && ![self ignoreTextInputbarAdjustment]; + // TextInput dragging isn't supported when + if (!self.view.window || !self.keyboardPanningEnabled || [self ignoreTextInputbarAdjustment] || self.isPresentedInPopover) { + return NO; + } + + return YES; + } + + return NO; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer +{ + if ([otherGestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]] && + [gestureRecognizer isEqual:self.verticalPanGesture] && [otherGestureRecognizer isEqual:self.verticalSwipeGesture]) { + return YES; } return NO; From bb587aada29999ea3b96ae6ac3cd4c6fba3e7820 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Wed, 13 Jan 2016 15:48:38 -0800 Subject: [PATCH 04/15] Fixes issue causing the keyboard to be hidden without replacing it with the mockup. Reproducible by scrolling up very quickly, while the keyboard is visible. This is a temporary fix, since it comes with a side effect too. --- Source/SLKTextViewController.m | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 1f93ae63..3dd339c9 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -948,42 +948,16 @@ - (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture CGFloat keyboardMaxY = CGRectGetHeight(SLKKeyWindowBounds()); CGFloat keyboardMinY = keyboardMaxY - CGRectGetHeight(keyboardView.frame); - - // Skips this if it's not the expected textView. - // Checking the keyboard height constant helps to disable the view constraints update on iPad when the keyboard is undocked. - // Checking the keyboard status allows to keep the inputAccessoryView valid when still reacing the bottom of the screen. - if (![self.textView isFirstResponder] || (self.keyboardHC.constant == 0 && self.keyboardStatus == SLKKeyboardStatusDidHide)) { - if ([gesture.view isEqual:_textInputbar] && gestureVelocity.y < 0) { - [self presentKeyboard:YES]; - } - return; - } - switch (gesture.state) { case UIGestureRecognizerStateBegan: { startPoint = CGPointZero; dragging = NO; - if (presenting) { - // Let's first present the keyboard without animation - [self presentKeyboard:NO]; - - // So we can capture the keyboard's view - keyboardView = [_textInputbar.inputAccessoryView keyboardViewProxy]; - - originalFrame = keyboardView.frame; - originalFrame.origin.y = CGRectGetMaxY(self.view.frame); - - // And move the keyboard to the bottom edge - // TODO: Fix an occasional layout glitch when the keyboard appears for the first time. - keyboardView.frame = originalFrame; - } - // Because the keyboard is on its own view hierarchy since iOS 9, // we instead show a snapshot of the keyboard and hide it // to give the illusion that the keyboard is being moved by the user. - if (SLK_IS_IOS9_AND_HIGHER) { + if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { [self.textInputbar showKeyboardMockup:YES]; } From a0da519deb93bb914728ad4f4192845c0a98f784 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Sun, 17 Jan 2016 21:00:13 -0800 Subject: [PATCH 05/15] Reduces the gesture recogniser methods count --- Source/SLKTextViewController.m | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 3dd339c9..90ad493a 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -913,7 +913,7 @@ - (void)setTextInputbarHidden:(BOOL)hidden animated:(BOOL)animated - (void)slk_didSwipeTextInputBar:(UISwipeGestureRecognizer *)gesture { if (gesture.state == UIGestureRecognizerStateEnded) { - if ([gesture.view isEqual:self.textInputbar]) { + if (!self.isPresentedInPopover && ![self ignoreTextInputbarAdjustment]) { [self presentKeyboard:YES]; } } @@ -921,14 +921,6 @@ - (void)slk_didSwipeTextInputBar:(UISwipeGestureRecognizer *)gesture - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture { - dispatch_async(dispatch_get_main_queue(), ^{ - [self slk_handlePanGestureRecognizer:gesture]; - }); -} - -- (void)slk_handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture -{ - // Local variables static CGPoint startPoint; static CGRect originalFrame; static BOOL dragging = NO; @@ -1093,11 +1085,6 @@ - (void)slk_didTapScrollView:(UIGestureRecognizer *)gesture } } -- (void)slk_didPanTextView:(UIGestureRecognizer *)gesture -{ - [self presentKeyboard:YES]; -} - - (void)slk_performRightAction { NSArray *actions = [self.rightButton actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]; From 31a4b6a606fbf8a63e33e46bedcbd947f2dbd09b Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Sun, 17 Jan 2016 21:01:17 -0800 Subject: [PATCH 06/15] Lower delay, just enough to avoid a glitch when hiding the keyboard's window --- Source/SLKTextInputbar.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 2675e38c..d08c6a35 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -565,8 +565,8 @@ - (void)showKeyboardMockup:(BOOL)show // Adds the mock view to the input bar, so when it moves they are glued together [self addSubview:_keyboardMockView]; - // let's delay hidding the keyboard window to avoid noticeable glitches - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + // Let's delay hiding the keyboard's window to avoid noticeable glitches + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ keyboardWindow.hidden = YES; }); } From 63e032ec92a032a2399ee57c9d464046d1bec245 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Sun, 17 Jan 2016 21:08:58 -0800 Subject: [PATCH 07/15] Cleans up the panning gesture implementation and makes sure to do the keyboard snapshot/swap whenever the finger touches the input bar (much more reliable) --- Source/SLKTextViewController.m | 36 ++++++++++------------------------ 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 90ad493a..3f4f7296 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -924,7 +924,6 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture static CGPoint startPoint; static CGRect originalFrame; static BOOL dragging = NO; - static BOOL presenting = NO; __block UIView *keyboardView = [_textInputbar.inputAccessoryView keyboardViewProxy]; @@ -946,26 +945,23 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture startPoint = CGPointZero; dragging = NO; - // Because the keyboard is on its own view hierarchy since iOS 9, - // we instead show a snapshot of the keyboard and hide it - // to give the illusion that the keyboard is being moved by the user. - if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { - [self.textInputbar showKeyboardMockup:YES]; - } - break; } case UIGestureRecognizerStateChanged: { - if (CGRectContainsPoint(_textInputbar.frame, gestureLocation) || dragging || presenting){ - + if (CGRectContainsPoint(_textInputbar.frame, gestureLocation) || dragging){ if (CGPointEqualToPoint(startPoint, CGPointZero)) { startPoint = gestureLocation; dragging = YES; - if (!presenting) { - originalFrame = keyboardView.frame; + // Because the keyboard is on its own view hierarchy since iOS 9, + // we instead show a snapshot of the keyboard and hide it + // to give the illusion that the keyboard is being moved by the user. + if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { + [self.textInputbar showKeyboardMockup:YES]; } + + originalFrame = keyboardView.frame; } self.movingKeyboard = YES; @@ -973,13 +969,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture CGPoint transition = CGPointMake(gestureLocation.x - startPoint.x, gestureLocation.y - startPoint.y); CGRect keyboardFrame = originalFrame; - - if (presenting) { - keyboardFrame.origin.y += transition.y; - } - else { - keyboardFrame.origin.y += MAX(transition.y, 0.0); - } + keyboardFrame.origin.y += MAX(transition.y, 0.0); // Makes sure they keyboard is always anchored to the bottom if (CGRectGetMinY(keyboardFrame) < keyboardMinY) { @@ -1027,18 +1017,13 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture } CGPoint transition = CGPointMake(0.0, fabs(gestureLocation.y - startPoint.y)); - CGRect keyboardFrame = originalFrame; - if (presenting) { - keyboardFrame.origin.y = keyboardMinY; - } - // The velocity can be changed to hide or show the keyboard based on the gesture CGFloat minVelocity = 20.0; CGFloat minDistance = CGRectGetHeight(keyboardFrame)/2.0; - BOOL hide = (gestureVelocity.y > minVelocity) || (presenting && transition.y < minDistance) || (!presenting && transition.y > minDistance); + BOOL hide = (gestureVelocity.y > minVelocity) || (transition.y > minDistance); if (hide) keyboardFrame.origin.y = keyboardMaxY; @@ -1061,7 +1046,6 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture startPoint = CGPointZero; originalFrame = CGRectZero; dragging = NO; - presenting = NO; self.movingKeyboard = NO; From e7d003a09da4d2bb85b7654f3f3e4c6ffe4d7de6 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Mon, 18 Jan 2016 10:34:31 -0800 Subject: [PATCH 08/15] Renames method for replacing the keyboard with snapshot --- Source/SLKTextInputbar.h | 9 ++++----- Source/SLKTextInputbar.m | 4 ++-- Source/SLKTextViewController.m | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index 433f391d..38e9e9bc 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -122,14 +122,13 @@ typedef NS_ENUM(NSUInteger, SLKCounterPosition) { */ - (void)endTextEdition; - /** - YES if a keyboard screenshot should be shown, replacing the keyboard area. - Since the keyboard is on its own view hierarchy since iOS 9, this is an easy technique to achieve the effect for dragging keyboard being moved together with the text input bar. + YES if a keyboard snapshot should be shown, replacing the system keyboard. + The snapshot is being added as a subview, aligned at the same position the keyboard is, before hiding it momentarily. - @param show YES if a keyboard screenshot should be show. + @param show YES if a keyboard snapshot should be show and the system keyboard hidden. */ -- (void)showKeyboardMockup:(BOOL)show; +- (void)showKeyboardSnapshot:(BOOL)show; #pragma mark - Text Counting diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index d08c6a35..410fcba3 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -539,9 +539,9 @@ - (void)slk_updateCounter } -#pragma mark - Keyboard Mockup +#pragma mark - Keyboard Snapshot -- (void)showKeyboardMockup:(BOOL)show +- (void)showKeyboardSnapshot:(BOOL)show { UIWindow *keyboardWindow = [self keyboardWindow]; diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 3f4f7296..c8b3df48 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -958,7 +958,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture // we instead show a snapshot of the keyboard and hide it // to give the illusion that the keyboard is being moved by the user. if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { - [self.textInputbar showKeyboardMockup:YES]; + [self.textInputbar showKeyboardSnapshot:YES]; } originalFrame = keyboardView.frame; @@ -1010,7 +1010,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture if (!dragging) { if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardMockup:NO]; + [self.textInputbar showKeyboardSnapshot:NO]; } break; @@ -1050,7 +1050,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture self.movingKeyboard = NO; if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardMockup:NO]; + [self.textInputbar showKeyboardSnapshot:NO]; } }]; From f3f31a7400d2e4fb1f24000c2fce88ba2617a824 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Tue, 16 Feb 2016 15:45:41 -0800 Subject: [PATCH 09/15] Renaming of APIs --- Source/SLKTextInputbar.h | 6 +++--- Source/SLKTextInputbar.m | 30 +++++++++++++++--------------- Source/SLKTextViewController.m | 6 +++--- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index 38e9e9bc..a738c10f 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -123,12 +123,12 @@ typedef NS_ENUM(NSUInteger, SLKCounterPosition) { - (void)endTextEdition; /** - YES if a keyboard snapshot should be shown, replacing the system keyboard. + YES if a keyboard snapshot placeholder should be shown, replacing the system keyboard. The snapshot is being added as a subview, aligned at the same position the keyboard is, before hiding it momentarily. - @param show YES if a keyboard snapshot should be show and the system keyboard hidden. + @param show YES if a keyboard snapshot placeholder should show instead of the system keyboard. */ -- (void)showKeyboardSnapshot:(BOOL)show; +- (void)showKeyboardPlaceholder:(BOOL)show; #pragma mark - Text Counting diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 410fcba3..046295ac 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -39,7 +39,7 @@ @interface SLKTextInputbar () @property (nonatomic, strong) NSArray *charCountLabelVCs; @property (nonatomic, strong) UILabel *charCountLabel; -@property (nonatomic, strong) UIView *keyboardMockView; +@property (nonatomic, strong) UIView *keyboardPlaceholderView; @property (nonatomic) CGPoint previousOrigin; @@ -539,41 +539,41 @@ - (void)slk_updateCounter } -#pragma mark - Keyboard Snapshot +#pragma mark - Keyboard Snapshot Placeholder -- (void)showKeyboardSnapshot:(BOOL)show +- (void)showKeyboardPlaceholder:(BOOL)show { UIWindow *keyboardWindow = [self keyboardWindow]; - if (!_keyboardMockView && show) { + if (!_keyboardPlaceholderView && show) { // Takes a snapshot of the keyboard's window - UIView *keyboardSnapshot = [keyboardWindow snapshotViewAfterScreenUpdates:NO]; + UIView *snapshotView = [keyboardWindow snapshotViewAfterScreenUpdates:NO]; // Shifts the snapshot up to fit to the bottom - CGRect snapshowFrame = keyboardSnapshot.frame; + CGRect snapshowFrame = snapshotView.frame; snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(self.superview.frame); - keyboardSnapshot.frame = snapshowFrame; + snapshotView.frame = snapshowFrame; CGRect mockframe = self.inputAccessoryView.keyboardViewProxy.frame; mockframe.origin.y = CGRectGetHeight(self.frame); - _keyboardMockView = [[UIView alloc] initWithFrame:mockframe]; - _keyboardMockView.backgroundColor = [UIColor clearColor]; - [_keyboardMockView addSubview:keyboardSnapshot]; + self.keyboardPlaceholderView = [[UIView alloc] initWithFrame:mockframe]; + self.keyboardPlaceholderView.backgroundColor = [UIColor clearColor]; + [self.keyboardPlaceholderView addSubview:snapshotView]; - // Adds the mock view to the input bar, so when it moves they are glued together - [self addSubview:_keyboardMockView]; + // Adds the placeholder view to the input bar, so when it looks they are sticked together. + [self addSubview:self.keyboardPlaceholderView]; // Let's delay hiding the keyboard's window to avoid noticeable glitches dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ keyboardWindow.hidden = YES; }); } - else if (_keyboardMockView && !show) { + else if (_keyboardPlaceholderView && !show) { - [_keyboardMockView removeFromSuperview]; - _keyboardMockView = nil; + [_keyboardPlaceholderView removeFromSuperview]; + _keyboardPlaceholderView = nil; keyboardWindow.hidden = NO; } diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index c8b3df48..90ded27a 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -958,7 +958,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture // we instead show a snapshot of the keyboard and hide it // to give the illusion that the keyboard is being moved by the user. if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { - [self.textInputbar showKeyboardSnapshot:YES]; + [self.textInputbar showKeyboardPlaceholder:YES]; } originalFrame = keyboardView.frame; @@ -1010,7 +1010,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture if (!dragging) { if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardSnapshot:NO]; + [self.textInputbar showKeyboardPlaceholder:NO]; } break; @@ -1050,7 +1050,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture self.movingKeyboard = NO; if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardSnapshot:NO]; + [self.textInputbar showKeyboardPlaceholder:NO]; } }]; From ea34b6c94187119d511dbe345c7b868f5e86e00d Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Tue, 16 Feb 2016 17:22:04 -0800 Subject: [PATCH 10/15] Splits the keyboard snapshot placeholder method in 2, show and hide, and tweaks the internals to work in other edge cases. --- Source/SLKTextInputbar.h | 11 ++++++++--- Source/SLKTextInputbar.m | 26 +++++++++++++++++--------- Source/SLKTextViewController.m | 6 +++--- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index a738c10f..43a1e29c 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -123,12 +123,17 @@ typedef NS_ENUM(NSUInteger, SLKCounterPosition) { - (void)endTextEdition; /** - YES if a keyboard snapshot placeholder should be shown, replacing the system keyboard. + Shows a keyboard snapshot placeholder, replacing the system keyboard. The snapshot is being added as a subview, aligned at the same position the keyboard is, before hiding it momentarily. - @param show YES if a keyboard snapshot placeholder should show instead of the system keyboard. + @param view The view where the snapshot is taken for, for aligning purposes. */ -- (void)showKeyboardPlaceholder:(BOOL)show; +- (void)showKeyboardPlaceholderFromView:(UIView *)view; + +/** + Hides the visible keyboard snapshot placeholder, if applicable. + */ +- (void)hideKeyboardPlaceholder; #pragma mark - Text Counting diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 046295ac..35bf2e57 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -541,24 +541,26 @@ - (void)slk_updateCounter #pragma mark - Keyboard Snapshot Placeholder -- (void)showKeyboardPlaceholder:(BOOL)show +- (void)showKeyboardPlaceholderFromView:(UIView *)view { - UIWindow *keyboardWindow = [self keyboardWindow]; + UIWindow *keyboardWindow = [self slk_keyboardWindow]; - if (!_keyboardPlaceholderView && show) { + if (!_keyboardPlaceholderView && keyboardWindow) { // Takes a snapshot of the keyboard's window UIView *snapshotView = [keyboardWindow snapshotViewAfterScreenUpdates:NO]; + CGRect screenBounds = [UIScreen mainScreen].bounds; + // Shifts the snapshot up to fit to the bottom CGRect snapshowFrame = snapshotView.frame; - snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(self.superview.frame); + snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(screenBounds); snapshotView.frame = snapshowFrame; - CGRect mockframe = self.inputAccessoryView.keyboardViewProxy.frame; - mockframe.origin.y = CGRectGetHeight(self.frame); + CGRect keyboardFrame = self.inputAccessoryView.keyboardViewProxy.frame; + keyboardFrame.origin.y = CGRectGetHeight(self.frame); - self.keyboardPlaceholderView = [[UIView alloc] initWithFrame:mockframe]; + self.keyboardPlaceholderView = [[UIView alloc] initWithFrame:keyboardFrame]; self.keyboardPlaceholderView.backgroundColor = [UIColor clearColor]; [self.keyboardPlaceholderView addSubview:snapshotView]; @@ -570,7 +572,13 @@ - (void)showKeyboardPlaceholder:(BOOL)show keyboardWindow.hidden = YES; }); } - else if (_keyboardPlaceholderView && !show) { +} + +- (void)hideKeyboardPlaceholder +{ + UIWindow *keyboardWindow = [self slk_keyboardWindow]; + + if (_keyboardPlaceholderView && keyboardWindow) { [_keyboardPlaceholderView removeFromSuperview]; _keyboardPlaceholderView = nil; @@ -579,7 +587,7 @@ - (void)showKeyboardPlaceholder:(BOOL)show } } -- (UIWindow *)keyboardWindow +- (UIWindow *)slk_keyboardWindow { NSArray *array = [[UIApplication sharedApplication] windows]; diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 90ded27a..f05d1dc9 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -958,7 +958,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture // we instead show a snapshot of the keyboard and hide it // to give the illusion that the keyboard is being moved by the user. if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { - [self.textInputbar showKeyboardPlaceholder:YES]; + [self.textInputbar showKeyboardPlaceholderFromView:self.view]; } originalFrame = keyboardView.frame; @@ -1010,7 +1010,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture if (!dragging) { if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardPlaceholder:NO]; + [self.textInputbar hideKeyboardPlaceholder]; } break; @@ -1050,7 +1050,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture self.movingKeyboard = NO; if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardPlaceholder:NO]; + [self.textInputbar hideKeyboardPlaceholder]; } }]; From ba534e97050d39f7b971999db3a0c0092535cfc1 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Tue, 16 Feb 2016 17:35:51 -0800 Subject: [PATCH 11/15] Moves the keyboard snapshot earlier, when the gesture is recognised. This helps avoiding a lag when dragging the text input bar while the keyboard placeholder isn't yet shown. Still needs tweaks. --- Source/SLKTextInputbar.h | 4 ++-- Source/SLKTextInputbar.m | 19 ++++++++++--------- Source/SLKTextViewController.m | 13 ++++++++++--- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index 43a1e29c..5bc6cdba 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -128,12 +128,12 @@ typedef NS_ENUM(NSUInteger, SLKCounterPosition) { @param view The view where the snapshot is taken for, for aligning purposes. */ -- (void)showKeyboardPlaceholderFromView:(UIView *)view; +- (void)prepareKeyboardPlaceholderFromView:(UIView *)view; /** Hides the visible keyboard snapshot placeholder, if applicable. */ -- (void)hideKeyboardPlaceholder; +- (void)showKeyboardPlaceholder:(BOOL)show; #pragma mark - Text Counting diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 35bf2e57..2c8c038a 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -541,12 +541,11 @@ - (void)slk_updateCounter #pragma mark - Keyboard Snapshot Placeholder -- (void)showKeyboardPlaceholderFromView:(UIView *)view +- (void)prepareKeyboardPlaceholderFromView:(UIView *)view { UIWindow *keyboardWindow = [self slk_keyboardWindow]; if (!_keyboardPlaceholderView && keyboardWindow) { - // Takes a snapshot of the keyboard's window UIView *snapshotView = [keyboardWindow snapshotViewAfterScreenUpdates:NO]; @@ -563,6 +562,14 @@ - (void)showKeyboardPlaceholderFromView:(UIView *)view self.keyboardPlaceholderView = [[UIView alloc] initWithFrame:keyboardFrame]; self.keyboardPlaceholderView.backgroundColor = [UIColor clearColor]; [self.keyboardPlaceholderView addSubview:snapshotView]; + } +} + +- (void)showKeyboardPlaceholder:(BOOL)show +{ + UIWindow *keyboardWindow = [self slk_keyboardWindow]; + + if (show && self.keyboardPlaceholderView && keyboardWindow) { // Adds the placeholder view to the input bar, so when it looks they are sticked together. [self addSubview:self.keyboardPlaceholderView]; @@ -572,13 +579,7 @@ - (void)showKeyboardPlaceholderFromView:(UIView *)view keyboardWindow.hidden = YES; }); } -} - -- (void)hideKeyboardPlaceholder -{ - UIWindow *keyboardWindow = [self slk_keyboardWindow]; - - if (_keyboardPlaceholderView && keyboardWindow) { + else if (!show && _keyboardPlaceholderView && keyboardWindow) { [_keyboardPlaceholderView removeFromSuperview]; _keyboardPlaceholderView = nil; diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index f05d1dc9..eee45b85 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -945,6 +945,13 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture startPoint = CGPointZero; dragging = NO; + // Because the keyboard is on its own view hierarchy since iOS 9, + // we instead show a snapshot of the keyboard and hide it + // to give the illusion that the keyboard is being moved by the user. + if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { + [self.textInputbar prepareKeyboardPlaceholderFromView:self.view]; + } + break; } case UIGestureRecognizerStateChanged: { @@ -958,7 +965,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture // we instead show a snapshot of the keyboard and hide it // to give the illusion that the keyboard is being moved by the user. if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { - [self.textInputbar showKeyboardPlaceholderFromView:self.view]; + [self.textInputbar showKeyboardPlaceholder:YES]; } originalFrame = keyboardView.frame; @@ -1010,7 +1017,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture if (!dragging) { if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar hideKeyboardPlaceholder]; + [self.textInputbar showKeyboardPlaceholder:NO]; } break; @@ -1050,7 +1057,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture self.movingKeyboard = NO; if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar hideKeyboardPlaceholder]; + [self.textInputbar showKeyboardPlaceholder:NO]; } }]; From 2153d54913a290dfbc06365735e9711513fa35c9 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Wed, 20 Jul 2016 02:00:25 -0700 Subject: [PATCH 12/15] Drops the iOS 8 keyboard panning technique to simplify things. Also improves the keyboard window hiding so it doesn't glitch too often. --- .../Base.lproj/Main.storyboard | 6 ++-- Source/SLKTextInputbar.m | 11 +++--- Source/SLKTextViewController.m | 36 ++++++------------- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/Examples/Messenger-Storyboard/Base.lproj/Main.storyboard b/Examples/Messenger-Storyboard/Base.lproj/Main.storyboard index 29b45d46..43ba7a05 100644 --- a/Examples/Messenger-Storyboard/Base.lproj/Main.storyboard +++ b/Examples/Messenger-Storyboard/Base.lproj/Main.storyboard @@ -1,14 +1,14 @@ - + - + - + diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 41b44e41..77356515 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -560,6 +560,7 @@ - (void)prepareKeyboardPlaceholderFromView:(UIView *)view - (void)showKeyboardPlaceholder:(BOOL)show { UIWindow *keyboardWindow = [self slk_keyboardWindow]; + int64_t delay = NSEC_PER_SEC * 0.025; if (show && self.keyboardPlaceholderView && keyboardWindow) { @@ -567,16 +568,18 @@ - (void)showKeyboardPlaceholder:(BOOL)show [self addSubview:self.keyboardPlaceholderView]; // Let's delay hiding the keyboard's window to avoid noticeable glitches - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.01 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ keyboardWindow.hidden = YES; }); } else if (!show && _keyboardPlaceholderView && keyboardWindow) { - [_keyboardPlaceholderView removeFromSuperview]; - _keyboardPlaceholderView = nil; - keyboardWindow.hidden = NO; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ + [_keyboardPlaceholderView removeFromSuperview]; + _keyboardPlaceholderView = nil; + }); } } diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 51ff9723..3d3b506a 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -910,7 +910,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture static CGRect originalFrame; static BOOL dragging = NO; - __block UIView *keyboardView = [_textInputbar.inputAccessoryView keyboardViewProxy]; + __block UIView *keyboardView = [self.textInputbar.inputAccessoryView keyboardViewProxy]; // When no keyboard view has been detecting, let's skip any handling. if (!keyboardView) { @@ -930,28 +930,21 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture startPoint = CGPointZero; dragging = NO; - // Because the keyboard is on its own view hierarchy since iOS 9, - // we instead show a snapshot of the keyboard and hide it + // Shows a snapshot of the keyboard and hides it // to give the illusion that the keyboard is being moved by the user. - if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { - [self.textInputbar prepareKeyboardPlaceholderFromView:self.view]; - } + [self.textInputbar prepareKeyboardPlaceholderFromView:self.view]; break; } case UIGestureRecognizerStateChanged: { - if (CGRectContainsPoint(_textInputbar.frame, gestureLocation) || dragging){ + if (CGRectContainsPoint(self.textInputbar.frame, gestureLocation) || dragging){ if (CGPointEqualToPoint(startPoint, CGPointZero)) { startPoint = gestureLocation; dragging = YES; - // Because the keyboard is on its own view hierarchy since iOS 9, - // we instead show a snapshot of the keyboard and hide it - // to give the illusion that the keyboard is being moved by the user. - if (SLK_IS_IOS9_AND_HIGHER && gestureVelocity.y > 0) { - [self.textInputbar showKeyboardPlaceholder:YES]; - } + // Displays the keyboard placeholder in the text input bar's view hierarchy. + [self.textInputbar showKeyboardPlaceholder:YES]; originalFrame = keyboardView.frame; } @@ -968,9 +961,6 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture keyboardFrame.origin.y = keyboardMinY; } - keyboardView.frame = keyboardFrame; - - self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:keyboardFrame]; self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; @@ -1001,10 +991,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture case UIGestureRecognizerStateFailed: { if (!dragging) { - if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardPlaceholder:NO]; - } - + [self.textInputbar showKeyboardPlaceholder:NO]; break; } @@ -1017,7 +1004,9 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture BOOL hide = (gestureVelocity.y > minVelocity) || (transition.y > minDistance); - if (hide) keyboardFrame.origin.y = keyboardMaxY; + if (hide) { + keyboardFrame.origin.y = keyboardMaxY; + } self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:keyboardFrame]; self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; @@ -1027,7 +1016,6 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionBeginFromCurrentState animations:^{ [self.view layoutIfNeeded]; - keyboardView.frame = keyboardFrame; } completion:^(BOOL finished) { if (hide) { @@ -1041,9 +1029,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture self.movingKeyboard = NO; - if (SLK_IS_IOS9_AND_HIGHER) { - [self.textInputbar showKeyboardPlaceholder:NO]; - } + [self.textInputbar showKeyboardPlaceholder:NO]; }]; break; From f8988984cb3397c50fde9f5a53081be48529d081 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Wed, 20 Jul 2016 11:07:43 -0700 Subject: [PATCH 13/15] Explores new way of displaying a keyboard placeholder in the key window's hiearchy --- Source/SLKTextInputbar.h | 3 +++ Source/SLKTextInputbar.m | 8 ++------ Source/SLKTextViewController.m | 3 +++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index 66b350d9..58a14494 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -129,6 +129,9 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)showKeyboardPlaceholder:(BOOL)show; +@property (nonatomic, strong) UIView *keyboardPlaceholderView; + + #pragma mark - Text Counting ///------------------------------------------------ diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index 77356515..a60f879f 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -31,7 +31,6 @@ @interface SLKTextInputbar () @property (nonatomic, strong) NSArray *charCountLabelVCs; @property (nonatomic, strong) UILabel *charCountLabel; -@property (nonatomic, strong) UIView *keyboardPlaceholderView; @property (nonatomic) CGPoint previousOrigin; @@ -548,10 +547,7 @@ - (void)prepareKeyboardPlaceholderFromView:(UIView *)view snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(screenBounds); snapshotView.frame = snapshowFrame; - CGRect keyboardFrame = self.inputAccessoryView.keyboardViewProxy.frame; - keyboardFrame.origin.y = CGRectGetHeight(self.frame); - - self.keyboardPlaceholderView = [[UIView alloc] initWithFrame:keyboardFrame]; + self.keyboardPlaceholderView = [[UIView alloc] init]; self.keyboardPlaceholderView.backgroundColor = [UIColor clearColor]; [self.keyboardPlaceholderView addSubview:snapshotView]; } @@ -565,7 +561,7 @@ - (void)showKeyboardPlaceholder:(BOOL)show if (show && self.keyboardPlaceholderView && keyboardWindow) { // Adds the placeholder view to the input bar, so when it looks they are sticked together. - [self addSubview:self.keyboardPlaceholderView]; + [self.window addSubview:self.keyboardPlaceholderView]; // Let's delay hiding the keyboard's window to avoid noticeable glitches dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 3d3b506a..206b8f5d 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -961,6 +961,9 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture keyboardFrame.origin.y = keyboardMinY; } + UIView *keyboardPlaceholder = self.textInputbar.keyboardPlaceholderView; + keyboardPlaceholder.frame = [self.view.window convertRect:keyboardFrame fromView:nil]; + self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:keyboardFrame]; self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; From f5b471aee17c51acdb140543d4d6d6c17dba5b4d Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Wed, 20 Jul 2016 11:49:23 -0700 Subject: [PATCH 14/15] Moves the keyboard stubbing to SLKTextViewController along with a few minor refactoring/renaming --- Source/SLKTextInputbar.h | 16 ------- Source/SLKTextInputbar.m | 59 ------------------------ Source/SLKTextViewController.m | 82 ++++++++++++++++++++++++++++++---- 3 files changed, 73 insertions(+), 84 deletions(-) diff --git a/Source/SLKTextInputbar.h b/Source/SLKTextInputbar.h index 58a14494..04fb8d78 100644 --- a/Source/SLKTextInputbar.h +++ b/Source/SLKTextInputbar.h @@ -116,22 +116,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)endTextEdition; -/** - Shows a keyboard snapshot placeholder, replacing the system keyboard. - The snapshot is being added as a subview, aligned at the same position the keyboard is, before hiding it momentarily. - - @param view The view where the snapshot is taken for, for aligning purposes. - */ -- (void)prepareKeyboardPlaceholderFromView:(UIView *)view; - -/** - Hides the visible keyboard snapshot placeholder, if applicable. - */ -- (void)showKeyboardPlaceholder:(BOOL)show; - -@property (nonatomic, strong) UIView *keyboardPlaceholderView; - - #pragma mark - Text Counting ///------------------------------------------------ diff --git a/Source/SLKTextInputbar.m b/Source/SLKTextInputbar.m index a60f879f..3b90bbfc 100644 --- a/Source/SLKTextInputbar.m +++ b/Source/SLKTextInputbar.m @@ -530,65 +530,6 @@ - (void)slk_updateCounter } -#pragma mark - Keyboard Snapshot Placeholder - -- (void)prepareKeyboardPlaceholderFromView:(UIView *)view -{ - UIWindow *keyboardWindow = [self slk_keyboardWindow]; - - if (!_keyboardPlaceholderView && keyboardWindow) { - // Takes a snapshot of the keyboard's window - UIView *snapshotView = [keyboardWindow snapshotViewAfterScreenUpdates:NO]; - - CGRect screenBounds = [UIScreen mainScreen].bounds; - - // Shifts the snapshot up to fit to the bottom - CGRect snapshowFrame = snapshotView.frame; - snapshowFrame.origin.y = CGRectGetHeight(self.inputAccessoryView.keyboardViewProxy.frame) - CGRectGetHeight(screenBounds); - snapshotView.frame = snapshowFrame; - - self.keyboardPlaceholderView = [[UIView alloc] init]; - self.keyboardPlaceholderView.backgroundColor = [UIColor clearColor]; - [self.keyboardPlaceholderView addSubview:snapshotView]; - } -} - -- (void)showKeyboardPlaceholder:(BOOL)show -{ - UIWindow *keyboardWindow = [self slk_keyboardWindow]; - int64_t delay = NSEC_PER_SEC * 0.025; - - if (show && self.keyboardPlaceholderView && keyboardWindow) { - - // Adds the placeholder view to the input bar, so when it looks they are sticked together. - [self.window addSubview:self.keyboardPlaceholderView]; - - // Let's delay hiding the keyboard's window to avoid noticeable glitches - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ - keyboardWindow.hidden = YES; - }); - } - else if (!show && _keyboardPlaceholderView && keyboardWindow) { - - keyboardWindow.hidden = NO; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ - [_keyboardPlaceholderView removeFromSuperview]; - _keyboardPlaceholderView = nil; - }); - } -} - -- (UIWindow *)slk_keyboardWindow -{ - NSArray *array = [[UIApplication sharedApplication] windows]; - - // NOTE: This is risky, since the order may change in the future. - // But it is the only way of looking up for the keyboard window without using private APIs. - return [array lastObject]; -} - - #pragma mark - Notification Events - (void)slk_didChangeTextViewText:(NSNotification *)notification diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 206b8f5d..bbd19170 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -33,6 +33,9 @@ @interface SLKTextViewController () // A hairline displayed on top of the auto-completion view, to better separate the content from the control. @property (nonatomic, strong) UIView *autoCompletionHairline; +// A view to stub the keyboard during the keyboard panning transition. +@property (nonatomic, strong) UIView *keyboardStubView; + // Auto-Layout height constraints used for updating their constants @property (nonatomic, strong) NSLayoutConstraint *scrollViewHC; @property (nonatomic, strong) NSLayoutConstraint *textInputbarHC; @@ -512,6 +515,15 @@ - (BOOL)slk_isIllogicalKeyboardStatus:(SLKKeyboardStatus)newStatus return YES; } +- (UIWindow *)slk_keyboardWindow +{ + NSArray *array = [[UIApplication sharedApplication] windows]; + + // NOTE: This is risky, since the order may change in the future + // but it is the only way of looking up for the keyboard's window without using private APIs. + return [array lastObject]; +} + #pragma mark - Setters @@ -930,9 +942,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture startPoint = CGPointZero; dragging = NO; - // Shows a snapshot of the keyboard and hides it - // to give the illusion that the keyboard is being moved by the user. - [self.textInputbar prepareKeyboardPlaceholderFromView:self.view]; + [self slk_prepareKeyboardStub]; break; } @@ -943,8 +953,8 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture startPoint = gestureLocation; dragging = YES; - // Displays the keyboard placeholder in the text input bar's view hierarchy. - [self.textInputbar showKeyboardPlaceholder:YES]; + // Displays the keyboard stub in the key windows's hierarchy. + [self slk_showKeyboardStub:YES]; originalFrame = keyboardView.frame; } @@ -961,8 +971,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture keyboardFrame.origin.y = keyboardMinY; } - UIView *keyboardPlaceholder = self.textInputbar.keyboardPlaceholderView; - keyboardPlaceholder.frame = [self.view.window convertRect:keyboardFrame fromView:nil]; + self.keyboardStubView.frame = [self.view.window convertRect:keyboardFrame fromView:nil]; self.keyboardHC.constant = [self slk_appropriateKeyboardHeightFromRect:keyboardFrame]; self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight]; @@ -994,7 +1003,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture case UIGestureRecognizerStateFailed: { if (!dragging) { - [self.textInputbar showKeyboardPlaceholder:NO]; + [self slk_showKeyboardStub:NO]; break; } @@ -1019,6 +1028,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionBeginFromCurrentState animations:^{ [self.view layoutIfNeeded]; + self.keyboardStubView.frame = [self.view.window convertRect:keyboardFrame fromView:nil]; } completion:^(BOOL finished) { if (hide) { @@ -1032,7 +1042,7 @@ - (void)slk_didPanTextInputBar:(UIPanGestureRecognizer *)gesture self.movingKeyboard = NO; - [self.textInputbar showKeyboardPlaceholder:NO]; + [self slk_showKeyboardStub:NO]; }]; break; @@ -1220,6 +1230,60 @@ - (void)slk_prepareForInterfaceTransitionWithDuration:(NSTimeInterval)duration }); } +// Takes a snapshot of the keyboard window and caches it in a wrapper view +// to be used later as a keyboard stub and give the illusion that the keyboard is being panned by the user. +- (void)slk_prepareKeyboardStub +{ + UIWindow *keyboardWindow = [self slk_keyboardWindow]; + + if (!_keyboardStubView && keyboardWindow) { + // Takes a snapshot of the keyboard's window + UIView *snapshotView = [keyboardWindow snapshotViewAfterScreenUpdates:NO]; + UIView *keyboardView = [self.textInputbar.inputAccessoryView keyboardViewProxy]; + + CGRect screenBounds = [UIScreen mainScreen].bounds; + + // Shifts the snapshot up to fit to the bottom + CGRect snapshowFrame = snapshotView.frame; + snapshowFrame.origin.y = CGRectGetHeight(keyboardView.frame) - CGRectGetHeight(screenBounds); + snapshotView.frame = snapshowFrame; + + self.keyboardStubView = [[UIView alloc] init]; + self.keyboardStubView.backgroundColor = [UIColor clearColor]; + [self.keyboardStubView addSubview:snapshotView]; + } +} + +// Shows/Hides the keyboard stub +- (void)slk_showKeyboardStub:(BOOL)show +{ + UIWindow *keyboardWindow = [self slk_keyboardWindow]; + int64_t delay = NSEC_PER_SEC * 0.025; + + if (!keyboardWindow || !self.keyboardStubView) { + return; + } + + if (show) { + // Adds the stub view to the key window, to overlap any other view in the hierarchy + [self.view.window addSubview:self.keyboardStubView]; + + // Let's delay hiding the keyboard's window to avoid noticeable glitches + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ + keyboardWindow.hidden = YES; + }); + } + else { + keyboardWindow.hidden = NO; + + // Let's the removal of the keyboard stub to avoid noticeable glitches + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{ + [_keyboardStubView removeFromSuperview]; + _keyboardStubView = nil; + }); + } +} + #pragma mark - Keyboard Events From fad87fdc1badd786ef289bbdfea706fff3438434 Mon Sep 17 00:00:00 2001 From: Ignacio Romero Zurbuchen Date: Wed, 20 Jul 2016 13:03:00 -0700 Subject: [PATCH 15/15] Typos --- Source/SLKTextViewController.h | 4 +--- Source/SLKTextViewController.m | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/SLKTextViewController.h b/Source/SLKTextViewController.h index 64155337..0a37522b 100644 --- a/Source/SLKTextViewController.h +++ b/Source/SLKTextViewController.h @@ -86,9 +86,7 @@ NS_CLASS_AVAILABLE_IOS(7_0) @interface SLKTextViewController : UIViewController /** YES if text view's content can be cleaned with a shake gesture. Default is NO. */ @property (nonatomic, assign) BOOL shakeToClearEnabled; -/** - YES if keyboard can be dismissed gradually with a vertical panning gesture. Default is YES. - */ +/** YES if keyboard can be dismissed gradually with a vertical panning gesture. Default is YES. */ @property (nonatomic, assign, getter = isKeyboardPanningEnabled) BOOL keyboardPanningEnabled; /** YES if an external keyboard has been detected (this value updates only when the text view becomes first responder). */ diff --git a/Source/SLKTextViewController.m b/Source/SLKTextViewController.m index 4d6b4cf3..c34a0dec 100644 --- a/Source/SLKTextViewController.m +++ b/Source/SLKTextViewController.m @@ -50,7 +50,7 @@ @interface SLKTextViewController () @property (nonatomic, getter=isViewVisible) BOOL viewVisible; // YES if the view controller's view's size is changing by its parent (i.e. when its window rotates or is resized) -@property (nonatomic, getter = isTransitioning) BOOL transitioning; +@property (nonatomic, getter=isTransitioning) BOOL transitioning; // Optional classes to be used instead of the default ones. @property (nonatomic, strong) Class textViewClass;