diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 402af31fe4..6fdef80577 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -588,6 +588,7 @@ 698C8B601CAB49FC0052DC3F /* ASLayoutElementExtensibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementExtensibility.h; path = AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h; sourceTree = ""; }; 698DFF431E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutSpecUtilities.h; sourceTree = ""; }; 698DFF461E36B7E9002891F1 /* ASLayoutSpecUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecUtilities.h; sourceTree = ""; }; + 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutSpecTests.m; sourceTree = ""; }; 69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayoutTests.mm; sourceTree = ""; }; 69B225681D7265DA00B25B22 /* ASXCTExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASXCTExtensions.h; sourceTree = ""; }; 69CB62A91CB8165900024920 /* _ASDisplayViewAccessiblity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASDisplayViewAccessiblity.h; sourceTree = ""; }; @@ -1020,6 +1021,7 @@ 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */, 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, + 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, ); name = Tests; path = AsyncDisplayKitTests; @@ -1763,6 +1765,7 @@ CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */, AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.m in Sources */, 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, + 699B83511E3C1BA500433FA4 /* ASLayoutSpecTests.m in Sources */, 696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */, 69FEE53D1D95A9AF0086F066 /* ASLayoutElementStyleTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, diff --git a/AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h b/AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h index 17140626f7..9121530851 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h +++ b/AsyncDisplayKit/Layout/ASLayoutElementExtensibility.h @@ -16,6 +16,10 @@ #import #endif +#import + +#pragma mark - ASLayoutElementExtensibility + @protocol ASLayoutElementExtensibility // The maximum number of extended values per type are defined in ASEnvironment.h above the ASEnvironmentStateExtensions @@ -30,5 +34,80 @@ - (void)setLayoutOptionExtensionEdgeInsets:(UIEdgeInsets)value atIndex:(int)idx; - (UIEdgeInsets)layoutOptionExtensionEdgeInsetsAtIndex:(int)idx; - @end + +#pragma mark - Dynamic Properties + +/** + * Unbox NSNumber based on the type + */ +#define ASDK_UNBOX_NUMBER(NUMBER, PROPERTY_TYPE) \ +const char *objCType = [NUMBER objCType]; \ +if (strcmp(objCType, @encode(BOOL)) == 0) { \ + return (PROPERTY_TYPE)[obj boolValue]; \ +} else if (strcmp(objCType, @encode(int)) == 0) { \ + return (PROPERTY_TYPE)[obj intValue]; \ +} else if (strcmp(objCType, @encode(NSInteger)) == 0) { \ + return (PROPERTY_TYPE)[obj integerValue]; \ +} else if (strcmp(objCType, @encode(NSUInteger)) == 0) { \ + return (PROPERTY_TYPE)[obj unsignedIntegerValue]; \ +} else if (strcmp(objCType, @encode(CGFloat)) == 0) { \ + return (PROPERTY_TYPE)[obj floatValue]; \ +} else { \ + NSAssert(NO, @"Data type not supported"); \ +} \ + +/** + * Define a NSObject property + */ +#define ASDK_STYLE_PROP_OBJ(PROPERTY_TYPE, PROPERTY_NAME, SETTER_NAME) \ +@dynamic PROPERTY_NAME; \ +- (PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + return (PROPERTY_TYPE)objc_getAssociatedObject(self, @selector(PROPERTY_NAME)); \ +} \ +\ +- (void)SETTER_NAME:(PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + objc_setAssociatedObject(self, @selector(PROPERTY_NAME), PROPERTY_NAME, OBJC_ASSOCIATION_RETAIN); \ +} \ + +/** + * Define an primitive property + */ +#define ASDK_STYLE_PROP_PRIM(PROPERTY_TYPE, PROPERTY_NAME, SETTER_NAME, DEFAULT_VALUE) \ +@dynamic PROPERTY_NAME; \ +- (PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + id obj = objc_getAssociatedObject(self, @selector(PROPERTY_NAME)); \ + \ + if (obj != nil) { \ + ASDK_UNBOX_NUMBER(obj, PROPERTY_TYPE); \ + } \ + \ + return DEFAULT_VALUE;\ +} \ +\ +- (void)SETTER_NAME:(PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + objc_setAssociatedObject(self, @selector(PROPERTY_NAME), @(PROPERTY_NAME), OBJC_ASSOCIATION_RETAIN); \ +} \ + +/** + * Define an structure property + */ +#define ASDK_STYLE_PROP_STR(PROPERTY_TYPE, PROPERTY_NAME, SETTER_NAME, DEFAULT_STRUCT) \ +@dynamic PROPERTY_NAME; \ +- (PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + id obj = objc_getAssociatedObject(self, @selector(PROPERTY_NAME)); \ + if (obj == nil) { \ + return DEFAULT_STRUCT; \ + } \ + PROPERTY_TYPE PROPERTY_NAME; [obj getValue:&PROPERTY_NAME]; return PROPERTY_NAME; \ +} \ +\ +- (void)SETTER_NAME:(PROPERTY_TYPE)PROPERTY_NAME \ +{ \ + objc_setAssociatedObject(self, @selector(PROPERTY_NAME), [NSValue value:&PROPERTY_NAME withObjCType:@encode(PROPERTY_TYPE)], OBJC_ASSOCIATION_RETAIN_NONATOMIC);\ +} \ diff --git a/AsyncDisplayKitTests/ASLayoutSpecTests.m b/AsyncDisplayKitTests/ASLayoutSpecTests.m new file mode 100644 index 0000000000..fc21efb346 --- /dev/null +++ b/AsyncDisplayKitTests/ASLayoutSpecTests.m @@ -0,0 +1,111 @@ +// +// ASLayoutSpecTests.m +// AsyncDisplayKit +// +// Created by Michael Schneider on 1/27/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +#import +#import + +#pragma mark - ASDKExtendedLayoutSpec + +/* + * Extend the ASDKExtendedLayoutElement + * It adds a + * - primitive / CGFloat (extendedWidth) + * - struct / ASDimension (extendedDimension) + * - primitive / ASStackLayoutDirection (extendedDirection) + */ +@protocol ASDKExtendedLayoutElement +@property (assign, nonatomic) CGFloat extendedWidth; +@property (assign, nonatomic) ASDimension extendedDimension; +@property (copy, nonatomic) NSString *extendedName; +@end + +/* + * Let the ASLayoutElementStyle conform to the ASDKExtendedLayoutElement protocol and add properties implementation + */ +@interface ASLayoutElementStyle (ASDKExtendedLayoutElement) +@end + +@implementation ASLayoutElementStyle (ASDKExtendedLayoutElement) +ASDK_STYLE_PROP_PRIM(CGFloat, extendedWidth, setExtendedWidth, 0); +ASDK_STYLE_PROP_STR(ASDimension, extendedDimension, setExtendedDimension, ASDimensionMake(ASDimensionUnitAuto, 0)); +ASDK_STYLE_PROP_OBJ(NSString *, extendedName, setExtendedName); +@end + +/* + * As the ASLayoutableStyle conforms to the ASDKExtendedLayoutable protocol now, ASDKExtendedLayoutable properties + * can be accessed in ASDKExtendedLayoutSpec + */ +@interface ASDKExtendedLayoutSpec : ASLayoutSpec +@end + +@implementation ASDKExtendedLayoutSpec + +- (void)doSetSomeStyleValuesToChildren +{ + for (id child in self.children) { + child.style.extendedWidth = 100; + child.style.extendedDimension = ASDimensionMake(100); + child.style.extendedName = @"ASDK"; + } +} + +- (void)doUseSomeStyleValuesFromChildren +{ + for (id child in self.children) { + __unused CGFloat extendedWidth = child.style.extendedWidth; + __unused ASDimension extendedDimension = child.style.extendedDimension; + __unused NSString *extendedName = child.style.extendedName; + } +} + +@end + + +#pragma mark - ASLayoutSpecTests + +@interface ASLayoutSpecTests : XCTestCase + +@end + +@implementation ASLayoutSpecTests + +- (void)testSetPrimitiveToExtendedStyle +{ + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.style.extendedWidth = 100; + XCTAssert(node.style.extendedWidth == 100, @"Primitive value should be set on extended style"); +} + +- (void)testSetStructToExtendedStyle +{ + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.style.extendedDimension = ASDimensionMake(100); + XCTAssertTrue(ASDimensionEqualToDimension(node.style.extendedDimension, ASDimensionMake(100)), @"Struct should be set on extended style"); +} + +- (void)testSetObjectToExtendedStyle +{ + NSString *extendedName = @"ASDK"; + + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.style.extendedName = extendedName; + XCTAssertEqualObjects(node.style.extendedName, extendedName, @"Object should be set on extended style"); +} + + +- (void)testUseOfExtendedStyleProperties +{ + ASDKExtendedLayoutSpec *extendedLayoutSpec = [ASDKExtendedLayoutSpec new]; + extendedLayoutSpec.children = @[[[ASDisplayNode alloc] init], [[ASDisplayNode alloc] init]]; + XCTAssertNoThrow([extendedLayoutSpec doSetSomeStyleValuesToChildren]); + XCTAssertNoThrow([extendedLayoutSpec doUseSomeStyleValuesFromChildren]); +} + +@end