Skip to content

Commit a8554a3

Browse files
author
Camilo Vera
committed
the keyboardDimissMode is not longer set to the scrollViewProxy, now the inputAccesoryView keeps the reference of the Keyboard to move it directly, now the transition looks much more smooth
1 parent a44764b commit a8554a3

File tree

3 files changed

+143
-149
lines changed

3 files changed

+143
-149
lines changed

Source/Classes/SLKInputAccessoryView.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
#import <UIKit/UIKit.h>
1818

19-
UIKIT_EXTERN NSString * const SLKInputAccessoryViewKeyboardFrameDidChangeNotification;
20-
2119
@interface SLKInputAccessoryView : UIView
22-
20+
/*
21+
* Used to save the reference of the keyboard or the Keyboard container..
22+
*/
23+
@property (nonatomic, weak, readonly) UIView *keyboard;
2324
@end

Source/Classes/SLKInputAccessoryView.m

Lines changed: 3 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -17,84 +17,16 @@
1717
#import "SLKInputAccessoryView.h"
1818
#import "SLKUIConstants.h"
1919

20-
NSString * const SLKInputAccessoryViewKeyboardFrameDidChangeNotification = @"SLKInputAccessoryViewKeyboardFrameDidChangeNotification";
21-
22-
@interface SLKInputAccessoryView ()
23-
@property (nonatomic, weak) UIView *observedSuperview;
24-
@end
25-
2620
@implementation SLKInputAccessoryView
2721

28-
#pragma mark - Getters
29-
30-
NSString *SLKKeyboardHandlingKeyPath()
31-
{
32-
// Listening for the superview's frame doesn't work on iOS8 and above, so we use its center
33-
if (SLK_IS_IOS8_AND_HIGHER) {
34-
return NSStringFromSelector(@selector(center));
35-
}
36-
else {
37-
return NSStringFromSelector(@selector(frame));
38-
}
39-
}
40-
41-
4222
#pragma mark - Super Overrides
4323

4424
- (void)willMoveToSuperview:(UIView *)newSuperview
4525
{
46-
[self slk_removeSuperviewObserver];
47-
[self slk_addSuperviewObserver:newSuperview];
48-
49-
[super willMoveToSuperview:newSuperview];
50-
}
51-
52-
53-
#pragma mark - Superview handling
54-
55-
- (void)slk_addSuperviewObserver:(UIView *)superview
56-
{
57-
if (!_observedSuperview && superview) {
58-
_observedSuperview = superview;
59-
[superview addObserver:self forKeyPath:SLKKeyboardHandlingKeyPath() options:0 context:NULL];
26+
if (newSuperview != nil)
27+
{
28+
_keyboard = newSuperview;
6029
}
6130
}
6231

63-
- (void)slk_removeSuperviewObserver
64-
{
65-
if (_observedSuperview) {
66-
[self.observedSuperview removeObserver:self forKeyPath:SLKKeyboardHandlingKeyPath()];
67-
_observedSuperview = nil;
68-
}
69-
}
70-
71-
72-
#pragma mark - Events
73-
74-
- (void)slk_didChangeKeyboardFrame:(CGRect)frame
75-
{
76-
NSDictionary *userInfo = @{UIKeyboardFrameEndUserInfoKey:[NSValue valueWithCGRect:frame]};
77-
[[NSNotificationCenter defaultCenter] postNotificationName:SLKInputAccessoryViewKeyboardFrameDidChangeNotification object:nil userInfo:userInfo];
78-
}
79-
80-
81-
#pragma mark - KVO Listener
82-
83-
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
84-
{
85-
if ([object isEqual:self.superview] && [keyPath isEqualToString:SLKKeyboardHandlingKeyPath()]) {
86-
[self slk_didChangeKeyboardFrame:self.superview.frame];
87-
}
88-
else {
89-
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
90-
}
91-
}
92-
93-
#pragma mark - Lifeterm
94-
95-
- (void)dealloc
96-
{
97-
[self slk_removeSuperviewObserver];
98-
}
99-
10032
@end

Source/Classes/SLKTextViewController.m

Lines changed: 136 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ @interface SLKTextViewController ()
3838
// A hairline displayed on top of the auto-completion view, to better separate the content from the control.
3939
@property (nonatomic, strong) UIView *autoCompletionHairline;
4040

41+
@property (nonatomic, strong) SLKInputAccessoryView *inputAccessoryView;
42+
4143
// Auto-Layout height constraints used for updating their constants
4244
@property (nonatomic, strong) NSLayoutConstraint *scrollViewHC;
4345
@property (nonatomic, strong) NSLayoutConstraint *textInputbarHC;
@@ -372,19 +374,20 @@ - (UIButton *)rightButton
372374

373375
- (SLKInputAccessoryView *)emptyInputAccessoryView
374376
{
375-
if (!self.isKeyboardPanningEnabled) {
376-
return nil;
377-
}
378-
379-
SLKInputAccessoryView *view = [[SLKInputAccessoryView alloc] initWithFrame:self.textInputbar.bounds];
380-
view.backgroundColor = [UIColor clearColor];
381-
view.userInteractionEnabled = NO;
382-
377+
if (self.inputAccessoryView == nil)
378+
{
379+
SLKInputAccessoryView *view = [[SLKInputAccessoryView alloc] initWithFrame:self.textInputbar.bounds];
380+
view.backgroundColor = [UIColor clearColor];
381+
view.userInteractionEnabled = NO;
382+
383383
#if SLK_INPUT_ACCESSORY_DEBUG
384-
view.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.5];
384+
view.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.5];
385385
#endif
386+
387+
self.inputAccessoryView = view;
388+
}
386389

387-
return view;
390+
return self.inputAccessoryView;
388391
}
389392

390393
- (UIModalPresentationStyle)modalPresentationStyle
@@ -506,6 +509,8 @@ - (void)setScrollViewProxy:(UIScrollView *)scrollView
506509

507510
[scrollView addGestureRecognizer:self.singleTapGesture];
508511

512+
[scrollView.panGestureRecognizer addTarget:self action:@selector(slk_didPanScrollView:)];
513+
509514
_scrollViewProxy = scrollView;
510515
}
511516

@@ -532,17 +537,6 @@ - (void)setInverted:(BOOL)inverted
532537
self.automaticallyAdjustsScrollViewInsets = inverted ? NO : YES;
533538
}
534539

535-
- (void)setKeyboardPanningEnabled:(BOOL)enabled
536-
{
537-
if (_keyboardPanningEnabled == enabled) {
538-
return;
539-
}
540-
541-
_keyboardPanningEnabled = enabled;
542-
543-
self.scrollViewProxy.keyboardDismissMode = enabled ? UIScrollViewKeyboardDismissModeInteractive : UIScrollViewKeyboardDismissModeNone;
544-
}
545-
546540
- (BOOL)slk_updateKeyboardStatus:(SLKKeyboardStatus)status
547541
{
548542
// Skips if trying to update the same status
@@ -817,6 +811,127 @@ - (void)willRequestUndo
817811

818812
#pragma mark - Private Methods
819813

814+
- (void)slk_didPanScrollView:(UIPanGestureRecognizer *)gesture
815+
{
816+
//if the panning is not enable, then let's skip it..
817+
if (!self.keyboardPanningEnabled) return;
818+
819+
// Skips this if it's not the expected textView.
820+
// Checking the keyboard height constant helps to disable the view constraints update on iPad when the keyboard is undocked.
821+
// Checking the keyboard status allows to keep the inputAccessoryView valid when still reacing the bottom of the screen.
822+
if (![self.textView isFirstResponder] || (self.keyboardHC.constant == 0 && self.keyboardStatus == SLKKeyboardStatusDidHide)) {
823+
return;
824+
}
825+
826+
// Skips if the view isn't visible
827+
if (!self.view.window) {
828+
return;
829+
}
830+
831+
// Skips if it is presented inside of a popover
832+
if (self.isPresentedInPopover) {
833+
return;
834+
}
835+
836+
static CGPoint startPoint = (CGPoint){0,0};
837+
static BOOL dragging = NO;
838+
static CGRect originalFrame = (CGRect){{0,0},{0,0}};
839+
840+
CGPoint point = [gesture locationInView:self.view];
841+
842+
if (gesture.state == UIGestureRecognizerStateBegan)
843+
{
844+
startPoint = CGPointZero;
845+
dragging = NO;
846+
}
847+
else if (gesture.state == UIGestureRecognizerStateChanged)
848+
{
849+
if (CGRectContainsPoint(self.textInputbar.frame, point) || dragging)
850+
{
851+
if (CGPointEqualToPoint(startPoint, CGPointZero)) {
852+
startPoint = point;
853+
dragging = YES;
854+
originalFrame = self.inputAccessoryView.keyboard.frame;
855+
}
856+
857+
self.movingKeyboard = YES;
858+
859+
CGPoint transition = CGPointMake(point.x - startPoint.x, point.y - startPoint.y);
860+
861+
CGRect keyboardFrame = originalFrame;
862+
863+
keyboardFrame.origin.y+= MAX(transition.y,0);
864+
865+
self.inputAccessoryView.keyboard.frame = keyboardFrame;
866+
867+
CGFloat constant = MAX(0.0, CGRectGetHeight(self.view.bounds) - CGRectGetMinY(keyboardFrame) - CGRectGetHeight(self.textView.inputAccessoryView.bounds));
868+
869+
self.keyboardHC.constant = constant;
870+
self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight];
871+
872+
// layoutIfNeeded must be called before any further scrollView internal adjustments (content offset and size)
873+
[self.view layoutIfNeeded];
874+
875+
// Overrides the scrollView's contentOffset to allow following the same position when dragging the keyboard
876+
CGPoint offset = _scrollViewOffsetBeforeDragging;
877+
878+
if (self.isInverted)
879+
{
880+
if (!self.scrollViewProxy.isDecelerating && self.scrollViewProxy.isTracking) {
881+
self.scrollViewProxy.contentOffset = _scrollViewOffsetBeforeDragging;
882+
}
883+
}
884+
else
885+
{
886+
CGFloat keyboardHeightDelta = _keyboardHeightBeforeDragging-self.keyboardHC.constant;
887+
offset.y -= keyboardHeightDelta;
888+
889+
self.scrollViewProxy.contentOffset = offset;
890+
}
891+
}
892+
893+
}
894+
else if (gesture.state == UIGestureRecognizerStateCancelled ||
895+
gesture.state == UIGestureRecognizerStateEnded ||
896+
gesture.state == UIGestureRecognizerStateFailed )
897+
{
898+
if (dragging)
899+
{
900+
CGPoint transition = CGPointMake(point.x - startPoint.x, point.y - startPoint.y);
901+
902+
if (transition.y < 0) transition.y = 0;
903+
904+
startPoint = CGPointZero;
905+
dragging = NO;
906+
CGRect keyboardFrame = originalFrame;
907+
908+
//the velocity can be changed to hide or show the keyboard based on the gesture
909+
CGFloat minVelocity = 20.0f;
910+
BOOL hide = [gesture velocityInView:self.view].y > minVelocity || transition.y > keyboardFrame.size.height/2.0f;
911+
912+
if (hide) keyboardFrame.origin.y = [UIScreen mainScreen].bounds.size.height;
913+
914+
CGFloat constant = MAX(0.0, CGRectGetHeight(self.view.bounds) - CGRectGetMinY(keyboardFrame) - CGRectGetHeight(self.textView.inputAccessoryView.bounds));
915+
916+
self.keyboardHC.constant = constant;
917+
self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight];
918+
919+
[UIView animateWithDuration:0.15 animations:^{
920+
[self.view layoutIfNeeded];
921+
self.inputAccessoryView.keyboard.frame = keyboardFrame;
922+
} completion:^(BOOL finished) {
923+
if (hide)
924+
{
925+
[UIView performWithoutAnimation:^{
926+
[self.textView resignFirstResponder];
927+
}];
928+
}
929+
}];
930+
}
931+
932+
}
933+
}
934+
820935
- (void)slk_didTapScrollView:(UIGestureRecognizer *)gesture
821936
{
822937
if (!self.isPresentedInPopover && !self.isExternalKeyboardDetected) {
@@ -1173,55 +1288,6 @@ - (void)slk_didShowOrHideKeyboard:(NSNotification *)notification
11731288
self.movingKeyboard = NO;
11741289
}
11751290

1176-
- (void)slk_didChangeKeyboardFrame:(NSNotification *)notification
1177-
{
1178-
// Skips if the view isn't visible
1179-
if (!self.view.window) {
1180-
return;
1181-
}
1182-
1183-
// Skips if it is presented inside of a popover
1184-
if (self.isPresentedInPopover) {
1185-
return;
1186-
}
1187-
1188-
// Skips this if it's not the expected textView.
1189-
// Checking the keyboard height constant helps to disable the view constraints update on iPad when the keyboard is undocked.
1190-
// Checking the keyboard status allows to keep the inputAccessoryView valid when still reacing the bottom of the screen.
1191-
if (![self.textView isFirstResponder] || (self.keyboardHC.constant == 0 && self.keyboardStatus == SLKKeyboardStatusDidHide)) {
1192-
return;
1193-
}
1194-
1195-
if (self.scrollViewProxy.isDragging) {
1196-
self.movingKeyboard = YES;
1197-
}
1198-
1199-
if (self.isMovingKeyboard == NO) {
1200-
return;
1201-
}
1202-
1203-
self.keyboardHC.constant = [self slk_appropriateKeyboardHeight:notification];
1204-
self.scrollViewHC.constant = [self slk_appropriateScrollViewHeight];
1205-
1206-
// layoutIfNeeded must be called before any further scrollView internal adjustments (content offset and size)
1207-
[self.view layoutIfNeeded];
1208-
1209-
// Overrides the scrollView's contentOffset to allow following the same position when dragging the keyboard
1210-
CGPoint offset = _scrollViewOffsetBeforeDragging;
1211-
1212-
if (self.isInverted) {
1213-
if (!self.scrollViewProxy.isDecelerating && self.scrollViewProxy.isTracking) {
1214-
self.scrollViewProxy.contentOffset = _scrollViewOffsetBeforeDragging;
1215-
}
1216-
}
1217-
else {
1218-
CGFloat keyboardHeightDelta = _keyboardHeightBeforeDragging-self.keyboardHC.constant;
1219-
offset.y -= keyboardHeightDelta;
1220-
1221-
self.scrollViewProxy.contentOffset = offset;
1222-
}
1223-
}
1224-
12251291
- (void)slk_didPostSLKKeyboardNotification:(NSNotification *)notification
12261292
{
12271293
if (![notification.object isEqual:self.textView]) {
@@ -1865,8 +1931,6 @@ - (void)slk_registerNotifications
18651931
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didPostSLKKeyboardNotification:) name:SLKKeyboardDidHideNotification object:nil];
18661932
#endif
18671933

1868-
// Keyboard Accessory View notifications
1869-
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_didChangeKeyboardFrame:) name:SLKInputAccessoryViewKeyboardFrameDidChangeNotification object:nil];
18701934

18711935
// TextView notifications
18721936
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(slk_willChangeTextViewText:) name:SLKTextViewTextWillChangeNotification object:nil];
@@ -1897,9 +1961,6 @@ - (void)slk_unregisterNotifications
18971961
[[NSNotificationCenter defaultCenter] removeObserver:self name:SLKKeyboardDidHideNotification object:nil];
18981962
#endif
18991963

1900-
// TextView notifications
1901-
[[NSNotificationCenter defaultCenter] removeObserver:self name:SLKInputAccessoryViewKeyboardFrameDidChangeNotification object:nil];
1902-
19031964
// TextView notifications
19041965
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil];
19051966
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil];

0 commit comments

Comments
 (0)