From aa62a5e4e016b67dc3f7dc902a18edbc88c867c4 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Thu, 3 Sep 2015 03:36:32 -0700 Subject: [PATCH] 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. --- Examples/UIExplorer/LayoutEventsExample.js | 6 ++-- Libraries/Components/View/View.js | 4 +++ React/Modules/RCTUIManager.m | 36 +++++++++------------- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/Examples/UIExplorer/LayoutEventsExample.js b/Examples/UIExplorer/LayoutEventsExample.js index 3407be48a..4dec90f8f 100644 --- a/Examples/UIExplorer/LayoutEventsExample.js +++ b/Examples/UIExplorer/LayoutEventsExample.js @@ -85,8 +85,7 @@ var LayoutEventExample = React.createClass({ return ( - onLayout events are called on mount and whenever layout is updated, - including after layout animations complete.{' '} + layout events are called on mount and whenever layout is recalculated. Note that the layout event will typically be received before the layout has updated on screen, especially when using layout animations.{' '} Press here to change layout. @@ -136,6 +135,9 @@ var styles = StyleSheet.create({ pressText: { fontWeight: 'bold', }, + italicText: { + fontStyle: 'italic', + }, }); exports.title = 'Layout Events'; diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js index fdb2ff6ed..d5235b5be 100644 --- a/Libraries/Components/View/View.js +++ b/Libraries/Components/View/View.js @@ -185,6 +185,10 @@ var View = React.createClass({ * Invoked on mount and layout changes with * * {nativeEvent: { layout: {x, y, width, height}}}. + * + * This event is fired immediately once the layout has been calculated, but + * the new layout may not yet be reflected on the screen at the time the + * event is received, especially if a layout animation is in progress. */ onLayout: PropTypes.func, diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 4969b0055..9acaff806 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -448,29 +448,12 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; NSMutableArray *areNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; NSMutableArray *parentsAreNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; - NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count]; for (RCTShadowView *shadowView in viewsWithNewFrames) { [frameReactTags addObject:shadowView.reactTag]; [frames addObject:[NSValue valueWithCGRect:shadowView.frame]]; [areNew addObject:@(shadowView.isNewView)]; [parentsAreNew addObject:@(shadowView.superview.isNewView)]; - - // TODO (#8214142): this can be greatly simplified by sending the layout - // event directly from the shadow thread, which may be better anyway. - id event = (id)kCFNull; - if (shadowView.onLayout) { - event = @{ - @"target": shadowView.reactTag, - @"layout": @{ - @"x": @(shadowView.frame.origin.x), - @"y": @(shadowView.frame.origin.y), - @"width": @(shadowView.frame.size.width), - @"height": @(shadowView.frame.size.height), - }, - }; - } - [onLayoutEvents addObject:event]; } for (RCTShadowView *shadowView in viewsWithNewFrames) { @@ -486,7 +469,20 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); for (RCTShadowView *shadowView in viewsWithNewFrames) { RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager]; RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView]; - if (block) [updateBlocks addObject:block]; + if (shadowView.onLayout) { + CGRect frame = shadowView.frame; + shadowView.onLayout(@{ + @"layout": @{ + @"x": @(frame.origin.x), + @"y": @(frame.origin.y), + @"width": @(frame.size.width), + @"height": @(frame.size.height), + }, + }); + } + if (block) { + [updateBlocks addObject:block]; + } } // Perform layout (possibly animated) @@ -497,7 +493,6 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); NSNumber *reactTag = frameReactTags[ii]; UIView *view = viewRegistry[reactTag]; CGRect frame = [frames[ii] CGRectValue]; - id event = onLayoutEvents[ii]; BOOL isNew = [areNew[ii] boolValue]; RCTAnimation *updateAnimation = isNew ? nil : _layoutAnimation.updateAnimation; @@ -506,9 +501,6 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls); void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; - if (event != (id)kCFNull) { - [self.bridge.eventDispatcher sendInputEventWithName:@"layout" body:event]; - } if (callback && completionsCalled == frames.count - 1) { callback(@[@(finished)]); }