diff --git a/node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingViewManager.m b/node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingViewManager.m index 1333a10..b908006 100644 --- a/node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingViewManager.m +++ b/node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingViewManager.m @@ -23,7 +23,7 @@ NSUInteger const kInputViewKey = 101010; NSUInteger const kMaxDeferedInitializeAccessoryViews = 15; NSInteger const kTrackingViewNotFoundErrorCode = 1; -NSInteger const kBottomViewHeight = 100; +NSInteger const kBottomViewHeight = 34; typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { KeyboardTrackingScrollBehaviorNone, @@ -40,6 +40,7 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { CGFloat _bottomViewHeight; } +@property (nonatomic) NSMutableDictionary* rctScrollViewsArray; @property (nonatomic, strong) UIScrollView *scrollViewToManage; @property (nonatomic) BOOL scrollIsInverted; @property (nonatomic) BOOL revealKeyboardInteractive; @@ -53,6 +54,15 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { @property (nonatomic) BOOL scrollToFocusedInput; @property (nonatomic) BOOL allowHitsOutsideBounds; +@property (nonatomic) BOOL normalList; +@property (nonatomic) NSString* scrollViewNativeID; +@property (nonatomic) CGFloat initialOffsetY; +@property (nonatomic) NSInteger viewInitialOffsetY; +@property (nonatomic) BOOL initialOffsetIsSet; +@property (nonatomic) BOOL paused; +@property (nonatomic, strong) UIView *accessoriesContainer; +@property (nonatomic) NSString* accessoriesContainerID; + @end @interface KeyboardTrackingView () @@ -70,17 +80,24 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { [self addObserver:self forKeyPath:@"bounds" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL]; _inputViewsMap = [NSMapTable weakToWeakObjectsMapTable]; _deferedInitializeAccessoryViewsCount = 0; + _rctScrollViewsArray = [[NSMutableDictionary alloc] init]; _observingInputAccessoryView = [ObservingInputAccessoryView new]; _observingInputAccessoryView.delegate = self; + _initialOffsetY = 0; + _initialOffsetIsSet = NO; + _viewInitialOffsetY = 0; + _manageScrollView = YES; _allowHitsOutsideBounds = NO; + _requiresSameParentToManageScrollView = YES; _bottomViewHeight = kBottomViewHeight; self.addBottomView = NO; self.scrollToFocusedInput = NO; + self.paused = NO; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rctContentDidAppearNotification:) name:RCTContentDidAppearNotification object:nil]; } @@ -134,7 +151,7 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { UIView* subview; for (UIView* view in webview.scrollView.subviews) { - if([[view.class description] hasPrefix:@"UIWeb"]) + if([[view.class description] hasPrefix:@"WKWeb"]) { subview = view; } @@ -149,7 +166,7 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { if(!_newClass) return; Method method = class_getInstanceMethod([UIResponder class], @selector(inputAccessoryView)); - class_addMethod(_newClass, @selector(inputAccessoryView), imp_implementationWithBlock(^(id _self){return _observingInputAccessoryView;}), method_getTypeEncoding(method)); + class_addMethod(_newClass, @selector(inputAccessoryView), imp_implementationWithBlock(^(id _self){return self->_observingInputAccessoryView;}), method_getTypeEncoding(method)); objc_registerClassPair(_newClass); } @@ -167,33 +184,32 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { - (void)initializeAccessoryViewsAndHandleInsets { NSArray* allSubviews = [self getBreadthFirstSubviewsForView:[self getRootView]]; - NSMutableArray* rctScrollViewsArray = [NSMutableArray array]; for (UIView* subview in allSubviews) { + if(subview.nativeID) { + NSLog(@"self.accessoriesContainerID %@ %@", self.accessoriesContainerID, subview.nativeID); + } + + if (subview.nativeID && [subview.nativeID isEqualToString:self.accessoriesContainerID]) { + NSLog(@"SuperView ID: %@", subview.nativeID); + _accessoriesContainer = subview; + } + if(_manageScrollView) { if(_scrollViewToManage == nil) { - if(_requiresSameParentToManageScrollView && [subview isKindOfClass:[RCTScrollView class]] && subview.superview == self.superview) + if([subview isKindOfClass:[RCTScrollView class]]) { - _scrollViewToManage = ((RCTScrollView*)subview).scrollView; - } - else if(!_requiresSameParentToManageScrollView && [subview isKindOfClass:[UIScrollView class]]) - { - _scrollViewToManage = (UIScrollView*)subview; - } + RCTScrollView *scrollView = (RCTScrollView*)subview; - if(_scrollViewToManage != nil) - { - _scrollIsInverted = CGAffineTransformEqualToTransform(_scrollViewToManage.superview.transform, CGAffineTransformMakeScale(1, -1)); + if (subview.nativeID && [subview.nativeID isEqualToString:self.scrollViewNativeID]) { + [_rctScrollViewsArray setObject:scrollView forKey:subview.nativeID]; + _scrollViewToManage = scrollView.scrollView; + } } } - - if([subview isKindOfClass:[RCTScrollView class]]) - { - [rctScrollViewsArray addObject:(RCTScrollView*)subview]; - } } if ([subview isKindOfClass:NSClassFromString(@"RCTTextField")]) @@ -242,7 +258,7 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { } } - for (RCTScrollView *scrollView in rctScrollViewsArray) + for (RCTScrollView *scrollView in [_rctScrollViewsArray allValues]) { if(scrollView.scrollView == _scrollViewToManage) { @@ -267,6 +283,38 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { [self addBottomViewIfNecessary]; } +- (void)resetScrollView:(NSString*) scrollViewNativeID { + for (RCTScrollView *scrollView in [_rctScrollViewsArray allValues]) + { + [scrollView removeScrollListener:self]; + } + [_rctScrollViewsArray removeAllObjects]; + _scrollViewToManage = nil; + _scrollViewNativeID = scrollViewNativeID; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self deferedInitializeAccessoryViewsAndHandleInsets]; + [self scrollToStart]; + }); +} + +-(void)pauseTracking:(NSString*) scrollViewNativeID { + if ([self.scrollViewNativeID isEqualToString:scrollViewNativeID]) { + _observingInputAccessoryView.delegate = nil; + self.scrollViewToManage = nil; + self.accessoriesContainer = nil; + self.paused = YES; + } +} + +-(void)resumeTracking:(NSString*) scrollViewNativeID { + if ([self.scrollViewNativeID isEqualToString:scrollViewNativeID]) { + self.paused = NO; + _observingInputAccessoryView.delegate = self; + [self deferedInitializeAccessoryViewsAndHandleInsets]; + } +} + - (void)setupTextView:(UITextView*)textView { if (textView != nil) @@ -338,12 +386,19 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if (self.paused) { + return; + } _observingInputAccessoryView.height = self.bounds.size.height; } - (void)observingInputAccessoryViewKeyboardWillDisappear:(ObservingInputAccessoryView *)observingInputAccessoryView { - _bottomViewHeight = kBottomViewHeight; + if (self.paused) { + return; + } + + _bottomViewHeight = [self getBottomSafeArea]; [self updateBottomViewFrame]; } @@ -386,34 +441,44 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { - (void)_updateScrollViewInsets { - if(self.scrollViewToManage != nil) + if(self.scrollViewToManage != nil && !self.paused) { + if (!_initialOffsetIsSet) { + self.initialOffsetY = self.scrollViewToManage.contentOffset.y; + _initialOffsetIsSet = YES; + } + + if (_observingInputAccessoryView.keyboardState != KeyboardStateWillHide && _observingInputAccessoryView.keyboardState != KeyboardStateHidden) { + [self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, self.initialOffsetY) animated:NO]; + } + UIEdgeInsets insets = self.scrollViewToManage.contentInset; CGFloat bottomSafeArea = [self getBottomSafeArea]; CGFloat bottomInset = MAX(self.bounds.size.height, _observingInputAccessoryView.keyboardHeight + _observingInputAccessoryView.height); CGFloat originalBottomInset = self.scrollIsInverted ? insets.top : insets.bottom; CGPoint originalOffset = self.scrollViewToManage.contentOffset; + CGFloat keyboardHeight = _observingInputAccessoryView.keyboardHeight; - bottomInset += (_observingInputAccessoryView.keyboardHeight == 0 ? bottomSafeArea : 0); + bottomInset += (keyboardHeight == 0 ? bottomSafeArea : 0); if(self.scrollIsInverted) { insets.top = bottomInset; } else { - insets.bottom = bottomInset; + insets.bottom = keyboardHeight; } self.scrollViewToManage.contentInset = insets; if(self.scrollBehavior == KeyboardTrackingScrollBehaviorScrollToBottomInvertedOnly && _scrollIsInverted) { - BOOL fisrtTime = _observingInputAccessoryView.keyboardHeight == 0 && _observingInputAccessoryView.keyboardState == KeyboardStateHidden; + BOOL firstTime = _observingInputAccessoryView.keyboardHeight == 0 && _observingInputAccessoryView.keyboardState == KeyboardStateHidden; BOOL willOpen = _observingInputAccessoryView.keyboardHeight != 0 && _observingInputAccessoryView.keyboardState == KeyboardStateHidden; BOOL isOpen = _observingInputAccessoryView.keyboardHeight != 0 && _observingInputAccessoryView.keyboardState == KeyboardStateShown; - if(fisrtTime || willOpen || (isOpen && !self.isDraggingScrollView)) + if(firstTime || willOpen || (isOpen && !self.isDraggingScrollView)) { - [self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, -self.scrollViewToManage.contentInset.top) animated:!fisrtTime]; + [self.scrollViewToManage setContentOffset:CGPointMake(self.scrollViewToManage.contentOffset.x, self.scrollViewToManage.contentOffset.y) animated:!firstTime]; } } else if(self.scrollBehavior == KeyboardTrackingScrollBehaviorFixedOffset && !self.isDraggingScrollView) @@ -422,16 +487,21 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { self.scrollViewToManage.contentOffset = CGPointMake(originalOffset.x, originalOffset.y + insetsDiff); } - insets = self.scrollViewToManage.contentInset; - if(self.scrollIsInverted) - { - insets.top = bottomInset; + CGFloat kHeight = _observingInputAccessoryView.keyboardHeight; + if (kHeight != 0 && (_observingInputAccessoryView.keyboardState == KeyboardStateShown || _observingInputAccessoryView.keyboardState == KeyboardStateWillShow)) { + kHeight -= (bottomSafeArea + _viewInitialOffsetY); } - else - { - insets.bottom = bottomInset; + + CGFloat positionY = self.normalList ? 0 : kHeight; + CGRect frame = CGRectMake(self.scrollViewToManage.frame.origin.x, positionY, + self.scrollViewToManage.frame.size.width, self.scrollViewToManage.frame.size.height); + self.scrollViewToManage.frame = frame; + + if (self.accessoriesContainer) { + CGFloat containerPositionY = self.normalList ? 0 : kHeight; + self.accessoriesContainer.bounds = CGRectMake(self.accessoriesContainer.bounds.origin.x, containerPositionY, + self.accessoriesContainer.bounds.size.width, self.accessoriesContainer.bounds.size.height); } - self.scrollViewToManage.scrollIndicatorInsets = insets; } } @@ -448,7 +518,6 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { if (self.addBottomView && _bottomView == nil) { _bottomView = [UIView new]; - _bottomView.backgroundColor = [UIColor whiteColor]; [self addSubview:_bottomView]; [self updateBottomViewFrame]; } @@ -467,6 +536,12 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { } } +-(void)setBottomViewBackgroundColor:(UIColor*) color { + if (_bottomView != nil) { + _bottomView.backgroundColor = color; + } +} + #pragma mark - safe area -(void)safeAreaInsetsDidChange @@ -495,7 +570,7 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { - (void) rctContentDidAppearNotification:(NSNotification*)notification { dispatch_async(dispatch_get_main_queue(), ^{ - if(notification.object == [self getRootView] && _manageScrollView && _scrollViewToManage == nil) + if(notification.object == [self getRootView] && self->_manageScrollView && self->_scrollViewToManage == nil) { [self initializeAccessoryViewsAndHandleInsets]; } @@ -506,11 +581,15 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { -(void)updateTransformAndInsets { + if (self.paused) { + return; + } + CGFloat bottomSafeArea = [self getBottomSafeArea]; - CGFloat accessoryTranslation = MIN(-bottomSafeArea, -_observingInputAccessoryView.keyboardHeight); + CGFloat accessoryTranslation = MIN(-bottomSafeArea, -(_observingInputAccessoryView.keyboardHeight - _viewInitialOffsetY)); if (_observingInputAccessoryView.keyboardHeight <= bottomSafeArea) { - _bottomViewHeight = kBottomViewHeight; + _bottomViewHeight = [self getBottomSafeArea]; } else if (_observingInputAccessoryView.keyboardState != KeyboardStateWillHide) { _bottomViewHeight = 0; } @@ -582,6 +661,8 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { self.isDraggingScrollView = YES; + _initialOffsetIsSet = NO; + self.initialOffsetY = scrollView.contentOffset.y; } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset @@ -592,6 +673,15 @@ typedef NS_ENUM(NSUInteger, KeyboardTrackingScrollBehavior) { - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { self.isDraggingScrollView = NO; + self.initialOffsetY = scrollView.contentOffset.y; +} + +- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { + self.initialOffsetY = scrollView.contentOffset.y; +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + self.initialOffsetY = scrollView.contentOffset.y; } - (CGFloat)getKeyboardHeight @@ -634,6 +724,13 @@ RCT_REMAP_VIEW_PROPERTY(requiresSameParentToManageScrollView, requiresSameParent RCT_REMAP_VIEW_PROPERTY(addBottomView, addBottomView, BOOL) RCT_REMAP_VIEW_PROPERTY(scrollToFocusedInput, scrollToFocusedInput, BOOL) RCT_REMAP_VIEW_PROPERTY(allowHitsOutsideBounds, allowHitsOutsideBounds, BOOL) +RCT_REMAP_VIEW_PROPERTY(normalList, normalList, BOOL) +RCT_REMAP_VIEW_PROPERTY(viewInitialOffsetY, viewInitialOffsetY, NSInteger) +RCT_EXPORT_VIEW_PROPERTY(scrollViewNativeID, NSString) +RCT_EXPORT_VIEW_PROPERTY(accessoriesContainerID, NSString) +RCT_CUSTOM_VIEW_PROPERTY(backgroundColor, UIColor, KeyboardTrackingView) { + [view setBottomViewBackgroundColor:[RCTConvert UIColor:json]]; +} + (BOOL)requiresMainQueueSetup { @@ -654,6 +751,48 @@ RCT_REMAP_VIEW_PROPERTY(allowHitsOutsideBounds, allowHitsOutsideBounds, BOOL) return [[KeyboardTrackingView alloc] init]; } +RCT_EXPORT_METHOD(resetScrollView:(nonnull NSNumber *)reactTag scrollViewNativeID:(NSString*)scrollViewNativeID) { + [self.bridge.uiManager addUIBlock: + ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + + KeyboardTrackingView *view = viewRegistry[reactTag]; + if (!view || ![view isKindOfClass:[KeyboardTrackingView class]]) { + RCTLogError(@"Error: cannot find KeyboardTrackingView with tag #%@", reactTag); + return; + } + + [view resetScrollView:scrollViewNativeID]; + }]; +} + +RCT_EXPORT_METHOD(pauseTracking:(nonnull NSNumber *)reactTag scrollViewNativeID:(NSString*)scrollViewNativeID) { + [self.bridge.uiManager addUIBlock: + ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + + KeyboardTrackingView *view = viewRegistry[reactTag]; + if (!view || ![view isKindOfClass:[KeyboardTrackingView class]]) { + RCTLogError(@"Error: cannot find KeyboardTrackingView with tag #%@", reactTag); + return; + } + + [view pauseTracking:scrollViewNativeID]; + }]; +} + +RCT_EXPORT_METHOD(resumeTracking:(nonnull NSNumber *)reactTag scrollViewNativeID:(NSString*)scrollViewNativeID) { + [self.bridge.uiManager addUIBlock: + ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { + + KeyboardTrackingView *view = viewRegistry[reactTag]; + if (!view || ![view isKindOfClass:[KeyboardTrackingView class]]) { + RCTLogError(@"Error: cannot find KeyboardTrackingView with tag #%@", reactTag); + return; + } + + [view resumeTracking:scrollViewNativeID]; + }]; +} + RCT_EXPORT_METHOD(getNativeProps:(nonnull NSNumber *)reactTag resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { [self.bridge.uiManager addUIBlock: diff --git a/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.android.js b/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.android.js index af15edf..20b6ab6 100644 --- a/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.android.js +++ b/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.android.js @@ -13,5 +13,8 @@ export default class KeyboardTrackingView extends PureComponent { async getNativeProps() { return {trackingViewHeight: 0, keyboardHeight: 0, contentTopInset: 0}; } + resetScrollView() {} + pauseTracking() {} + resumeTracking() {} scrollToStart() {} } diff --git a/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.ios.js b/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.ios.js index 5e2c207..07c8257 100644 --- a/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.ios.js +++ b/node_modules/react-native-keyboard-tracking-view/src/KeyboardTrackingView.ios.js @@ -25,6 +25,24 @@ export default class KeyboardTrackingView extends PureComponent { return {}; } + resetScrollView(scrollViewNativeID) { + if (this.ref && KeyboardTrackingViewManager && KeyboardTrackingViewManager.resetScrollView) { + KeyboardTrackingViewManager.resetScrollView(ReactNative.findNodeHandle(this.ref), scrollViewNativeID); + } + } + + pauseTracking(scrollViewNativeID) { + if (this.ref && KeyboardTrackingViewManager && KeyboardTrackingViewManager.pauseTracking) { + KeyboardTrackingViewManager.pauseTracking(ReactNative.findNodeHandle(this.ref), scrollViewNativeID); + } + } + + resumeTracking(scrollViewNativeID) { + if (this.ref && KeyboardTrackingViewManager && KeyboardTrackingViewManager.resumeTracking) { + KeyboardTrackingViewManager.resumeTracking(ReactNative.findNodeHandle(this.ref), scrollViewNativeID); + } + } + scrollToStart() { if (this.ref && KeyboardTrackingViewManager && KeyboardTrackingViewManager.scrollToStart) { KeyboardTrackingViewManager.scrollToStart(ReactNative.findNodeHandle(this.ref));