@@ -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