From ba170ec78c2ccfeef4047d529ad4c7e9095761cd Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Sun, 19 Feb 2017 23:05:42 -0800 Subject: [PATCH] Simplifying mess with RCTRootViewSizeFlexibility Summary: Now things look much more clear, I hope. This diff: * Introduces new property of `RCTRootShadowView` `availableSize` which represents exactly what we transmit to layout engine; * Illuminates conflict between logical `availableSize` and explicitly specified size of DOM node (current `size`); * Splits overcomplicated `setSize:forView:` method into two unrelated ones; * Changes actual values of `RCTRootViewSizeFlexibility` enum constants for simpler usage; * Completely removes `sizeFlexibility` concept from `RCTRootShadowView` (in favor of special values of `availableSize`); * Makes the code clearer finally. This is beginning of big effort to improve `RCTRootView` and co. Reviewed By: mmmulani Differential Revision: D4562834 fbshipit-source-id: f5baaf2859ea430d44645a6b5d35f222f15a668e --- React/Base/RCTRootView.h | 8 ++--- React/Base/RCTRootView.m | 41 +++++++++++++++++++--- React/Modules/RCTUIManager.h | 19 ++++++++++- React/Modules/RCTUIManager.m | 60 ++++++++++++++++----------------- React/Views/RCTRootShadowView.h | 6 ++-- React/Views/RCTRootShadowView.m | 25 +++----------- 6 files changed, 95 insertions(+), 64 deletions(-) diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h index aa9692b416..7f63baa9fd 100644 --- a/React/Base/RCTRootView.h +++ b/React/Base/RCTRootView.h @@ -21,10 +21,10 @@ * rootViewDidChangeIntrinsicSize method of the RCTRootViewDelegate will be called. */ typedef NS_ENUM(NSInteger, RCTRootViewSizeFlexibility) { - RCTRootViewSizeFlexibilityNone = 0, - RCTRootViewSizeFlexibilityWidth, - RCTRootViewSizeFlexibilityHeight, - RCTRootViewSizeFlexibilityWidthAndHeight, + RCTRootViewSizeFlexibilityNone = 0, + RCTRootViewSizeFlexibilityWidth = 1 << 0, + RCTRootViewSizeFlexibilityHeight = 1 << 1, + RCTRootViewSizeFlexibilityWidthAndHeight = RCTRootViewSizeFlexibilityWidth | RCTRootViewSizeFlexibilityHeight, }; /** diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index 24819026a1..cb96fdfaab 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -45,6 +45,7 @@ NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotificat @property (nonatomic, readonly) BOOL contentHasAppeared; @property (nonatomic, readonly, strong) RCTTouchHandler *touchHandler; @property (nonatomic, assign) BOOL passThroughTouches; +@property (nonatomic, assign) RCTRootViewSizeFlexibility sizeFlexibility; - (instancetype)initWithFrame:(CGRect)frame bridge:(RCTBridge *)bridge @@ -281,8 +282,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility { + if (_sizeFlexibility == sizeFlexibility) { + return; + } + _sizeFlexibility = sizeFlexibility; [self setNeedsLayout]; + _contentView.sizeFlexibility = _sizeFlexibility; } - (void)layoutSubviews @@ -383,9 +389,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) if ((self = [super initWithFrame:frame])) { _bridge = bridge; self.reactTag = reactTag; + _sizeFlexibility = sizeFlexibility; _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge]; [_touchHandler attachToView:self]; - [_bridge.uiManager registerRootView:self withSizeFlexibility:sizeFlexibility]; + [_bridge.uiManager registerRootView:self]; self.layer.backgroundColor = NULL; } return self; @@ -394,6 +401,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame:(CGRect)frame) RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder) +- (void)layoutSubviews +{ + [super layoutSubviews]; + [self updateAvailableSize]; +} + - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex { [super insertReactSubview:subview atIndex:atIndex]; @@ -407,12 +420,30 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder:(nonnull NSCoder *)aDecoder) }); } -- (void)setFrame:(CGRect)frame +- (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility { - super.frame = frame; - if (self.reactTag && _bridge.isValid) { - [_bridge.uiManager setSize:frame.size forView:self]; + if (_sizeFlexibility == sizeFlexibility) { + return; } + + _sizeFlexibility = sizeFlexibility; + [self setNeedsLayout]; +} + +- (void)updateAvailableSize +{ + if (!self.reactTag || !_bridge.isValid) { + return; + } + + CGSize size = self.bounds.size; + CGSize availableSize = + CGSizeMake( + _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? INFINITY : size.width, + _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? INFINITY : size.height + ); + + [_bridge.uiManager setAvailableSize:availableSize forRootView:self]; } - (void)setBackgroundColor:(UIColor *)backgroundColor diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index 0acabc6d64..14f866f754 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -58,7 +58,7 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; /** * Register a root view with the RCTUIManager. */ -- (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility; +- (void)registerRootView:(UIView *)rootView; /** * Gets the view name associated with a reactTag. @@ -70,6 +70,15 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ - (UIView *)viewForReactTag:(NSNumber *)reactTag; +/** + * Set the available size (`availableSize` property) for a root view. + * This might be used in response to changes in external layout constraints. + * This value will be directly trasmitted to layout engine and defines how big viewport is; + * this value does not affect root node size style properties. + * Can be considered as something similar to `setSize:forView:` but applicable only for root view. + */ +- (void)setAvailableSize:(CGSize)availableSize forRootView:(UIView *)rootView; + /** * Set the size of a view. This might be in response to a screen rotation * or some other layout event outside of the React-managed view hierarchy. @@ -149,6 +158,14 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; - (void)setFrame:(CGRect)frame forView:(UIView *)view __deprecated_msg("Use `setSize:forView:` or `setIntrinsicContentSize:forView:` instead."); + +/** + * This method is deprecated and will be removed in next releases. + * Use `registerRootView:` instead. There is no need to specify `sizeFlexibility` anymore. + */ +- (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility +__deprecated_msg("Use `registerRootView:` instead."); + @end /** diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 627e2e0f4e..f582cb6aea 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -376,7 +376,7 @@ dispatch_queue_t RCTGetUIManagerQueue(void) return RCTGetUIManagerQueue(); } -- (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility +- (void)registerRootView:(UIView *)rootView { RCTAssertMainQueue(); @@ -390,7 +390,6 @@ dispatch_queue_t RCTGetUIManagerQueue(void) // Register view _viewRegistry[reactTag] = rootView; - CGSize size = rootView.bounds.size; // Register shadow view dispatch_async(RCTGetUIManagerQueue(), ^{ @@ -400,10 +399,8 @@ dispatch_queue_t RCTGetUIManagerQueue(void) RCTRootShadowView *shadowView = [RCTRootShadowView new]; shadowView.reactTag = reactTag; - shadowView.size = size; shadowView.backgroundColor = rootView.backgroundColor; shadowView.viewName = NSStringFromClass([rootView class]); - shadowView.sizeFlexibility = sizeFlexibility; self->_shadowViewRegistry[shadowView.reactTag] = shadowView; [self->_rootViewTags addObject:reactTag]; }); @@ -425,44 +422,39 @@ dispatch_queue_t RCTGetUIManagerQueue(void) return _viewRegistry[reactTag]; } +- (void)setAvailableSize:(CGSize)availableSize forRootView:(UIView *)rootView +{ + RCTAssertMainQueue(); + NSNumber *reactTag = rootView.reactTag; + dispatch_async(RCTGetUIManagerQueue(), ^{ + RCTRootShadowView *shadowView = (RCTRootShadowView *)self->_shadowViewRegistry[reactTag]; + RCTAssert(shadowView != nil, @"Could not locate shadow view with tag #%@", reactTag); + RCTAssert([shadowView isKindOfClass:[RCTRootShadowView class]], @"Located shadow view (with tag #%@) is actually not root view.", reactTag); + + if (CGSizeEqualToSize(availableSize, shadowView.availableSize)) { + return; + } + + shadowView.availableSize = availableSize; + [self setNeedsLayout]; + }); +} + - (void)setSize:(CGSize)size forView:(UIView *)view { RCTAssertMainQueue(); - // The following variable has no meaning if the view is not a react root view - RCTRootViewSizeFlexibility sizeFlexibility = RCTRootViewSizeFlexibilityNone; - - if (RCTIsReactRootView(view.reactTag)) { - RCTRootView *rootView = (RCTRootView *)[view superview]; - if (rootView != nil) { - sizeFlexibility = rootView.sizeFlexibility; - } - } - NSNumber *reactTag = view.reactTag; dispatch_async(RCTGetUIManagerQueue(), ^{ RCTShadowView *shadowView = self->_shadowViewRegistry[reactTag]; RCTAssert(shadowView != nil, @"Could not locate shadow view with tag #%@", reactTag); - BOOL needsLayout = NO; - if (!CGSizeEqualToSize(size, shadowView.size)) { - shadowView.size = size; - needsLayout = YES; + if (CGSizeEqualToSize(size, shadowView.size)) { + return; } - // Trigger re-layout when size flexibility changes, as the root view might grow or - // shrink in the flexible dimensions. - if (RCTIsReactRootView(reactTag)) { - RCTRootShadowView *rootShadowView = (RCTRootShadowView *)shadowView; - if (rootShadowView.sizeFlexibility != sizeFlexibility) { - rootShadowView.sizeFlexibility = sizeFlexibility; - needsLayout = YES; - } - } - - if (needsLayout) { - [self setNeedsLayout]; - } + shadowView.size = size; + [self setNeedsLayout]; }); } @@ -1652,6 +1644,12 @@ static UIView *_jsResponder; @implementation RCTUIManager (Deprecated) +- (void)registerRootView:(UIView *)rootView withSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility +{ + RCTLogWarn(@"Calling of `[-RCTUIManager registerRootView:withSizeFlexibility:]` which is deprecated."); + [self registerRootView:rootView]; +} + - (void)setFrame:(CGRect)frame forView:(UIView *)view { RCTLogWarn(@"Calling of `[-RCTUIManager setFrame:forView:]` which is deprecated."); diff --git a/React/Views/RCTRootShadowView.h b/React/Views/RCTRootShadowView.h index bda4835ea7..95b96edf11 100644 --- a/React/Views/RCTRootShadowView.h +++ b/React/Views/RCTRootShadowView.h @@ -13,10 +13,10 @@ @interface RCTRootShadowView : RCTShadowView /** - * Size flexibility type used to find size constraints. - * Default to RCTRootViewSizeFlexibilityNone + * Available size to layout all views. + * Defaults to {INFINITY, INFINITY} */ -@property (nonatomic, assign) RCTRootViewSizeFlexibility sizeFlexibility; +@property (nonatomic, assign) CGSize availableSize; /** * Layout direction (LTR or RTL) inherited from native environment and diff --git a/React/Views/RCTRootShadowView.m b/React/Views/RCTRootShadowView.m index 0042259224..2c5a933c90 100644 --- a/React/Views/RCTRootShadowView.m +++ b/React/Views/RCTRootShadowView.m @@ -22,33 +22,18 @@ self = [super init]; if (self) { _baseDirection = [[RCTI18nUtil sharedInstance] isRTL] ? YGDirectionRTL : YGDirectionLTR; + _availableSize = CGSizeMake(INFINITY, INFINITY); } return self; } -- (void)applySizeConstraints -{ - switch (_sizeFlexibility) { - case RCTRootViewSizeFlexibilityNone: - break; - case RCTRootViewSizeFlexibilityWidth: - YGNodeStyleSetWidth(self.cssNode, YGUndefined); - break; - case RCTRootViewSizeFlexibilityHeight: - YGNodeStyleSetHeight(self.cssNode, YGUndefined); - break; - case RCTRootViewSizeFlexibilityWidthAndHeight: - YGNodeStyleSetWidth(self.cssNode, YGUndefined); - YGNodeStyleSetHeight(self.cssNode, YGUndefined); - break; - } -} - - (NSSet *)collectViewsWithUpdatedFrames { - [self applySizeConstraints]; + // Treating `INFINITY` as `YGUndefined` (which equals `NAN`). + float availableWidth = _availableSize.width == INFINITY ? YGUndefined : _availableSize.width; + float availableHeight = _availableSize.height == INFINITY ? YGUndefined : _availableSize.height; - YGNodeCalculateLayout(self.cssNode, YGUndefined, YGUndefined, _baseDirection); + YGNodeCalculateLayout(self.cssNode, availableWidth, availableHeight, _baseDirection); NSMutableSet *viewsWithNewFrame = [NSMutableSet set]; [self applyLayoutNode:self.cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero];