Skip to content

Commit aa62a5e

Browse files
committed
Send layout events on shadow queue
Summary: We currently wait until after views have been updated on the main thread before sending layout events. This means that any code that relies on those events to update the UI will lag the atual layout by at least one frame. This changes the RCTUIManager to send the event immediately after layout has occured on the shadow thread. This noticably improves the respinsiveness of the layout example in UIExplorer, which now updates the dimension labels immediately instead of waiting until after the layout animation has completed.
1 parent f28255e commit aa62a5e

3 files changed

Lines changed: 22 additions & 24 deletions

File tree

Examples/UIExplorer/LayoutEventsExample.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ var LayoutEventExample = React.createClass({
8585
return (
8686
<View style={this.state.containerStyle}>
8787
<Text>
88-
onLayout events are called on mount and whenever layout is updated,
89-
including after layout animations complete.{' '}
88+
layout events are called on mount and whenever layout is recalculated. Note that the layout event will typically be received <Text style={styles.italicText}>before</Text> the layout has updated on screen, especially when using layout animations.{' '}
9089
<Text style={styles.pressText} onPress={this.animateViewLayout}>
9190
Press here to change layout.
9291
</Text>
@@ -136,6 +135,9 @@ var styles = StyleSheet.create({
136135
pressText: {
137136
fontWeight: 'bold',
138137
},
138+
italicText: {
139+
fontStyle: 'italic',
140+
},
139141
});
140142

141143
exports.title = 'Layout Events';

Libraries/Components/View/View.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ var View = React.createClass({
185185
* Invoked on mount and layout changes with
186186
*
187187
* {nativeEvent: { layout: {x, y, width, height}}}.
188+
*
189+
* This event is fired immediately once the layout has been calculated, but
190+
* the new layout may not yet be reflected on the screen at the time the
191+
* event is received, especially if a layout animation is in progress.
188192
*/
189193
onLayout: PropTypes.func,
190194

React/Modules/RCTUIManager.m

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -448,29 +448,12 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
448448
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
449449
NSMutableArray *areNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
450450
NSMutableArray *parentsAreNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
451-
NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
452451

453452
for (RCTShadowView *shadowView in viewsWithNewFrames) {
454453
[frameReactTags addObject:shadowView.reactTag];
455454
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
456455
[areNew addObject:@(shadowView.isNewView)];
457456
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
458-
459-
// TODO (#8214142): this can be greatly simplified by sending the layout
460-
// event directly from the shadow thread, which may be better anyway.
461-
id event = (id)kCFNull;
462-
if (shadowView.onLayout) {
463-
event = @{
464-
@"target": shadowView.reactTag,
465-
@"layout": @{
466-
@"x": @(shadowView.frame.origin.x),
467-
@"y": @(shadowView.frame.origin.y),
468-
@"width": @(shadowView.frame.size.width),
469-
@"height": @(shadowView.frame.size.height),
470-
},
471-
};
472-
}
473-
[onLayoutEvents addObject:event];
474457
}
475458

476459
for (RCTShadowView *shadowView in viewsWithNewFrames) {
@@ -486,7 +469,20 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
486469
for (RCTShadowView *shadowView in viewsWithNewFrames) {
487470
RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
488471
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
489-
if (block) [updateBlocks addObject:block];
472+
if (shadowView.onLayout) {
473+
CGRect frame = shadowView.frame;
474+
shadowView.onLayout(@{
475+
@"layout": @{
476+
@"x": @(frame.origin.x),
477+
@"y": @(frame.origin.y),
478+
@"width": @(frame.size.width),
479+
@"height": @(frame.size.height),
480+
},
481+
});
482+
}
483+
if (block) {
484+
[updateBlocks addObject:block];
485+
}
490486
}
491487

492488
// Perform layout (possibly animated)
@@ -497,7 +493,6 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
497493
NSNumber *reactTag = frameReactTags[ii];
498494
UIView *view = viewRegistry[reactTag];
499495
CGRect frame = [frames[ii] CGRectValue];
500-
id event = onLayoutEvents[ii];
501496

502497
BOOL isNew = [areNew[ii] boolValue];
503498
RCTAnimation *updateAnimation = isNew ? nil : _layoutAnimation.updateAnimation;
@@ -506,9 +501,6 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)roo
506501

507502
void (^completion)(BOOL) = ^(BOOL finished) {
508503
completionsCalled++;
509-
if (event != (id)kCFNull) {
510-
[self.bridge.eventDispatcher sendInputEventWithName:@"layout" body:event];
511-
}
512504
if (callback && completionsCalled == frames.count - 1) {
513505
callback(@[@(finished)]);
514506
}

0 commit comments

Comments
 (0)