[Yoga] Initial commit for supporting Yoga-powered layout calculation. (#2982)

* [Yoga + AsyncDisplayKit] Initial commit for supporting Yoga-powered layout calculation.

Because this results in ASLayout objects, it preserve support for automaticallyManagesSubnodes
as well as the animated transition system. More work remains to vet performance of the new mode,
and it will remain in +Beta for the forseeable future.

I'm not sure that this should ever be used as the primary ASDK layout system, but it should remain an
option for some apps to experiment with if they require an implementation that more strictly mirrors
W3C standard Flexbox.

* [Yoga] Improve usage of ASHierarchyState to ensure simultaneous yoga layouts can't happen.

* [Yoga] Strictly minimize the impact of the Yoga integration on existing code.

Created new file ASDisplayNode+Yoga.mm, reduced size and number of integration points in core code.

* [Yoga] Figured out how to further reduce ASDisplayNode.mm impact by allocating _yogaNode in property accessor, and changing all accesses to use the property.
This commit is contained in:
appleguy
2017-02-09 16:10:29 -08:00
committed by Adlai Holler
parent 5c8818d107
commit f91265757e
14 changed files with 585 additions and 34 deletions

View File

@@ -44,6 +44,7 @@ Pod::Spec.new do |spec|
end
spec.subspec 'PINRemoteImage' do |pin|
# Note: The core.prefix_header_file includes setup of PIN_REMOTE_IMAGE, so the line below could be removed.
pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' }
pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.7'
pin.dependency 'PINRemoteImage/PINCache'
@@ -51,11 +52,17 @@ Pod::Spec.new do |spec|
end
spec.subspec 'IGListKit' do |igl|
# Note: The core.prefix_header_file includes setup of IG_LIST_KIT, so the line below could be removed.
igl.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) IG_LIST_KIT=1' }
igl.dependency 'IGListKit', '2.1.0'
igl.dependency 'AsyncDisplayKit/Core'
end
spec.subspec 'Yoga' do |yoga|
yoga.dependency 'Yoga', '1.0.2'
yoga.dependency 'AsyncDisplayKit/Core'
end
# Include optional PINRemoteImage module
spec.default_subspec = 'PINRemoteImage'

View File

@@ -177,6 +177,7 @@
8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */; };
8BDA5FC71CDBDF91007D13B2 /* ASVideoPlayerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */; };
90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */ = {isa = PBXBuildFile; fileRef = 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */; };
92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; };
92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -613,6 +614,7 @@
8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDefaultPlaybackButton.m; sourceTree = "<group>"; };
8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASVideoPlayerNode.h; sourceTree = "<group>"; };
8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASVideoPlayerNode.mm; sourceTree = "<group>"; };
90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASDisplayNode+Yoga.mm"; sourceTree = "<group>"; };
92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = "<group>"; };
92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = "<group>"; };
92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
@@ -893,6 +895,7 @@
058D09D8195D050800B7D73C /* ASDisplayNode.h */,
058D09D9195D050800B7D73C /* ASDisplayNode.mm */,
68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */,
90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */,
683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */,
058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */,
058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */,
@@ -1902,6 +1905,7 @@
34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */,
254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */,
254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */,
90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */,
509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */,
B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */,
8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */,

View File

@@ -8,6 +8,7 @@
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <AsyncDisplayKit/ASAvailability.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import <AsyncDisplayKit/ASEventLog.h>
@@ -20,15 +21,15 @@ void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORI
ASDISPLAYNODE_EXTERN_C_END
#if ASEVENTLOG_ENABLE
#define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__]
#define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:(AS_SAVE_EVENT_BACKTRACES ? [NSThread callStackSymbols] : nil) format:__VA_ARGS__]
#else
#define ASDisplayNodeLogEvent(node, ...)
#define ASDisplayNodeLogEvent(node, ...)
#endif
#if ASEVENTLOG_ENABLE
#define ASDisplayNodeGetEventLog(node) node.eventLog
#define ASDisplayNodeGetEventLog(node) node.eventLog
#else
#define ASDisplayNodeGetEventLog(node) nil
#define ASDisplayNodeGetEventLog(node) nil
#endif
/**
@@ -147,4 +148,38 @@ typedef struct {
@end
#pragma mark - Yoga Layout Support
#if YOGA
@interface ASDisplayNode (Yoga)
@property (nonatomic, strong) NSArray *yogaChildren;
- (void)addYogaChild:(ASDisplayNode *)child;
- (void)removeYogaChild:(ASDisplayNode *)child;
// This method should not normally be called directly.
- (ASLayout *)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize;
@end
@interface ASLayoutElementStyle (Yoga)
@property (nonatomic, assign, readwrite) ASStackLayoutDirection direction;
@property (nonatomic, assign, readwrite) CGFloat spacing;
@property (nonatomic, assign, readwrite) ASStackLayoutJustifyContent justifyContent;
@property (nonatomic, assign, readwrite) ASStackLayoutAlignItems alignItems;
@property (nonatomic, assign, readwrite) YGPositionType positionType;
@property (nonatomic, assign, readwrite) ASEdgeInsets position;
@property (nonatomic, assign, readwrite) ASEdgeInsets margin;
@property (nonatomic, assign, readwrite) ASEdgeInsets padding;
@property (nonatomic, assign, readwrite) ASEdgeInsets border;
@property (nonatomic, assign, readwrite) CGFloat aspectRatio;
@property (nonatomic, assign, readwrite) YGWrap flexWrap;
@end
#endif
NS_ASSUME_NONNULL_END

View File

@@ -232,8 +232,8 @@ NS_ASSUME_NONNULL_BEGIN
/** @name Observing node state changes */
/**
* Declare <ASInterfaceState> methods as requiring super calls (this can't be required in the protocol).
* For descriptions, see <ASInterfaceState> definition.
* Declare <ASInterfaceStateDelegate> methods as requiring super calls (this can't be required in the protocol).
* For descriptions, see <ASInterfaceStateDelegate> definition.
*/
- (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER;

View File

@@ -0,0 +1,407 @@
//
// ASDisplayNode+Yoga.mm
// AsyncDisplayKit
//
// Created by Scott Goodson on 2/8/17.
// Copyright © 2017 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/ASAvailability.h>
#if YOGA /* YOGA */
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASLayout.h>
// If Yoga support becomes a supported feature, move this traversal to ASDisplayNodeExtras.
void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node));
void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node))
{
if (node == nil) {
return;
}
block(node);
for (ASDisplayNode *child in [node yogaChildren]) {
ASDisplayNodePerformBlockOnEveryYogaChild(child, block);
}
}
#pragma mark - Yoga Type Conversion Helpers
YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems);
YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent);
YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf);
YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction);
float yogaFloatForCGFloat(CGFloat value);
float yogaDimensionToPoints(ASDimension dimension);
float yogaDimensionToPercent(ASDimension dimension);
ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets);
YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode,
float width, YGMeasureMode widthMode,
float height, YGMeasureMode heightMode);
#define YGNODE_STYLE_SET_DIMENSION(yogaNode, property, dimension) \
if (dimension.unit == ASDimensionUnitPoints) { \
YGNodeStyleSet##property(yogaNode, yogaDimensionToPoints(dimension)); \
} else if (dimension.unit == ASDimensionUnitFraction) { \
YGNodeStyleSet##property##Percent(yogaNode, yogaDimensionToPercent(dimension)); \
} else { \
YGNodeStyleSet##property(yogaNode, YGUndefined); \
}\
#define YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, property, dimension, edge) \
if (dimension.unit == ASDimensionUnitPoints) { \
YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \
} else if (dimension.unit == ASDimensionUnitFraction) { \
YGNodeStyleSet##property##Percent(yogaNode, edge, yogaDimensionToPercent(dimension)); \
} else { \
YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \
} \
#define YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, property, dimension, edge) \
if (dimension.unit == ASDimensionUnitPoints) { \
YGNodeStyleSet##property(yogaNode, edge, yogaDimensionToPoints(dimension)); \
} else if (dimension.unit == ASDimensionUnitFraction) { \
ASDisplayNodeAssert(NO, @"Unexpected Fraction value in applying ##property## values to YGNode"); \
} else { \
YGNodeStyleSet##property(yogaNode, edge, YGUndefined); \
} \
YGAlign yogaAlignItems(ASStackLayoutAlignItems alignItems)
{
switch (alignItems) {
case ASStackLayoutAlignItemsStart: return YGAlignFlexStart;
case ASStackLayoutAlignItemsEnd: return YGAlignFlexEnd;
case ASStackLayoutAlignItemsCenter: return YGAlignCenter;
case ASStackLayoutAlignItemsStretch: return YGAlignStretch;
case ASStackLayoutAlignItemsBaselineFirst: return YGAlignBaseline;
// FIXME: WARNING, Yoga does not currently support last-baseline item alignment.
case ASStackLayoutAlignItemsBaselineLast: return YGAlignBaseline;
}
}
YGJustify yogaJustifyContent(ASStackLayoutJustifyContent justifyContent)
{
switch (justifyContent) {
case ASStackLayoutJustifyContentStart: return YGJustifyFlexStart;
case ASStackLayoutJustifyContentCenter: return YGJustifyCenter;
case ASStackLayoutJustifyContentEnd: return YGJustifyFlexEnd;
case ASStackLayoutJustifyContentSpaceBetween: return YGJustifySpaceBetween;
case ASStackLayoutJustifyContentSpaceAround: return YGJustifySpaceAround;
}
}
YGAlign yogaAlignSelf(ASStackLayoutAlignSelf alignSelf)
{
switch (alignSelf) {
case ASStackLayoutAlignSelfStart: return YGAlignFlexStart;
case ASStackLayoutAlignSelfCenter: return YGAlignCenter;
case ASStackLayoutAlignSelfEnd: return YGAlignFlexEnd;
case ASStackLayoutAlignSelfStretch: return YGAlignStretch;
case ASStackLayoutAlignSelfAuto: return YGAlignAuto;
}
}
YGFlexDirection yogaFlexDirection(ASStackLayoutDirection direction)
{
return direction == ASStackLayoutDirectionVertical ? YGFlexDirectionColumn : YGFlexDirectionRow;
}
float yogaFloatForCGFloat(CGFloat value)
{
if (value < CGFLOAT_MAX / 2) {
return value;
} else {
return YGUndefined;
}
}
float yogaDimensionToPoints(ASDimension dimension)
{
ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitPoints,
@"Dimensions should not be type Fraction for this method: %f", dimension.value);
return yogaFloatForCGFloat(dimension.value);
}
float yogaDimensionToPercent(ASDimension dimension)
{
ASDisplayNodeCAssert(dimension.unit == ASDimensionUnitFraction,
@"Dimensions should not be type Points for this method: %f", dimension.value);
return 100.0 * yogaFloatForCGFloat(dimension.value);
}
ASDimension dimensionForEdgeWithEdgeInsets(YGEdge edge, ASEdgeInsets insets)
{
switch (edge) {
case YGEdgeLeft: return insets.left;
case YGEdgeTop: return insets.top;
case YGEdgeRight: return insets.right;
case YGEdgeBottom: return insets.bottom;
default: ASDisplayNodeCAssert(NO, @"YGEdge other than ASEdgeInsets is not supported.");
return ASDimensionAuto;
}
}
YGSize ASLayoutElementYogaMeasureFunc(YGNodeRef yogaNode, float width, YGMeasureMode widthMode,
float height, YGMeasureMode heightMode)
{
id <ASLayoutElement> layoutElement = (__bridge id <ASLayoutElement>)YGNodeGetContext(yogaNode);
ASSizeRange sizeRange;
sizeRange.max = CGSizeMake(width, height);
sizeRange.min = sizeRange.max;
if (widthMode == YGMeasureModeAtMost) {
sizeRange.min.width = 0.0;
}
if (heightMode == YGMeasureModeAtMost) {
sizeRange.min.height = 0.0;
}
CGSize size = [[layoutElement layoutThatFits:sizeRange] size];
return (YGSize){ .width = (float)size.width, .height = (float)size.height };
}
#pragma mark - ASDisplayNode+Yoga
@interface ASDisplayNode (YogaInternal)
@property (nonatomic, weak) ASDisplayNode *yogaParent;
@property (nonatomic, assign) YGNodeRef yogaNode;
@end
@implementation ASDisplayNode (Yoga)
- (void)setYogaNode:(YGNodeRef)yogaNode
{
_yogaNode = yogaNode;
}
- (YGNodeRef)yogaNode
{
if (_yogaNode == NULL) {
_yogaNode = YGNodeNew();
}
return _yogaNode;
}
- (void)setYogaParent:(ASDisplayNode *)yogaParent
{
if (_yogaParent == yogaParent) {
return;
}
YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed.
YGNodeRef oldParentRef = YGNodeGetParent(yogaNode);
if (oldParentRef != NULL) {
YGNodeRemoveChild(oldParentRef, yogaNode);
}
_yogaParent = yogaParent;
if (yogaParent) {
self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled;
YGNodeRef newParentRef = yogaParent.yogaNode;
YGNodeInsertChild(newParentRef, yogaNode, YGNodeGetChildCount(newParentRef));
} else {
self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled;
}
}
- (ASDisplayNode *)yogaParent
{
return _yogaParent;
}
- (void)setYogaChildren:(NSArray *)yogaChildren
{
for (ASDisplayNode *child in _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.
[self removeYogaChild:child];
}
_yogaChildren = nil;
for (ASDisplayNode *child in yogaChildren) {
[self addYogaChild:child];
}
}
- (NSArray *)yogaChildren
{
return _yogaChildren;
}
- (void)addYogaChild:(ASDisplayNode *)child
{
if (child == nil) {
return;
}
if (_yogaChildren == nil) {
_yogaChildren = [NSMutableArray array];
}
// Clean up state in case this child had another parent.
[self removeYogaChild:child];
// YGNodeRef insertion is done in setParent:
child.yogaParent = self;
[_yogaChildren addObject:child];
self.hierarchyState |= ASHierarchyStateYogaLayoutEnabled;
}
- (void)removeYogaChild:(ASDisplayNode *)child
{
if (child == nil) {
return;
}
// YGNodeRef removal is done in setParent:
child.yogaParent = nil;
[_yogaChildren removeObjectIdenticalTo:child];
if (_yogaChildren.count == 0 && self.yogaParent == nil) {
self.hierarchyState &= ~ASHierarchyStateYogaLayoutEnabled;
}
}
- (ASLayout *)layoutTreeForYogaNode
{
YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed.
uint32_t childCount = YGNodeGetChildCount(yogaNode);
ASDisplayNodeAssert(childCount == self.yogaChildren.count,
@"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren);
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount];
for (ASDisplayNode *subnode in self.yogaChildren) {
[sublayouts addObject:[subnode layoutTreeForYogaNode]];
}
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));
CGPoint position = CGPointMake(YGNodeLayoutGetLeft(yogaNode), YGNodeLayoutGetTop(yogaNode));
ASDisplayNodeLogEvent(self, @"Yoga calculatedSize: %@", NSStringFromCGSize(size));
ASLayout *layout = [ASLayout layoutWithLayoutElement:self
size:size
position:position
sublayouts:sublayouts];
return layout;
}
- (void)setYogaMeasureFuncIfNeeded
{
// Manual size calculation via calculateSizeThatFits:
// This will be used for ASTextNode, as well as any other leaf node that has no layout spec.
if ((self.methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) == NO
&& self.layoutSpecBlock == NULL && self.yogaChildren.count == 0) {
YGNodeRef yogaNode = self.yogaNode; // Use property to assign Ref if needed.
YGNodeSetContext(yogaNode, (__bridge void *)self);
YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc);
}
}
- (ASLayout *)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize
{
if (ASHierarchyStateIncludesYogaLayoutMeasuring(self.hierarchyState)) {
ASDisplayNodeAssert(NO, @"A Yoga layout is being performed by a parent; children must not perform their own until it is done! %@", [self displayNodeRecursiveDescription]);
return [ASLayout layoutWithLayoutElement:self size:CGSizeZero];
}
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
node.hierarchyState |= ASHierarchyStateYogaLayoutMeasuring;
});
YGNodeRef rootYogaNode = self.yogaNode;
// Apply the constrainedSize as a base, known frame of reference.
// If the root node also has style.*Size set, these will be overridden below.
YGNodeStyleSetMinWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.width));
YGNodeStyleSetMinHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.min.height));
// TODO(appleguy); Is it sufficient to set only Width OR MaxWidth (+ same for height), or do we need all four?
YGNodeStyleSetMaxWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.width));
YGNodeStyleSetMaxHeight(rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.height));
YGNodeStyleSetWidth (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.width));
YGNodeStyleSetHeight (rootYogaNode, yogaFloatForCGFloat(rootConstrainedSize.max.height));
// TODO(appleguy): We need this on all containers, not just the root. Need to be able to check for "unset"
YGNodeStyleSetAlignItems(rootYogaNode, yogaAlignItems(self.style.alignItems));
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
ASLayoutElementStyle *style = node.style;
YGNodeRef yogaNode = node.yogaNode;
YGNodeStyleSetDirection (yogaNode, YGDirectionInherit);
YGNodeStyleSetFlexWrap (yogaNode, style.flexWrap);
YGNodeStyleSetFlexGrow (yogaNode, style.flexGrow);
YGNodeStyleSetFlexShrink (yogaNode, style.flexShrink);
YGNODE_STYLE_SET_DIMENSION (yogaNode, FlexBasis, style.flexBasis);
YGNodeStyleSetFlexDirection (yogaNode, yogaFlexDirection(style.direction));
YGNodeStyleSetAlignSelf (yogaNode, yogaAlignSelf(style.alignSelf));
YGNodeStyleSetAlignItems (yogaNode, yogaAlignItems(style.alignItems));
YGNodeStyleSetJustifyContent(yogaNode, yogaJustifyContent(style.justifyContent));
YGNodeStyleSetPositionType (yogaNode, style.positionType);
ASEdgeInsets position = style.position;
ASEdgeInsets margin = style.margin;
ASEdgeInsets padding = style.padding;
ASEdgeInsets border = style.border;
YGEdge edge = YGEdgeLeft;
for (int i = 0; i < 4; i++) {
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Position, dimensionForEdgeWithEdgeInsets(edge, position), edge);
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Margin, dimensionForEdgeWithEdgeInsets(edge, margin), edge);
YGNODE_STYLE_SET_DIMENSION_WITH_EDGE(yogaNode, Padding, dimensionForEdgeWithEdgeInsets(edge, padding), edge);
YGNODE_STYLE_SET_FLOAT_WITH_EDGE(yogaNode, Border, dimensionForEdgeWithEdgeInsets(edge, border), edge);
edge = (edge == YGEdgeLeft ? YGEdgeTop : (edge == YGEdgeTop ? YGEdgeRight : YGEdgeBottom));
}
CGFloat aspectRatio = style.aspectRatio;
if (aspectRatio > FLT_EPSILON && aspectRatio < CGFLOAT_MAX / 2.0) {
YGNodeStyleSetAspectRatio(yogaNode, aspectRatio);
}
// For the root node, we use rootConstrainedSize above. For children, consult the style for their size.
if (node != self) {
YGNODE_STYLE_SET_DIMENSION(yogaNode, Width, style.width);
YGNODE_STYLE_SET_DIMENSION(yogaNode, Height, style.height);
YGNODE_STYLE_SET_DIMENSION(yogaNode, MinWidth, style.minWidth);
YGNODE_STYLE_SET_DIMENSION(yogaNode, MinHeight, style.minHeight);
YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxWidth, style.maxWidth);
YGNODE_STYLE_SET_DIMENSION(yogaNode, MaxHeight, style.maxHeight);
}
[node setYogaMeasureFuncIfNeeded];
/* TODO(appleguy): STYLE SETTER METHODS LEFT TO IMPLEMENT
void YGNodeStyleSetFlexDirection(YGNodeRef node, YGFlexDirection flexDirection);
void YGNodeStyleSetOverflow(YGNodeRef node, YGOverflow overflow);
void YGNodeStyleSetFlex(YGNodeRef node, float flex);
*/
});
// It is crucial to use yogaFloat... to convert CGFLOAT_MAX into YGUndefined here.
YGNodeCalculateLayout(rootYogaNode,
yogaFloatForCGFloat(rootConstrainedSize.max.width),
yogaFloatForCGFloat(rootConstrainedSize.max.height),
YGDirectionInherit);
#if ASEVENTLOG_ENABLE
YGNodePrint(rootYogaNode, (YGPrintOptions)(YGPrintOptionsChildren | YGPrintOptionsStyle | YGPrintOptionsLayout));
#endif
ASLayout *yogaLayout = [self layoutTreeForYogaNode];
ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) {
node.hierarchyState &= ~ASHierarchyStateYogaLayoutMeasuring;
});
return yogaLayout;
}
@end
#endif /* YOGA */

View File

@@ -427,6 +427,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_subnodes = nil;
#if YOGA
if (_yogaNode != NULL) {
YGNodeFree(_yogaNode);
}
#endif
// TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway.
[self __setSupernode:nil];
}
@@ -1070,7 +1076,8 @@ ASLayoutElementFinalLayoutElementDefault
restrictedToSize:(ASLayoutElementSize)size
relativeToParentSize:(CGSize)parentSize
{
const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, ASLayoutElementSizeResolve(self.style.size, parentSize));
ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize);
const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize);
return [self calculateLayoutThatFits:resolvedRange];
}
@@ -1080,6 +1087,14 @@ ASLayoutElementFinalLayoutElementDefault
ASDN::MutexLocker l(__instanceLock__);
#if YOGA /* YOGA */
if (ASHierarchyStateIncludesYogaLayoutEnabled(_hierarchyState) == YES &&
ASHierarchyStateIncludesYogaLayoutMeasuring(_hierarchyState) == NO) {
ASDN::MutexUnlocker ul(__instanceLock__);
return [self calculateLayoutFromYogaRoot:constrainedSize];
}
#endif /* YOGA */
// Manual size calculation via calculateSizeThatFits:
if (((_methodOverrides & ASDisplayNodeMethodOverrideLayoutSpecThatFits) ||
(_layoutSpecBlock != NULL)) == NO) {
@@ -3189,11 +3204,9 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
// Entering layout pending state
} else {
// Leaving layout pending state, reset related properties
{
ASDN::MutexLocker l(__instanceLock__);
_pendingTransitionID = ASLayoutElementContextInvalidTransitionID;
_pendingLayoutTransition = nil;
}
ASDN::MutexLocker l(__instanceLock__);
_pendingTransitionID = ASLayoutElementContextInvalidTransitionID;
_pendingLayoutTransition = nil;
}
}

View File

@@ -8,16 +8,17 @@
#import <Foundation/Foundation.h>
#endif
// Some build systems (Cocoapods, Buck, Bazel, etc) will define these flags manually if the functionality
// is needed by the app. Carthage in particular, or if a user forgets to set the build flag, benefit from
// checking if each flag is not defined and then setting it to whether or not the header is accessible.
// CocoaPods has a preproceessor macro for PIN_REMOTE_IMAGE, if already defined, okay
#ifndef PIN_REMOTE_IMAGE
// For Carthage or manual builds, this will define PIN_REMOTE_IMAGE if the header is available in the
// search path e.g. they've dragged in the framework (technically this will not be able to detect if
// a user does not include the framework in the link binary with build step).
#define PIN_REMOTE_IMAGE __has_include(<PINRemoteImage/PINRemoteImage.h>)
#define PIN_REMOTE_IMAGE __has_include(<PINRemoteImage/PINRemoteImage.h>)
#endif
#ifndef IG_LIST_KIT
#define IG_LIST_KIT __has_include(<IGListKit/IGListKit.h>)
#define IG_LIST_KIT __has_include(<IGListKit/IGListKit.h>)
#endif

View File

@@ -191,6 +191,15 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutSize(AS
NSStringFromASDimension(size.height)];
}
#pragma mark - ASEdgeInsets
typedef struct {
ASDimension top;
ASDimension left;
ASDimension bottom;
ASDimension right;
} ASEdgeInsets;
#pragma mark - ASSizeRange
/**

View File

@@ -157,6 +157,8 @@ ASDISPLAYNODE_EXTERN_C_END
#pragma mark - Deprecated
#define ASLayoutable ASLayoutElement
/**
* @abstract Calculate a layout based on given size range.
*
@@ -259,7 +261,6 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty;
*/
@property (nonatomic, assign, readwrite) ASDimension maxWidth;
#pragma mark - ASLayoutElementStyleSizeHelpers
/**
@@ -325,7 +326,6 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty;
@end
#pragma mark - ASLayoutElementStylability
@protocol ASLayoutElementStylability

View File

@@ -10,13 +10,14 @@
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "ASDisplayNode+FrameworkPrivate.h"
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutElement.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <map>
#import <atomic>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
extern void ASLayoutElementPerformBlockOnEveryElement(id<ASLayoutElement> element, void(^block)(id<ASLayoutElement> element))
{
@@ -138,6 +139,20 @@ do {\
std::atomic<CGFloat> _ascender;
std::atomic<CGFloat> _descender;
std::atomic<CGPoint> _layoutPosition;
#if YOGA
std::atomic<ASStackLayoutDirection> _direction;
std::atomic<CGFloat> _spacing;
std::atomic<ASStackLayoutJustifyContent> _justifyContent;
std::atomic<ASStackLayoutAlignItems> _alignItems;
std::atomic<YGPositionType> _positionType;
std::atomic<ASEdgeInsets> _position;
std::atomic<ASEdgeInsets> _margin;
std::atomic<ASEdgeInsets> _padding;
std::atomic<ASEdgeInsets> _border;
std::atomic<CGFloat> _aspectRatio;
std::atomic<YGWrap> _flexWrap;
#endif
}
@dynamic width, height, minWidth, maxWidth, minHeight, maxHeight;
@@ -350,7 +365,6 @@ do {\
ASLayoutElementStyleCallDelegate(ASLayoutElementStyleMaxHeightProperty);
}
#pragma mark - ASStackLayoutElement
- (void)setSpacingBefore:(CGFloat)spacingBefore
@@ -573,6 +587,36 @@ do {\
return result;
}
#pragma mark - Yoga Flexbox Properties
#if YOGA
- (ASStackLayoutDirection)direction { return _direction.load(); }
- (CGFloat)spacing { return _spacing.load(); }
- (ASStackLayoutJustifyContent)justifyContent { return _justifyContent.load(); }
- (ASStackLayoutAlignItems)alignItems { return _alignItems.load(); }
- (YGPositionType)positionType { return _positionType.load(); }
- (ASEdgeInsets)position { return _position.load(); }
- (ASEdgeInsets)margin { return _margin.load(); }
- (ASEdgeInsets)padding { return _padding.load(); }
- (ASEdgeInsets)border { return _border.load(); }
- (CGFloat)aspectRatio { return _aspectRatio.load(); }
- (YGWrap)flexWrap { return _flexWrap.load(); }
- (void)setDirection:(ASStackLayoutDirection)direction { _direction.store(direction); }
- (void)setSpacing:(CGFloat)spacing { _spacing.store(spacing); }
- (void)setJustifyContent:(ASStackLayoutJustifyContent)justify { _justifyContent.store(justify); }
- (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { _alignItems.store(alignItems); }
- (void)setPositionType:(YGPositionType)positionType { _positionType.store(positionType); }
- (void)setPosition:(ASEdgeInsets)position { _position.store(position); }
- (void)setMargin:(ASEdgeInsets)margin { _margin.store(margin); }
- (void)setPadding:(ASEdgeInsets)padding { _padding.store(padding); }
- (void)setBorder:(ASEdgeInsets)border { _border.store(border); }
- (void)setAspectRatio:(CGFloat)aspectRatio { _aspectRatio.store(aspectRatio); }
- (void)setFlexWrap:(YGWrap)flexWrap { _flexWrap.store(flexWrap); }
#endif
#pragma mark Deprecated
#pragma clang diagnostic push

View File

@@ -49,7 +49,9 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
/** One of the supernodes of this node is performing a transition.
Any layout calculated during this state should not be applied immediately, but pending until later. */
ASHierarchyStateLayoutPending = 1 << 3,
ASHierarchyStateVisualizeLayout = 1 << 4
ASHierarchyStateYogaLayoutEnabled = 1 << 4,
ASHierarchyStateYogaLayoutMeasuring = 1 << 5,
ASHierarchyStateVisualizeLayout = 1 << 6
};
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState)
@@ -59,7 +61,17 @@ ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged);
return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged);
}
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesYogaLayoutMeasuring(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateYogaLayoutMeasuring) == ASHierarchyStateYogaLayoutMeasuring);
}
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesYogaLayoutEnabled(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateYogaLayoutEnabled) == ASHierarchyStateYogaLayoutEnabled);
}
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesVisualizeLayout(ASHierarchyState hierarchyState)

View File

@@ -15,14 +15,13 @@
#import <atomic>
#import <AsyncDisplayKit/ASDisplayNode.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/_ASTransitionContext.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
#import <AsyncDisplayKit/ASLayoutElement.h>
#import <AsyncDisplayKit/ASLayoutTransition.h>
#import <AsyncDisplayKit/ASThread.h>
#import <AsyncDisplayKit/_ASTransitionContext.h>
#import <AsyncDisplayKit/ASWeakSet.h>
#import <AsyncDisplayKit/ASDisplayNode+Beta.h>
NS_ASSUME_NONNULL_BEGIN
@protocol _ASDisplayLayerDelegate;
@@ -107,8 +106,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
@protected
ASDisplayNode * __weak _supernode;
NSMutableArray *_subnodes;
NSMutableArray<ASDisplayNode *> *_subnodes;
ASLayoutElementStyle *_style;
ASPrimitiveTraitCollection _primitiveTraitCollection;
@@ -179,6 +178,12 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
NSTimeInterval _layoutComputationTotalTime;
NSInteger _layoutComputationNumberOfPasses;
#if YOGA
YGNodeRef _yogaNode;
ASDisplayNode *_yogaParent;
NSMutableArray<ASDisplayNode *> *_yogaChildren;
#endif
#if TIME_DISPLAYNODE_OPS
@public
NSTimeInterval _debugTimeToCreateView;

View File

@@ -11,11 +11,11 @@
#import <CoreFoundation/CFBase.h>
#ifndef kCFCoreFoundationVersionNumber_iOS_9_0
#define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10
#define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10
#endif
#ifndef kCFCoreFoundationVersionNumber_iOS_10_0
#define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00
#define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00
#endif
#define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0)
@@ -24,6 +24,20 @@
#define AS_TARGET_OS_OSX (!(TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH))
#define AS_TARGET_OS_IOS TARGET_OS_IPHONE
#ifndef YOGA_HEADER_PATH
#define YOGA_HEADER_PATH <Yoga/Yoga.h>
#endif
#ifndef YOGA
#define YOGA __has_include(YOGA_HEADER_PATH)
#endif
// If Yoga is available, make it available anywhere we use ASAvailability.
// This reduces Yoga-specific code in other files.
#if YOGA
#import YOGA_HEADER_PATH
#endif
#if AS_TARGET_OS_OSX
#define UIEdgeInsets NSEdgeInsets
@@ -65,5 +79,4 @@
}
@end
#endif

View File

@@ -1,5 +1,6 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
target 'Sample' do
pod 'AsyncDisplayKit', :path => '../..'
pod 'AsyncDisplayKit', :path => '../..'
pod 'AsyncDisplayKit/Yoga', :path => '../..'
end