From 66d3f3c6165bdffcfae58d539660bfad1f9ce23e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 7 Jul 2015 07:38:24 -0700 Subject: [PATCH] [rn] Keep native ListView child frames in sync on JS wrapper Summary: At the moment the `ListView.js` `_childFrames` variable is only updated on scroll. As a consequence, `onChangeVisibleRows` won't get triggered for the initial render, nor any future render not trigered by scroll events. To fix this we need to make sure native and JS have the child frames in sync. --- .../CustomComponents/ListView/ListView.js | 17 ++++++-- React/Views/RCTScrollView.m | 40 ++++++++++--------- React/Views/RCTScrollViewManager.m | 23 +++++++++++ 3 files changed, 59 insertions(+), 21 deletions(-) diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js index cebfac68e..2cf967e50 100644 --- a/Libraries/CustomComponents/ListView/ListView.js +++ b/Libraries/CustomComponents/ListView/ListView.js @@ -29,6 +29,7 @@ var ListViewDataSource = require('ListViewDataSource'); var React = require('React'); var RCTUIManager = require('NativeModules').UIManager; +var RKScrollViewManager = require('NativeModules').ScrollViewManager; var ScrollView = require('ScrollView'); var ScrollResponder = require('ScrollResponder'); var StaticRenderer = require('StaticRenderer'); @@ -400,6 +401,13 @@ var ListView = React.createClass({ logError, this._setScrollVisibleHeight ); + + // RKScrollViewManager.calculateChildFrames not available on every platform + RKScrollViewManager && RKScrollViewManager.calculateChildFrames && + RKScrollViewManager.calculateChildFrames( + React.findNodeHandle(this.refs[SCROLLVIEW_REF]), + this._updateChildFrames, + ); }, _setScrollContentHeight: function(left, top, width, height) { @@ -412,6 +420,10 @@ var ListView = React.createClass({ this._renderMoreRowsIfNeeded(); }, + _updateChildFrames: function(childFrames) { + this._updateVisibleRows(childFrames); + }, + _renderMoreRowsIfNeeded: function() { if (this.scrollProperties.contentHeight === null || this.scrollProperties.visibleHeight === null || @@ -449,11 +461,10 @@ var ListView = React.createClass({ scrollProperties.offsetY; }, - _updateVisibleRows: function(e) { + _updateVisibleRows: function(updatedFrames) { if (!this.props.onChangeVisibleRows) { return; // No need to compute visible rows if there is no callback } - var updatedFrames = e && e.nativeEvent.updatedChildFrames; if (updatedFrames) { updatedFrames.forEach((newFrame) => { this._childFrames[newFrame.index] = merge(newFrame); @@ -522,7 +533,7 @@ var ListView = React.createClass({ this.scrollProperties.visibleHeight = e.nativeEvent.layoutMeasurement.height; this.scrollProperties.contentHeight = e.nativeEvent.contentSize.height; this.scrollProperties.offsetY = e.nativeEvent.contentOffset.y; - this._updateVisibleRows(e); + this._updateVisibleRows(e.nativeEvent.updatedChildFrames); var nearEnd = this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold; if (nearEnd && this.props.onEndReached && diff --git a/React/Views/RCTScrollView.m b/React/Views/RCTScrollView.m index 252867849..bc4db361e 100644 --- a/React/Views/RCTScrollView.m +++ b/React/Views/RCTScrollView.m @@ -357,6 +357,10 @@ RCT_NOT_IMPLEMENTED(-init) @end +@interface RCTScrollView (Private) +- (NSArray *)calculateChildFramesData; +@end + @implementation RCTScrollView { RCTEventDispatcher *_eventDispatcher; @@ -533,6 +537,23 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) (_scrollEventThrottle > 0 && _scrollEventThrottle < (now - _lastScrollDispatchTime))) { // Calculate changed frames + NSArray *childFrames = [self calculateChildFramesData]; + + // Dispatch event + [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove + reactTag:self.reactTag + scrollView:scrollView + userData:@{@"updatedChildFrames": childFrames}]; + + // Update dispatch time + _lastScrollDispatchTime = now; + _allowNextScrollNoMatterWhat = NO; + } + RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll:scrollView); +} + +- (NSArray *)calculateChildFramesData +{ NSMutableArray *updatedChildFrames = [[NSMutableArray alloc] init]; [[_contentView reactSubviews] enumerateObjectsUsingBlock: ^(UIView *subview, NSUInteger idx, __unused BOOL *stop) { @@ -558,26 +579,9 @@ RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove) @"height": @(newFrame.size.height), }]; } - }]; - // If there are new frames, add them to event data - NSDictionary *userData = nil; - if (updatedChildFrames.count > 0) { - userData = @{@"updatedChildFrames": updatedChildFrames}; - } - - // Dispatch event - [_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove - reactTag:self.reactTag - scrollView:scrollView - userData:userData]; - - // Update dispatch time - _lastScrollDispatchTime = now; - _allowNextScrollNoMatterWhat = NO; - } - RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll:scrollView); + return updatedChildFrames; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView diff --git a/React/Views/RCTScrollViewManager.m b/React/Views/RCTScrollViewManager.m index 6e74d1333..a83bf61da 100644 --- a/React/Views/RCTScrollViewManager.m +++ b/React/Views/RCTScrollViewManager.m @@ -14,6 +14,10 @@ #import "RCTSparseArray.h" #import "RCTUIManager.h" +@interface RCTScrollView (Private) +- (NSArray *)calculateChildFramesData; +@end + @implementation RCTConvert (UIScrollView) RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{ @@ -91,4 +95,23 @@ RCT_EXPORT_METHOD(getContentSize:(NSNumber *)reactTag }]; } +RCT_EXPORT_METHOD(calculateChildFrames:(NSNumber *)reactTag + callback:(RCTResponseSenderBlock)callback) +{ + [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + + UIView *view = viewRegistry[reactTag]; + if (!view) { + RCTLogError(@"Cannot find view with tag #%@", reactTag); + return; + } + + NSArray *childFrames = [((RCTScrollView *)view) calculateChildFramesData]; + + if (childFrames) { + callback(@[childFrames]); + } + }]; +} + @end