diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e9aa51e544..cadfc1ed66 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -233,6 +233,8 @@ 9C3061091B857EC400D0530B /* ASBaselineLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C3061051B857EC400D0530B /* ASBaselineLayoutSpec.mm */; }; 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */; }; AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -580,6 +582,7 @@ 9C3061041B857EC400D0530B /* ASBaselineLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBaselineLayoutSpec.h; path = AsyncDisplayKit/Layout/ASBaselineLayoutSpec.h; sourceTree = ""; }; 9C3061051B857EC400D0530B /* ASBaselineLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASBaselineLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASBaselineLayoutSpec.mm; sourceTree = ""; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; + 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStaticLayoutable.h; path = AsyncDisplayKit/Layout/ASStaticLayoutable.h; sourceTree = ""; }; 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionViewTests.m; sourceTree = ""; }; AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutDefines.h; path = AsyncDisplayKit/Layout/ASStackLayoutDefines.h; sourceTree = ""; }; AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionView.h; sourceTree = ""; }; @@ -993,6 +996,7 @@ AC21EC0F1B3D0BF600C8B19A /* ASStackLayoutDefines.h */, ACF6ED161B17843500DA7C62 /* ASStackLayoutSpec.h */, ACF6ED171B17843500DA7C62 /* ASStackLayoutSpec.mm */, + 9C6BB3B01B8CC9C200F13F52 /* ASStaticLayoutable.h */, ACF6ED181B17843500DA7C62 /* ASStaticLayoutSpec.h */, ACF6ED191B17843500DA7C62 /* ASStaticLayoutSpec.mm */, 9C3061041B857EC400D0530B /* ASBaselineLayoutSpec.h */, @@ -1046,6 +1050,7 @@ ACF6ED201B17843500DA7C62 /* ASDimension.h in Headers */, ACF6ED2B1B17843500DA7C62 /* ASOverlayLayoutSpec.h in Headers */, ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */, + 9C6BB3B21B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */, ACF6ED2A1B17843500DA7C62 /* ASLayoutable.h in Headers */, ACF6ED311B17843500DA7C62 /* ASStaticLayoutSpec.h in Headers */, ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */, @@ -1161,6 +1166,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 9C6BB3B31B8CC9C200F13F52 /* ASStaticLayoutable.h in Headers */, B35062321B010EFD0018CF92 /* ASTextNodeShadower.h in Headers */, 34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */, B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */, diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 05698813f4..13ee5fdf00 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -677,6 +677,11 @@ static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possible return NO; } +- (id)finalLayoutable +{ + return self; +} + /** * NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is * disallowed in UIKit documentation and the behavior is left undefined. The output does not have a rigorously defined diff --git a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm index fd3cfce134..ea18aca990 100644 --- a/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm @@ -14,11 +14,9 @@ #import "ASBaseDefines.h" #import "ASLayout.h" +static NSString * const kBackgroundChildKey = @"kBackgroundChildKey"; + @interface ASBackgroundLayoutSpec () -{ - id _child; - id _background; -} @end @implementation ASBackgroundLayoutSpec @@ -30,12 +28,11 @@ } ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); - _child = child; - _background = background; + [self setChild:child]; + self.background = background; return self; } - + (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background; { return [[self alloc] initWithChild:child background:background]; @@ -46,12 +43,12 @@ */ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { - ASLayout *contentsLayout = [_child measureWithSizeRange:constrainedSize]; + ASLayout *contentsLayout = [[self child] measureWithSizeRange:constrainedSize]; NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:2]; - if (_background) { + if (self.background) { // Size background to exactly the same size. - ASLayout *backgroundLayout = [_background measureWithSizeRange:{contentsLayout.size, contentsLayout.size}]; + ASLayout *backgroundLayout = [self.background measureWithSizeRange:{contentsLayout.size, contentsLayout.size}]; backgroundLayout.position = CGPointZero; [sublayouts addObject:backgroundLayout]; } @@ -63,14 +60,12 @@ - (void)setBackground:(id)background { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _background = background; + [super setChild:background forIdentifier:kBackgroundChildKey]; } -- (void)setChild:(id)child +- (id)background { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _child = child; + return [super childForIdentifier:kBackgroundChildKey]; } @end diff --git a/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.h b/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.h index 8b784a56c3..46587d996b 100644 --- a/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.h @@ -42,9 +42,6 @@ typedef NS_ENUM(NSUInteger, ASBaselineLayoutBaselineAlignment) { /** The type of baseline alignment */ @property (nonatomic, assign) ASBaselineLayoutBaselineAlignment baselineAlignment; -- (void)addChild:(id)child; -- (void)addChildren:(NSArray *)children; - /** @param direction The direction of the stack view (horizontal or vertical) @param spacing The spacing between the children diff --git a/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.mm b/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.mm index 670c69c991..45fa687f8c 100644 --- a/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASBaselineLayoutSpec.mm @@ -27,7 +27,6 @@ @implementation ASBaselineLayoutSpec { - std::vector> _children; ASDN::RecursiveMutex _propertyLock; } @@ -47,10 +46,7 @@ _justifyContent = justifyContent; _baselineAlignment = baselineAlignment; - _children = std::vector>(); - for (id child in children) { - _children.push_back(child); - } + [self setChildren:children]; return self; } @@ -65,7 +61,12 @@ ASStackLayoutSpecStyle stackStyle = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems}; ASBaselineLayoutSpecStyle style = { .baselineAlignment = _baselineAlignment, .stackLayoutStyle = stackStyle }; - const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_children, stackStyle, constrainedSize); + std::vector> stackChildren = std::vector>(); + for (id child in self.children) { + stackChildren.push_back(child); + } + + const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, stackStyle, constrainedSize); const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, stackStyle, constrainedSize); const auto baselinePositionedLayout = ASBaselinePositionedLayout::compute(positionedLayout, style, constrainedSize); @@ -82,16 +83,19 @@ sublayouts:sublayouts]; } -- (void)addChild:(id)child +- (void)setChildren:(NSArray *)children { - _children.push_back(child); + [super setChildren:children]; +#if DEBUG + for (id child in children) { + NSAssert(([child finalLayoutable] == child && [child conformsToProtocol:@protocol(ASBaselineLayoutable)]) || ([child finalLayoutable] != child && [[child finalLayoutable] conformsToProtocol:@protocol(ASBaselineLayoutable)]), @"child must conform to ASBaselineLayoutable"); + } +#endif } -- (void)addChildren:(NSArray *)children +- (void)setChild:(id)child forIdentifier:(NSString *)identifier { - for (id child in children) { - [self addChild:child]; - } + ASDisplayNodeAssert(NO, @"ASBaselineLayoutSpec only supports setChildren"); } @end diff --git a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm index 1830895320..58dc55f962 100644 --- a/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASCenterLayoutSpec.mm @@ -17,7 +17,6 @@ { ASCenterLayoutSpecCenteringOptions _centeringOptions; ASCenterLayoutSpecSizingOptions _sizingOptions; - id _child; } - (instancetype)initWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions @@ -30,7 +29,7 @@ ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); _centeringOptions = centeringOptions; _sizingOptions = sizingOptions; - _child = child; + [self setChild:child]; return self; } @@ -41,12 +40,6 @@ return [[self alloc] initWithCenteringOptions:centeringOptions sizingOptions:sizingOptions child:child]; } -- (void)setChild:(id)child -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _child = child; -} - - (void)setCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); @@ -71,7 +64,7 @@ (_centeringOptions & ASCenterLayoutSpecCenteringX) != 0 ? 0 : constrainedSize.min.width, (_centeringOptions & ASCenterLayoutSpecCenteringY) != 0 ? 0 : constrainedSize.min.height, }; - ASLayout *sublayout = [_child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)]; + ASLayout *sublayout = [self.child measureWithSizeRange:ASSizeRangeMake(minChildSize, constrainedSize.max)]; // If we have an undetermined height or width, use the child size to define the layout // size diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm index 5ca96ab848..cc6ec9411a 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.mm @@ -19,7 +19,6 @@ @interface ASInsetLayoutSpec () { UIEdgeInsets _insets; - id _child; } @end @@ -50,7 +49,7 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner) } ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); _insets = insets; - _child = child; + [self setChild:child]; return self; } @@ -59,12 +58,6 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner) return [[self alloc] initWithInsets:insets child:child]; } -- (void)setChild:(id)child -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _child = child; -} - - (void)setInsets:(UIEdgeInsets)insets { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); @@ -95,7 +88,7 @@ static CGFloat centerInset(CGFloat outer, CGFloat inner) MAX(0, constrainedSize.max.height - insetsY), } }; - ASLayout *sublayout = [_child measureWithSizeRange:insetConstrainedSize]; + ASLayout *sublayout = [self.child measureWithSizeRange:insetConstrainedSize]; const CGSize computedSize = ASSizeRangeClamp(constrainedSize, { finite(sublayout.size.width + _insets.left + _insets.right, constrainedSize.max.width), diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h index bb200cbfe1..f599c84813 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -22,4 +22,13 @@ - (instancetype)init; +- (void)setChild:(id)child; +- (id)child; + +- (void)setChild:(id)child forIdentifier:(NSString *)identifier; +- (id)childForIdentifier:(NSString *)identifier; + +- (void)setChildren:(NSArray *)children; +- (NSArray *)children; + @end diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index ef09aa365e..58ecd114e9 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -16,6 +16,13 @@ #import "ASInternalHelpers.h" #import "ASLayout.h" +static NSString * const kDefaultChildKey = @"kDefaultChildKey"; +static NSString * const kDefaultChildrenKey = @"kDefaultChildrenKey"; + +@interface ASLayoutSpec() +@property (nonatomic, strong) NSMutableDictionary *layoutChildren; +@end + @implementation ASLayoutSpec @synthesize spacingBefore = _spacingBefore; @@ -24,12 +31,14 @@ @synthesize flexShrink = _flexShrink; @synthesize flexBasis = _flexBasis; @synthesize alignSelf = _alignSelf; +@synthesize layoutChildren = _layoutChildren; - (instancetype)init { if (!(self = [super init])) { return nil; } + _layoutChildren = [NSMutableDictionary dictionary]; _flexBasis = ASRelativeDimensionUnconstrained; _isMutable = YES; return self; @@ -42,4 +51,45 @@ return [ASLayout layoutWithLayoutableObject:self size:constrainedSize.min]; } +- (id)finalLayoutable +{ + return self; +} + +- (void)setChild:(id)child; +{ + [self setChild:child forIdentifier:kDefaultChildKey]; +} + +- (id)child +{ + return self.layoutChildren[kDefaultChildKey]; +} + +- (void)setChild:(id)child forIdentifier:(NSString *)identifier +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + self.layoutChildren[identifier] = [child finalLayoutable]; +} + +- (id)childForIdentifier:(NSString *)identifier +{ + return self.layoutChildren[identifier]; +} + +- (void)setChildren:(NSArray *)children +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + NSMutableArray *finalChildren = [NSMutableArray arrayWithCapacity:children.count]; + for (id child in children) { + [finalChildren addObject:[child finalLayoutable]]; + } + self.layoutChildren[kDefaultChildrenKey] = [NSArray arrayWithArray:finalChildren]; +} + +- (NSArray *)children +{ + return self.layoutChildren[kDefaultChildrenKey]; +} + @end diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 4ac01ee05f..4ff1ee030a 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -12,6 +12,7 @@ #import @class ASLayout; +@class ASLayoutSpec; /** * The ASLayoutable protocol declares a method for measuring the layout of an object. A class must implement the method @@ -29,4 +30,16 @@ */ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize; +/** + @abstract Give this object a last chance to add itself to a container ASLayoutable (most likely an ASLayoutSpec) before + being added to a ASLayoutSpec. + + For example, consider a node whose superclass is laid out via calculateLayoutThatFits:. The subclass cannot implement + layoutSpecThatFits: since its ASLayout is already being created by calculateLayoutThatFits:. By implementing this method + a subclass can wrap itself in an ASLayoutSpec right before it is added to a layout spec. + + It is rare that a class will need to implement this method. + */ +- (id)finalLayoutable; + @end diff --git a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm index f4f025573b..05c1c0c4ca 100644 --- a/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASOverlayLayoutSpec.mm @@ -14,11 +14,9 @@ #import "ASBaseDefines.h" #import "ASLayout.h" +static NSString * const kOverlayChildKey = @"kOverlayChildKey"; + @implementation ASOverlayLayoutSpec -{ - id _overlay; - id _child; -} - (instancetype)initWithChild:(id)child overlay:(id)overlay { @@ -26,8 +24,8 @@ return nil; } ASDisplayNodeAssertNotNil(child, @"Child that will be overlayed on shouldn't be nil"); - _overlay = overlay; - _child = child; + self.overlay = overlay; + [self setChild:child]; return self; } @@ -36,16 +34,14 @@ return [[self alloc] initWithChild:child overlay:overlay]; } -- (void)setChild:(id)child -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _child = child; -} - - (void)setOverlay:(id)overlay { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _overlay = overlay; + [super setChild:overlay forIdentifier:kOverlayChildKey]; +} + +- (id)overlay +{ + return [super childForIdentifier:kOverlayChildKey]; } /** @@ -56,8 +52,8 @@ ASLayout *contentsLayout = [_child measureWithSizeRange:constrainedSize]; contentsLayout.position = CGPointZero; NSMutableArray *sublayouts = [NSMutableArray arrayWithObject:contentsLayout]; - if (_overlay) { - ASLayout *overlayLayout = [_overlay measureWithSizeRange:{contentsLayout.size, contentsLayout.size}]; + if (self.overlay) { + ASLayout *overlayLayout = [self.overlay measureWithSizeRange:{contentsLayout.size, contentsLayout.size}]; overlayLayout.position = CGPointZero; [sublayouts addObject:overlayLayout]; } diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm index 5e5c6f0c66..64e9e2c118 100644 --- a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm @@ -22,7 +22,6 @@ @implementation ASRatioLayoutSpec { CGFloat _ratio; - id _child; } + (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child @@ -38,16 +37,10 @@ ASDisplayNodeAssertNotNil(child, @"Child cannot be nil"); ASDisplayNodeAssert(ratio > 0, @"Ratio should be strictly positive, but received %f", ratio); _ratio = ratio; - _child = child; + [self setChild:child]; return self; } -- (void)setChild:(id)child -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _child = child; -} - - (void)setRatio:(CGFloat)ratio { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); @@ -77,7 +70,7 @@ // If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through. const ASSizeRange childRange = (bestSize == sizeOptions.end()) ? constrainedSize : ASSizeRangeMake(*bestSize, *bestSize); - ASLayout *sublayout = [_child measureWithSizeRange:childRange]; + ASLayout *sublayout = [self.child measureWithSizeRange:childRange]; sublayout.position = CGPointZero; return [ASLayout layoutWithLayoutableObject:self size:sublayout.size sublayouts:@[sublayout]]; } diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index 7825a9b8fc..b5a126c5cb 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -55,7 +55,4 @@ */ + (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children; -- (void)addChild:(id)child; -- (void)addChildren:(NSArray *)children; - @end diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 3149979dd6..07243f30cc 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -23,9 +23,6 @@ #import "ASThread.h" @implementation ASStackLayoutSpec -{ - std::vector> _children; -} - (instancetype)init { @@ -47,26 +44,10 @@ _spacing = spacing; _justifyContent = justifyContent; - _children = std::vector>(); - for (id child in children) { - _children.push_back(child); - } + [self setChildren:children]; return self; } -- (void)addChild:(id)child -{ - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _children.push_back(child); -} - -- (void)addChildren:(NSArray *)children -{ - for (id child in children) { - [self addChild:child]; - } -} - - (void)setDirection:(ASStackLayoutDirection)direction { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); @@ -91,10 +72,32 @@ _spacing = spacing; } +- (void)setChildren:(NSArray *)children +{ + [super setChildren:children]; + +#if DEBUG + for (id child in children) { + ASDisplayNodeAssert(([child finalLayoutable] == child && [child conformsToProtocol:@protocol(ASStackLayoutable)]) || ([child finalLayoutable] != child && [[child finalLayoutable] conformsToProtocol:@protocol(ASStackLayoutable)]), @"child must conform to ASBaselineLayoutable"); + } +#endif +} + +- (void)setChild:(id)child forIdentifier:(NSString *)identifier +{ + ASDisplayNodeAssert(NO, @"ASStackLayoutSpec only supports setChildren"); +} + - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { ASStackLayoutSpecStyle style = {.direction = _direction, .spacing = _spacing, .justifyContent = _justifyContent, .alignItems = _alignItems}; - const auto unpositionedLayout = ASStackUnpositionedLayout::compute(_children, style, constrainedSize); + std::vector> stackChildren = std::vector>(); + for (id child in self.children) { + NSAssert([child conformsToProtocol:@protocol(ASStackLayoutable)], @"Child must implement ASStackLayoutable"); + stackChildren.push_back(child); + } + + const auto unpositionedLayout = ASStackUnpositionedLayout::compute(stackChildren, style, constrainedSize); const auto positionedLayout = ASStackPositionedLayout::compute(unpositionedLayout, style, constrainedSize); const CGSize finalSize = directionSize(style.direction, unpositionedLayout.stackDimensionSum, positionedLayout.crossSize); NSArray *sublayouts = [NSArray arrayWithObjects:&positionedLayout.sublayouts[0] count:positionedLayout.sublayouts.size()]; diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.h b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.h index 38ed2f06d6..8bd3f3d9f2 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.h @@ -11,43 +11,6 @@ #import #import -/** - * An ASStaticLayoutSpecChild object wraps an ASLayoutable object and provides position and size information, - * to be used as a child of an ASStaticLayoutSpec. - */ -@interface ASStaticLayoutSpecChild : NSObject - -@property (nonatomic, readonly) CGPoint position; -@property (nonatomic, readonly) id layoutableObject; - -/** - If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec. - */ -@property (nonatomic, readonly) ASRelativeSizeRange size; - -/** - * Initializer. - * - * @param position The position of this child within its parent spec. - * - * @param layoutableObject The backing ASLayoutable object of this child. - * - * @param size The size range that this child's size is trstricted according to. - */ -+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject size:(ASRelativeSizeRange)size; - -/** - * Convenience initializer with default size is Unconstrained in both dimensions, which sets the child's min size to zero - * and max size to the maximum available space it can consume without overflowing the spec's bounds. - * - * @param position The position of this child within its parent spec. - * - * @param layoutableObject The backing ASLayoutable object of this child. - */ -+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject; - -@end - /** * A layout spec that positions children at fixed positions. * @@ -56,10 +19,8 @@ @interface ASStaticLayoutSpec : ASLayoutSpec /** - @param children Children to be positioned at fixed positions, each is of type ASStaticLayoutSpecChild. + @param children Children to be positioned at fixed positions, each conforms to ASStaticLayoutable */ + (instancetype)staticLayoutSpecWithChildren:(NSArray *)children; -- (void)addChild:(ASStaticLayoutSpecChild *)child; - @end diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm index eca2637acd..585bf94660 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -13,31 +13,9 @@ #import "ASLayoutSpecUtilities.h" #import "ASInternalHelpers.h" #import "ASLayout.h" - -@implementation ASStaticLayoutSpecChild - -+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject size:(ASRelativeSizeRange)size; -{ - ASStaticLayoutSpecChild *c = [[super alloc] init]; - if (c) { - c->_position = position; - c->_layoutableObject = layoutableObject; - c->_size = size; - } - return c; -} - -+ (instancetype)staticLayoutChildWithPosition:(CGPoint)position layoutableObject:(id)layoutableObject -{ - return [self staticLayoutChildWithPosition:position layoutableObject:layoutableObject size:ASRelativeSizeRangeUnconstrained]; -} - -@end +#import "ASStaticLayoutable.h" @implementation ASStaticLayoutSpec -{ - NSArray *_children; -} + (instancetype)staticLayoutSpecWithChildren:(NSArray *)children { @@ -54,14 +32,19 @@ if (!(self = [super init])) { return nil; } - _children = children; + self.children = children; return self; } -- (void)addChild:(ASStaticLayoutSpecChild *)child +- (void)setChildren:(NSArray *)children { - ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _children = [_children arrayByAddingObject:child]; + [super setChildren:children]; + +#if DEBUG + for (id child in children) { + ASDisplayNodeAssert(([child finalLayoutable] == child && [child conformsToProtocol:@protocol(ASStaticLayoutable)]) || ([child finalLayoutable] != child && [[child finalLayoutable] conformsToProtocol:@protocol(ASStaticLayoutable)]), @"child must conform to ASStaticLayoutable"); + } +#endif } - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize @@ -71,16 +54,16 @@ constrainedSize.max.height }; - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:_children.count]; - for (ASStaticLayoutSpecChild *child in _children) { + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:self.children.count]; + for (id child in self.children) { CGSize autoMaxSize = { constrainedSize.max.width - child.position.x, constrainedSize.max.height - child.position.y }; - ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.size) + ASSizeRange childConstraint = ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRangeUnconstrained, child.sizeRange) ? ASSizeRangeMake({0, 0}, autoMaxSize) - : ASRelativeSizeRangeResolve(child.size, size); - ASLayout *sublayout = [child.layoutableObject measureWithSizeRange:childConstraint]; + : ASRelativeSizeRangeResolve(child.sizeRange, size); + ASLayout *sublayout = [child measureWithSizeRange:childConstraint]; sublayout.position = child.position; [sublayouts addObject:sublayout]; } diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutable.h b/AsyncDisplayKit/Layout/ASStaticLayoutable.h new file mode 100644 index 0000000000..5618fa6e9b --- /dev/null +++ b/AsyncDisplayKit/Layout/ASStaticLayoutable.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +#import +#import + +@protocol ASStaticLayoutable + +/** + If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec. + */ +@property (nonatomic, assign) ASRelativeSizeRange sizeRange; + +/** The position of this object within its parent spec. */ +@property (nonatomic, assign) CGPoint position; + +@end