[Yoga] Refine the handling of measurement functions when Yoga is used. (#408)

This ensures that measure funcs are not set for container / empty spacer
nodes, because Yoga has more internal capabilities than layoutThatFits:
knows about.

Avoid need for the main layout pass to directly call setup for measure
funcs, by making it an implicit part of .yogaLayoutInProgress =.

Tear down the measure func after the layout pass to avoid retain cycle.
This commit is contained in:
appleguy 2017-07-02 13:01:07 -07:00 committed by GitHub
parent fcb293e049
commit 1d1a3787c2
2 changed files with 11 additions and 16 deletions

View File

@ -40,7 +40,7 @@
- (void)setYogaChildren:(NSArray *)yogaChildren - (void)setYogaChildren:(NSArray *)yogaChildren
{ {
for (ASDisplayNode *child in _yogaChildren) { for (ASDisplayNode *child in [_yogaChildren copy]) {
// Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren // Make sure to un-associate the YGNodeRef tree before replacing _yogaChildren
// If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here. // If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here.
[self removeYogaChild:child]; [self removeYogaChild:child];
@ -68,14 +68,8 @@
// Clean up state in case this child had another parent. // Clean up state in case this child had another parent.
[self removeYogaChild:child]; [self removeYogaChild:child];
BOOL hadZeroChildren = (_yogaChildren.count == 0);
[_yogaChildren addObject:child]; [_yogaChildren addObject:child];
// Ensure any measure function is removed before inserting the YGNodeRef child.
if (hadZeroChildren) {
[self updateYogaMeasureFuncIfNeeded];
}
// YGNodeRef insertion is done in setParent: // YGNodeRef insertion is done in setParent:
child.yogaParent = self; child.yogaParent = self;
} }
@ -86,15 +80,10 @@
return; return;
} }
BOOL hadChildren = (_yogaChildren.count > 0);
[_yogaChildren removeObjectIdenticalTo:child]; [_yogaChildren removeObjectIdenticalTo:child];
// YGNodeRef removal is done in setParent: // YGNodeRef removal is done in setParent:
child.yogaParent = nil; child.yogaParent = nil;
// Ensure any measure function is re-added after removing the YGNodeRef child.
if (hadChildren && _yogaChildren.count == 0) {
[self updateYogaMeasureFuncIfNeeded];
}
} }
- (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute - (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute
@ -144,6 +133,7 @@
- (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress - (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress
{ {
setFlag(YogaLayoutInProgress, yogaLayoutInProgress); setFlag(YogaLayoutInProgress, yogaLayoutInProgress);
[self updateYogaMeasureFuncIfNeeded];
} }
- (BOOL)yogaLayoutInProgress - (BOOL)yogaLayoutInProgress
@ -184,8 +174,14 @@
{ {
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits: // Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
// This will be used for ASTextNode, as well as any other node that has no Yoga children // This will be used for ASTextNode, as well as any other node that has no Yoga children
id <ASLayoutElement> layoutElementToMeasure = (self.yogaChildren.count == 0 ? self : nil); BOOL isLeafNode = (self.yogaChildren.count == 0);
ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, layoutElementToMeasure); BOOL definesCustomLayout = [self implementsLayoutMethod];
// We set the measure func only during layout. Otherwise, a cycle is created:
// The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef.
BOOL shouldHaveMeasureFunc = (isLeafNode && definesCustomLayout && checkFlag(YogaLayoutInProgress));
ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil);
} }
- (void)invalidateCalculatedYogaLayout - (void)invalidateCalculatedYogaLayout
@ -214,7 +210,6 @@
// Prepare all children for the layout pass with the current Yoga tree configuration. // Prepare all children for the layout pass with the current Yoga tree configuration.
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
node.yogaLayoutInProgress = YES; node.yogaLayoutInProgress = YES;
[node updateYogaMeasureFuncIfNeeded];
}); });
if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) { if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) {