Skip to content

Commit

Permalink
[rn] Keep native ListView child frames in sync on JS wrapper
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
martinbigio committed Jul 7, 2015
1 parent b57a14d commit 66d3f3c
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 21 deletions.
17 changes: 14 additions & 3 deletions Libraries/CustomComponents/ListView/ListView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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) {
Expand All @@ -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 ||
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 &&
Expand Down
40 changes: 22 additions & 18 deletions React/Views/RCTScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

@end

@interface RCTScrollView (Private)
- (NSArray *)calculateChildFramesData;
@end

@implementation RCTScrollView
{
RCTEventDispatcher *_eventDispatcher;
Expand Down Expand Up @@ -533,6 +537,23 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView
(_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) {
Expand All @@ -558,26 +579,9 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView
@"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
Expand Down
23 changes: 23 additions & 0 deletions React/Views/RCTScrollViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#import "RCTSparseArray.h"
#import "RCTUIManager.h"

@interface RCTScrollView (Private)
- (NSArray *)calculateChildFramesData;
@end

@implementation RCTConvert (UIScrollView)

RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
Expand Down Expand Up @@ -91,4 +95,23 @@ - (NSDictionary *)constantsToExport
}];
}

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

2 comments on commit 66d3f3c

@paramaggarwal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Nice!

@booxood
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😂 NICE!!!

Please sign in to comment.