[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

@ -143,7 +143,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
{
NSString *string = NSStringFromClass([self class]);
if (_debugName) {
string = [string stringByAppendingString:[NSString stringWithFormat:@"\"%@\"", _debugName]];
string = [string stringByAppendingString:[NSString stringWithFormat:@"\"%@\"",_debugName]];
}
return string;
}

View File

@ -40,7 +40,7 @@
- (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
// If this becomes a performance bottleneck, it can be optimized by not doing the NSArray removals here.
[self removeYogaChild:child];
@ -68,14 +68,8 @@
// Clean up state in case this child had another parent.
[self removeYogaChild:child];
BOOL hadZeroChildren = (_yogaChildren.count == 0);
[_yogaChildren addObject:child];
// Ensure any measure function is removed before inserting the YGNodeRef child.
if (hadZeroChildren) {
[self updateYogaMeasureFuncIfNeeded];
}
// YGNodeRef insertion is done in setParent:
child.yogaParent = self;
}
@ -86,15 +80,10 @@
return;
}
BOOL hadChildren = (_yogaChildren.count > 0);
[_yogaChildren removeObjectIdenticalTo:child];
// YGNodeRef removal is done in setParent:
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
@ -144,6 +133,7 @@
- (void)setYogaLayoutInProgress:(BOOL)yogaLayoutInProgress
{
setFlag(YogaLayoutInProgress, yogaLayoutInProgress);
[self updateYogaMeasureFuncIfNeeded];
}
- (BOOL)yogaLayoutInProgress
@ -184,8 +174,14 @@
{
// Size calculation via calculateSizeThatFits: or layoutSpecThatFits:
// 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);
ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, layoutElementToMeasure);
BOOL isLeafNode = (self.yogaChildren.count == 0);
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
@ -214,7 +210,6 @@
// Prepare all children for the layout pass with the current Yoga tree configuration.
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
node.yogaLayoutInProgress = YES;
[node updateYogaMeasureFuncIfNeeded];
});
if (ASSizeRangeEqualToSizeRange(rootConstrainedSize, ASSizeRangeUnconstrained)) {