diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index 8800aa6fc..3be5f15f6 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -397,9 +397,19 @@ - (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds // Use the last known constrainedSize passed from a parent during layout (if never, use bounds). NSUInteger version = _layoutVersion; ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass]; +#if YOGA + // This flag indicates to the Texture+Yoga code that this next layout is intended to be + // displayed (vs. just for measurement). This will cause it to call setNeedsLayout on any nodes + // whose layout changes as a result of the Yoga recalculation. This is necessary because a + // change in one Yoga node can change the layout for any other node in the tree. + self.willApplyNextYogaCalculatedLayout = YES; +#endif ASLayout *layout = [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:boundsSizeForLayout]; +#if YOGA + self.willApplyNextYogaCalculatedLayout = NO; +#endif nextLayout = ASDisplayNodeLayout(layout, constrainedSize, boundsSizeForLayout, version); // Now that the constrained size of pending layout might have been reused, the layout is useless // Release it and any orphaned subnodes it retains diff --git a/Source/ASDisplayNode+Yoga.h b/Source/ASDisplayNode+Yoga.h index 93cbf7a7c..ac6b92175 100644 --- a/Source/ASDisplayNode+Yoga.h +++ b/Source/ASDisplayNode+Yoga.h @@ -29,6 +29,7 @@ AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullab @property BOOL yogaLayoutInProgress; // TODO: Make this atomic (lock). @property (nullable, nonatomic) ASLayout *yogaCalculatedLayout; +@property (nonatomic) BOOL willApplyNextYogaCalculatedLayout; // Will walk up the Yoga tree and returns the root node - (ASDisplayNode *)yogaRoot; @@ -54,7 +55,7 @@ AS_EXTERN void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullab /// For internal usage only - (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize; /// For internal usage only -- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize; +- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize willApply:(BOOL)willApply; /// For internal usage only - (void)invalidateCalculatedYogaLayout; /** diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index c52eeb679..aefb236e2 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -166,6 +166,14 @@ - (ASLayout *)yogaCalculatedLayout return _yogaCalculatedLayout; } +- (BOOL)willApplyNextYogaCalculatedLayout { + return _willApplyNextYogaCalculatedLayout; +} + +- (void)setWillApplyNextYogaCalculatedLayout:(BOOL)willApplyNextYogaCalculatedLayout { + _willApplyNextYogaCalculatedLayout = willApplyNextYogaCalculatedLayout; +} + - (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress { setFlag(YogaLayoutInProgress, yogaLayoutInProgress); @@ -194,7 +202,7 @@ - (ASLayout *)layoutForYogaNode return [ASLayout layoutWithLayoutElement:self size:size position:position sublayouts:nil]; } -- (void)setupYogaCalculatedLayout +- (void)setupYogaCalculatedLayoutAndSetNeedsLayoutForChangedNodes:(BOOL)setNeedsLayoutForChangedNodes { ASScopedLockSelfOrToRoot(); @@ -231,6 +239,13 @@ - (void)setupYogaCalculatedLayout layout = [layout filteredNodeLayoutTree]; if ([self.yogaCalculatedLayout isEqual:layout] == NO) { + if (setNeedsLayoutForChangedNodes && !self.willApplyNextYogaCalculatedLayout) { + // This flag will be set when this layout is intended for immediate display. In this case, we + // want to ensure that we call setNeedsLayout on any other nodes. Note that we skip any nodes + // whose willApplyNextYogaCalculatedLayout flags are set, as those are the nodes that are + // already being laid out. + [self setNeedsLayout]; + } self.yogaCalculatedLayout = layout; } else { layout = self.yogaCalculatedLayout; @@ -320,7 +335,7 @@ - (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize if (self.yogaLayoutInProgress == NO) { ASYogaLog("Calculating yoga layout from root %@, %@", self, NSStringFromASSizeRange(constrainedSize)); - [self calculateLayoutFromYogaRoot:constrainedSize]; + [self calculateLayoutFromYogaRoot:constrainedSize willApply:self.willApplyNextYogaCalculatedLayout]; } else { ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout); } @@ -337,7 +352,7 @@ - (ASLayout *)calculateLayoutYoga:(ASSizeRange)constrainedSize return [self calculateLayoutLayoutSpec:constrainedSize]; } -- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize +- (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize willApply:(BOOL)willApply { ASScopedLockSet lockSet = [self lockToRootIfNeededForLayout]; ASDisplayNode *yogaRoot = self.yogaRoot; @@ -345,7 +360,7 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize if (self != yogaRoot) { ASYogaLog("ESCALATING to Yoga root: %@", self); // TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually. - [yogaRoot calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained]; + [yogaRoot calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained willApply:willApply]; return; } @@ -398,7 +413,7 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize }); ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { - [node setupYogaCalculatedLayout]; + [node setupYogaCalculatedLayoutAndSetNeedsLayoutForChangedNodes:willApply]; node.yogaLayoutInProgress = NO; }); diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 55cef7579..b20470c6c 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -164,6 +164,7 @@ AS_EXTERN NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimest NSMutableArray *_yogaChildren; __weak ASDisplayNode *_yogaParent; ASLayout *_yogaCalculatedLayout; + BOOL _willApplyNextYogaCalculatedLayout; #endif // Automatically manages subnodes