From 416d8f92e151ee9b8055b82571665279fe9be8ea Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 27 Sep 2016 13:50:36 -0700 Subject: [PATCH] [Layout] Add delegate to ASLayoutableStyle for property changes (#2283) * Add delegate to ASLayoutableStyle for property changes * Moving ASLayoutableStyle delegate to readonly * Add ASLayoutableStyleTests * Fix property stirngs * Fix video example * Address comments of Adlai --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 + AsyncDisplayKit/ASVideoPlayerNode.mm | 6 +- AsyncDisplayKit/Layout/ASLayoutable.h | 40 +++++- AsyncDisplayKit/Layout/ASLayoutable.mm | 116 +++++++++++++++++- AsyncDisplayKitTests/ASLayoutableStyleTests.m | 77 ++++++++++++ examples/Videos/Sample/ViewController.m | 8 +- 6 files changed, 238 insertions(+), 13 deletions(-) create mode 100644 AsyncDisplayKitTests/ASLayoutableStyleTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f1f46d1606..9ff09ca859 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -221,6 +221,7 @@ 69E1006F1CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; 69E100701CA89CB600D88C1B /* ASEnvironmentInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */; }; 69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 69FEE53D1D95A9AF0086F066 /* ASLayoutableStyleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 69FEE53C1D95A9AF0086F066 /* ASLayoutableStyleTests.m */; }; 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */ = {isa = PBXBuildFile; fileRef = AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */; settings = {ATTRIBUTES = (Public, ); }; }; 764D83D61C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */; }; @@ -996,6 +997,7 @@ 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEnvironmentInternal.h; sourceTree = ""; }; 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEnvironmentInternal.mm; sourceTree = ""; }; 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; + 69FEE53C1D95A9AF0086F066 /* ASLayoutableStyleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutableStyleTests.m; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+Debug.h"; sourceTree = ""; }; 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+Debug.m"; sourceTree = ""; }; @@ -1419,6 +1421,7 @@ 058D09C6195D04C000B7D73C /* Supporting Files */, 052EE06A1A15A0D8002C6279 /* TestResources */, 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, + 69FEE53C1D95A9AF0086F066 /* ASLayoutableStyleTests.m */, ); path = AsyncDisplayKitTests; sourceTree = ""; @@ -2251,6 +2254,7 @@ CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */, 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, 696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */, + 69FEE53D1D95A9AF0086F066 /* ASLayoutableStyleTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, CC54A81E1D7008B300296A24 /* ASDispatchTests.m in Sources */, 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */, diff --git a/AsyncDisplayKit/ASVideoPlayerNode.mm b/AsyncDisplayKit/ASVideoPlayerNode.mm index 3982cd8650..049b2fbb13 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.mm +++ b/AsyncDisplayKit/ASVideoPlayerNode.mm @@ -705,7 +705,7 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; ASDisplayNodeAssert(NO, @"Infinite width or height in ASVideoPlayerNode"); maxSize = CGSizeZero; } - _videoNode.style.size = ASLayoutableSizeMakeFromCGSize(maxSize); + [_videoNode.style setSizeWithCGSize:maxSize]; ASLayoutSpec *layoutSpec; if (_delegateFlags.delegateLayoutSpecForControls) { @@ -718,12 +718,12 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; if (_spinnerNode) { ASCenterLayoutSpec *centerLayoutSpec = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:_spinnerNode]; - centerLayoutSpec.style.size = ASLayoutableSizeMakeFromCGSize(maxSize); + [centerLayoutSpec.style setSizeWithCGSize:maxSize]; [children addObject:centerLayoutSpec]; } ASOverlayLayoutSpec *overlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_videoNode overlay:layoutSpec]; - overlaySpec.style.size = ASLayoutableSizeMakeFromCGSize(maxSize); + [overlaySpec.style setSizeWithCGSize:maxSize]; [children addObject:overlaySpec]; return [ASStaticLayoutSpec staticLayoutSpecWithChildren:children]; diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 563fcfe762..903b159fa9 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -150,18 +150,52 @@ NS_ASSUME_NONNULL_BEGIN @end + #pragma mark - ASLayoutableStyle +extern NSString * const ASLayoutableStyleWidthProperty; +extern NSString * const ASLayoutableStyleMinWidthProperty; +extern NSString * const ASLayoutableStyleMaxWidthProperty; + +extern NSString * const ASLayoutableStyleHeightProperty; +extern NSString * const ASLayoutableStyleMinHeightProperty; +extern NSString * const ASLayoutableStyleMaxHeightProperty; + +extern NSString * const ASLayoutableStyleSpacingBeforeProperty; +extern NSString * const ASLayoutableStyleSpacingAfterProperty; +extern NSString * const ASLayoutableStyleFlexGrowProperty; +extern NSString * const ASLayoutableStyleFlexShrinkProperty; +extern NSString * const ASLayoutableStyleFlexBasisProperty; +extern NSString * const ASLayoutableStyleAlignSelfProperty; +extern NSString * const ASLayoutableStyleAscenderProperty; +extern NSString * const ASLayoutableStyleDescenderProperty; + +extern NSString * const ASLayoutableStyleLayoutPositionProperty; + +@protocol ASLayoutableStyleDelegate +- (void)style:(__kindof ASLayoutableStyle *)style propertyDidChange:(NSString *)propertyName; +@end + @interface ASLayoutableStyle : NSObject +#pragma mark - Delegate -#pragma mark - Sizing +/** + * @abstract Initializes the layoutable style with a specified delegate + */ +- (instancetype)initWithDelegate:(id)delegate; + +/** + * @abstract The object that acts as the delegate of the style. + * + * @discussion The delegate must adopt the ASLayoutableStyleDelegate protocol. The delegate is not retained. + */ +@property (nullable, nonatomic, weak, readonly) id delegate; -// TODO: Move to internal method? /** * @abstract A size constraint that should apply to this ASLayoutable. */ -@property (nonatomic, assign, readwrite) ASLayoutableSize size; +@property (nonatomic, assign, readonly) ASLayoutableSize size; /** * @abstract The width property specifies the height of the content area of an ASLayoutable. diff --git a/AsyncDisplayKit/Layout/ASLayoutable.mm b/AsyncDisplayKit/Layout/ASLayoutable.mm index 1d31951210..266318886f 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.mm +++ b/AsyncDisplayKit/Layout/ASLayoutable.mm @@ -86,12 +86,52 @@ void ASLayoutableClearCurrentContext() #pragma mark - ASLayoutableStyle +NSString * const ASLayoutableStyleWidthProperty = @"ASLayoutableStyleWidthProperty"; +NSString * const ASLayoutableStyleMinWidthProperty = @"ASLayoutableStyleMinWidthProperty"; +NSString * const ASLayoutableStyleMaxWidthProperty = @"ASLayoutableStyleMaxWidthProperty"; + +NSString * const ASLayoutableStyleHeightProperty = @"ASLayoutableStyleHeightProperty"; +NSString * const ASLayoutableStyleMinHeightProperty = @"ASLayoutableStyleMinHeightProperty"; +NSString * const ASLayoutableStyleMaxHeightProperty = @"ASLayoutableStyleMaxHeightProperty"; + +NSString * const ASLayoutableStyleSpacingBeforeProperty = @"ASLayoutableStyleSpacingBeforeProperty"; +NSString * const ASLayoutableStyleSpacingAfterProperty = @"ASLayoutableStyleSpacingAfterProperty"; +NSString * const ASLayoutableStyleFlexGrowProperty = @"ASLayoutableStyleFlexGrowProperty"; +NSString * const ASLayoutableStyleFlexShrinkProperty = @"ASLayoutableStyleFlexShrinkProperty"; +NSString * const ASLayoutableStyleFlexBasisProperty = @"ASLayoutableStyleFlexBasisProperty"; +NSString * const ASLayoutableStyleAlignSelfProperty = @"ASLayoutableStyleAlignSelfProperty"; +NSString * const ASLayoutableStyleAscenderProperty = @"ASLayoutableStyleAscenderProperty"; +NSString * const ASLayoutableStyleDescenderProperty = @"ASLayoutableStyleDescenderProperty"; + +NSString * const ASLayoutableStyleLayoutPositionProperty = @"ASLayoutableStyleLayoutPositionProperty"; + +#define ASLayoutableStyleCallDelegate(propertyName)\ +do {\ + [_delegate style:self propertyDidChange:propertyName];\ +} while(0) + +@interface ASLayoutableStyle () +@property (nullable, nonatomic, weak) id delegate; +@end + @implementation ASLayoutableStyle { ASDN::RecursiveMutex __instanceLock__; + ASLayoutableSize _size; } @dynamic width, height, minWidth, maxWidth, minHeight, maxHeight; +#pragma mark - Lifecycle + +- (instancetype)initWithDelegate:(id)delegate +{ + self = [self init]; + if (self) { + _delegate = delegate; + } + return self; +} + - (instancetype)init { self = [super init]; @@ -101,7 +141,6 @@ void ASLayoutableClearCurrentContext() return self; } - #pragma mark - ASLayoutableSizeForwarding - (ASDimension)width @@ -114,6 +153,7 @@ void ASLayoutableClearCurrentContext() { ASDN::MutexLocker l(__instanceLock__); _size.width = width; + ASLayoutableStyleCallDelegate(ASLayoutableStyleWidthProperty); } - (ASDimension)height @@ -126,6 +166,7 @@ void ASLayoutableClearCurrentContext() { ASDN::MutexLocker l(__instanceLock__); _size.height = height; + ASLayoutableStyleCallDelegate(ASLayoutableStyleHeightProperty); } - (ASDimension)minWidth @@ -138,6 +179,7 @@ void ASLayoutableClearCurrentContext() { ASDN::MutexLocker l(__instanceLock__); _size.minWidth = minWidth; + ASLayoutableStyleCallDelegate(ASLayoutableStyleMinWidthProperty); } - (ASDimension)maxWidth @@ -150,6 +192,7 @@ void ASLayoutableClearCurrentContext() { ASDN::MutexLocker l(__instanceLock__); _size.maxWidth = maxWidth; + ASLayoutableStyleCallDelegate(ASLayoutableStyleMaxWidthProperty); } - (ASDimension)minHeight @@ -162,6 +205,7 @@ void ASLayoutableClearCurrentContext() { ASDN::MutexLocker l(__instanceLock__); _size.minHeight = minHeight; + ASLayoutableStyleCallDelegate(ASLayoutableStyleMinHeightProperty); } - (ASDimension)maxHeight @@ -174,10 +218,9 @@ void ASLayoutableClearCurrentContext() { ASDN::MutexLocker l(__instanceLock__); _size.maxHeight = maxHeight; + ASLayoutableStyleCallDelegate(ASLayoutableStyleMaxHeightProperty); } -#pragma mark - Layout measurement and sizing - - (void)setSizeWithCGSize:(CGSize)size { self.width = ASDimensionMakeWithPoints(size.width); @@ -192,4 +235,71 @@ void ASLayoutableClearCurrentContext() self.maxHeight = ASDimensionMakeWithPoints(size.height); } +#pragma mark - ASStackLayoutable + +- (void)setSpacingBefore:(CGFloat)spacingBefore +{ + ASDN::MutexLocker l(__instanceLock__); + _spacingBefore = spacingBefore; + ASLayoutableStyleCallDelegate(ASLayoutableStyleSpacingBeforeProperty); +} + +- (void)setSpacingAfter:(CGFloat)spacingAfter +{ + ASDN::MutexLocker l(__instanceLock__); + _spacingAfter = spacingAfter; + ASLayoutableStyleCallDelegate(ASLayoutableStyleSpacingAfterProperty); +} + +- (void)setFlexGrow:(BOOL)flexGrow +{ + ASDN::MutexLocker l(__instanceLock__); + _flexGrow = flexGrow; + ASLayoutableStyleCallDelegate(ASLayoutableStyleFlexGrowProperty); +} + +- (void)setFlexShrink:(BOOL)flexShrink +{ + ASDN::MutexLocker l(__instanceLock__); + _flexShrink = flexShrink; + ASLayoutableStyleCallDelegate(ASLayoutableStyleFlexShrinkProperty); +} + +- (void)setFlexBasis:(ASDimension)flexBasis +{ + ASDN::MutexLocker l(__instanceLock__); + _flexBasis = flexBasis; + ASLayoutableStyleCallDelegate(ASLayoutableStyleFlexBasisProperty); +} + +- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf +{ + ASDN::MutexLocker l(__instanceLock__); + _alignSelf = alignSelf; + ASLayoutableStyleCallDelegate(ASLayoutableStyleAlignSelfProperty); +} + +- (void)setAscender:(CGFloat)ascender +{ + ASDN::MutexLocker l(__instanceLock__); + _ascender = ascender; + ASLayoutableStyleCallDelegate(ASLayoutableStyleAscenderProperty); +} + +- (void)setDescender:(CGFloat)descender +{ + ASDN::MutexLocker l(__instanceLock__); + _descender = descender; + ASLayoutableStyleCallDelegate(ASLayoutableStyleDescenderProperty); +} + +#pragma mark - ASStaticLayoutable + +- (void)setLayoutPosition:(CGPoint)layoutPosition +{ + ASDN::MutexLocker l(__instanceLock__); + _layoutPosition = layoutPosition; + ASLayoutableStyleCallDelegate(ASLayoutableStyleLayoutPositionProperty); +} + @end diff --git a/AsyncDisplayKitTests/ASLayoutableStyleTests.m b/AsyncDisplayKitTests/ASLayoutableStyleTests.m new file mode 100644 index 0000000000..46d7013114 --- /dev/null +++ b/AsyncDisplayKitTests/ASLayoutableStyleTests.m @@ -0,0 +1,77 @@ +// +// ASLayoutableStyleTests.mm +// AsyncDisplayKit +// +// 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 "ASLayoutable.h" + +#pragma mark - ASLayoutableStyleTestsDelegate + +@interface ASLayoutableStyleTestsDelegate : NSObject +@property (copy, nonatomic) NSString *propertyNameChanged; +@end + +@implementation ASLayoutableStyleTestsDelegate + +- (void)style:(id)style propertyDidChange:(NSString *)propertyName +{ + self.propertyNameChanged = propertyName; +} + +@end + +#pragma mark - ASLayoutableStyleTests + +@interface ASLayoutableStyleTests : XCTestCase + +@end + +@implementation ASLayoutableStyleTests + +- (void)testSettingSizeProperties +{ + ASLayoutableStyle *style = [ASLayoutableStyle new]; + style.width = ASDimensionMake(100); + style.height = ASDimensionMake(100); + + XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100))); + XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100))); +} + +- (void)testSettingSizeViaHelper +{ + ASLayoutableStyle *style = [ASLayoutableStyle new]; + [style setSizeWithCGSize:CGSizeMake(100, 100)]; + + XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100))); + XCTAssertTrue(ASDimensionEqualToDimension(style.height, ASDimensionMake(100))); +} + +- (void)testSettingExactSize +{ + ASLayoutableStyle *style = [ASLayoutableStyle new]; + [style setExactSizeWithCGSize:CGSizeMake(100, 100)]; + + XCTAssertTrue(ASDimensionEqualToDimension(style.minWidth, ASDimensionMake(100))); + XCTAssertTrue(ASDimensionEqualToDimension(style.minHeight, ASDimensionMake(100))); + XCTAssertTrue(ASDimensionEqualToDimension(style.maxWidth, ASDimensionMake(100))); + XCTAssertTrue(ASDimensionEqualToDimension(style.maxHeight, ASDimensionMake(100))); +} + +- (void)testSettingPropertiesWillCallDelegate +{ + ASLayoutableStyleTestsDelegate *delegate = [ASLayoutableStyleTestsDelegate new]; + ASLayoutableStyle *style = [[ASLayoutableStyle alloc] initWithDelegate:delegate]; + XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionAuto)); + style.width = ASDimensionMake(100); + XCTAssertTrue(ASDimensionEqualToDimension(style.width, ASDimensionMake(100))); + XCTAssertTrue([delegate.propertyNameChanged isEqualToString:ASLayoutableStyleWidthProperty]); +} + +@end diff --git a/examples/Videos/Sample/ViewController.m b/examples/Videos/Sample/ViewController.m index 79933819f2..94e556f1c8 100644 --- a/examples/Videos/Sample/ViewController.m +++ b/examples/Videos/Sample/ViewController.m @@ -54,16 +54,16 @@ _rootNode.layoutSpecBlock = ^ASLayoutSpec *(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { // Layout all nodes absolute in a static layout spec - guitarVideoNode.style.size = ASLayoutableSizeMakeFromCGSize(CGSizeMake(mainScreenBoundsSize.width, mainScreenBoundsSize.height / 3.0)); + [guitarVideoNode.style setSizeWithCGSize:CGSizeMake(mainScreenBoundsSize.width, mainScreenBoundsSize.height / 3.0)]; guitarVideoNode.style.layoutPosition = CGPointMake(0, 0); - nicCageVideoNode.style.size = ASLayoutableSizeMakeFromCGSize(CGSizeMake(mainScreenBoundsSize.width/2, mainScreenBoundsSize.height / 3.0)); + [nicCageVideoNode.style setSizeWithCGSize:CGSizeMake(mainScreenBoundsSize.width/2, mainScreenBoundsSize.height / 3.0)]; nicCageVideoNode.style.layoutPosition = CGPointMake(mainScreenBoundsSize.width / 2.0, mainScreenBoundsSize.height / 3.0); - simonVideoNode.style.size = ASLayoutableSizeMakeFromCGSize(CGSizeMake(mainScreenBoundsSize.width/2, mainScreenBoundsSize.height / 3.0)); + [simonVideoNode.style setSizeWithCGSize:CGSizeMake(mainScreenBoundsSize.width/2, mainScreenBoundsSize.height / 3.0)]; simonVideoNode.style.layoutPosition = CGPointMake(0.0, mainScreenBoundsSize.height - (mainScreenBoundsSize.height / 3.0)); - hlsVideoNode.style.size = ASLayoutableSizeMakeFromCGSize(CGSizeMake(mainScreenBoundsSize.width / 2.0, mainScreenBoundsSize.height / 3.0)); + [hlsVideoNode.style setSizeWithCGSize:CGSizeMake(mainScreenBoundsSize.width / 2.0, mainScreenBoundsSize.height / 3.0)]; hlsVideoNode.style.layoutPosition = CGPointMake(0.0, mainScreenBoundsSize.height / 3.0); return [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]];