diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index cefcdea8e4..28ee692cc4 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -33,6 +33,9 @@ 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */; }; 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; + 1A6C000D1FAB4E2100D05926 /* ASCornerLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A6C000B1FAB4E2000D05926 /* ASCornerLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 1A6C000E1FAB4E2100D05926 /* ASCornerLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A6C000C1FAB4E2100D05926 /* ASCornerLayoutSpec.mm */; }; + 1A6C00111FAB4EDD00D05926 /* ASCornerLayoutSpecSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; }; 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */; }; @@ -307,8 +310,8 @@ B350625C1B010F070018CF92 /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; - BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */; }; BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */; }; + BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */; }; @@ -572,6 +575,9 @@ 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = ""; }; 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = ""; }; 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = ""; }; + 1A6C000B1FAB4E2000D05926 /* ASCornerLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCornerLayoutSpec.h; sourceTree = ""; }; + 1A6C000C1FAB4E2100D05926 /* ASCornerLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCornerLayoutSpec.mm; sourceTree = ""; }; + 1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCornerLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = ""; }; 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionViewLayout+ASConvenience.m"; sourceTree = ""; }; 205F0E111B371BD7007741D0 /* ASScrollDirection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollDirection.m; sourceTree = ""; }; @@ -785,8 +791,8 @@ B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutManager.h; path = TextKit/ASLayoutManager.h; sourceTree = ""; }; B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTabBarControllerTests.m; sourceTree = ""; }; BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNavigationControllerTests.m; sourceTree = ""; }; + BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTabBarControllerTests.m; sourceTree = ""; }; BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = ""; }; @@ -1187,6 +1193,7 @@ 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */, ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, + 1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */, 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */, ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, @@ -1524,6 +1531,8 @@ ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */, ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */, ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */, + 1A6C000B1FAB4E2000D05926 /* ASCornerLayoutSpec.h */, + 1A6C000C1FAB4E2100D05926 /* ASCornerLayoutSpec.mm */, ACF6ED071B17843500DA7C62 /* ASDimension.h */, ACF6ED081B17843500DA7C62 /* ASDimension.mm */, 690C35631E055C7B00069B91 /* ASDimensionInternal.h */, @@ -1717,6 +1726,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 1A6C000D1FAB4E2100D05926 /* ASCornerLayoutSpec.h in Headers */, E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */, E5B225281F1790D6001E1431 /* ASHashing.h in Headers */, CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */, @@ -2069,13 +2079,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-AsyncDisplayKitTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 3B9D88CDF51B429C8409E4B6 /* [CP] Copy Pods Resources */ = { @@ -2174,6 +2187,7 @@ ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */, + 1A6C00111FAB4EDD00D05926 /* ASCornerLayoutSpecSnapshotTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, @@ -2334,6 +2348,7 @@ 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, CCCCCCDC1EC3EF060087FE10 /* ASTextLine.m in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, + 1A6C000E1FAB4E2100D05926 /* ASCornerLayoutSpec.mm in Sources */, CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */, 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 909C4C761F09C98B00D6B76F /* ASTextNode2.mm in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e4f027f9c..fa04a6c773 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler) - [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651) - [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660) +- [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index b1903945ef..c20f5077a1 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -79,6 +79,7 @@ #import #import #import +#import #import #import #import diff --git a/Source/Layout/ASCornerLayoutSpec.h b/Source/Layout/ASCornerLayoutSpec.h new file mode 100644 index 0000000000..23d09d0179 --- /dev/null +++ b/Source/Layout/ASCornerLayoutSpec.h @@ -0,0 +1,79 @@ +// +// ASCornerLayoutSpec.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +/** + The corner location for positioning corner element. + */ +typedef NS_ENUM(NSInteger, ASCornerLayoutLocation) { + ASCornerLayoutLocationTopLeft, + ASCornerLayoutLocationTopRight, + ASCornerLayoutLocationBottomLeft, + ASCornerLayoutLocationBottomRight, +}; + +NS_ASSUME_NONNULL_BEGIN + +/** + A layout spec that positions a corner element which relatives to the child element. + + @warning Both child element and corner element must have valid preferredSize for layout calculation. + */ +@interface ASCornerLayoutSpec : ASLayoutSpec + +/** + A layout spec that positions a corner element which relatives to the child element. + + @param child A child that is laid out to determine the size of this spec. + @param corner A layoutElement object that is laid out to a corner on the child. + @param location The corner position option. + @return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. + */ +- (instancetype)initWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location AS_WARN_UNUSED_RESULT; + +/** + A layout spec that positions a corner element which relatives to the child element. + + @param child A child that is laid out to determine the size of this spec. + @param corner A layoutElement object that is laid out to a corner on the child. + @param location The corner position option. + @return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. + */ ++ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location AS_WARN_UNUSED_RESULT; + +/** + A layoutElement object that is laid out to a corner on the child. + */ +@property (nonatomic, strong) id corner; + +/** + The corner position option. + */ +@property (nonatomic, assign) ASCornerLayoutLocation cornerLocation; + +/** + The point which offsets from the corner location. Use this property to make delta + distance from the default corner location. Default is CGPointZero. + */ +@property (nonatomic, assign) CGPoint offset; + +/** + Whether should include corner element into layout size calculation. If included, + the layout size will be the union size of both child and corner; If not included, + the layout size will be only child's size. Default is NO. + */ +@property (nonatomic, assign) BOOL wrapsCorner; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Layout/ASCornerLayoutSpec.mm b/Source/Layout/ASCornerLayoutSpec.mm new file mode 100644 index 0000000000..d1104089b3 --- /dev/null +++ b/Source/Layout/ASCornerLayoutSpec.mm @@ -0,0 +1,169 @@ +// +// ASCornerLayoutSpec.mm +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import +#import + +CGPoint as_calculatedCornerOriginIn(CGRect baseFrame, CGSize cornerSize, ASCornerLayoutLocation cornerLocation, CGPoint offset) +{ + CGPoint cornerOrigin = CGPointZero; + CGPoint baseOrigin = baseFrame.origin; + CGSize baseSize = baseFrame.size; + + switch (cornerLocation) { + case ASCornerLayoutLocationTopLeft: + cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; + cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; + break; + case ASCornerLayoutLocationTopRight: + cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; + cornerOrigin.y = baseOrigin.y - cornerSize.height / 2; + break; + case ASCornerLayoutLocationBottomLeft: + cornerOrigin.x = baseOrigin.x - cornerSize.width / 2; + cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; + break; + case ASCornerLayoutLocationBottomRight: + cornerOrigin.x = baseOrigin.x + baseSize.width - cornerSize.width / 2; + cornerOrigin.y = baseOrigin.y + baseSize.height - cornerSize.height / 2; + break; + } + + cornerOrigin.x += offset.x; + cornerOrigin.y += offset.y; + + return cornerOrigin; +} + +static NSUInteger const kBaseChildIndex = 0; +static NSUInteger const kCornerChildIndex = 1; + +@interface ASCornerLayoutSpec() +@end + +@implementation ASCornerLayoutSpec + +- (instancetype)initWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location +{ + self = [super init]; + if (self) { + self.child = child; + self.corner = corner; + self.cornerLocation = location; + } + return self; +} + ++ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location +{ + return [[self alloc] initWithChild:child corner:corner location:location]; +} + +#pragma mark - Children + +- (void)setChild:(id)child +{ + ASDisplayNodeAssertNotNil(child, @"Child shouldn't be nil."); + [super setChild:child atIndex:kBaseChildIndex]; +} + +- (id)child +{ + return [super childAtIndex:kBaseChildIndex]; +} + +- (void)setCorner:(id)corner +{ + ASDisplayNodeAssertNotNil(corner, @"Corner element cannot be nil."); + [super setChild:corner atIndex:kCornerChildIndex]; +} + +- (id)corner +{ + return [super childAtIndex:kCornerChildIndex]; +} + +#pragma mark - Calculation + +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +{ + CGSize size = { + ASPointsValidForSize(constrainedSize.max.width) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.width, + ASPointsValidForSize(constrainedSize.max.height) == NO ? ASLayoutElementParentDimensionUndefined : constrainedSize.max.height + }; + + id child = self.child; + id corner = self.corner; + + // Element validation + [self _validateElement:child]; + [self _validateElement:corner]; + + CGRect childFrame = CGRectZero; + CGRect cornerFrame = CGRectZero; + + // Layout child + ASLayout *childLayout = [child layoutThatFits:constrainedSize parentSize:size]; + childFrame.size = childLayout.size; + + // Layout corner + ASLayout *cornerLayout = [corner layoutThatFits:constrainedSize parentSize:size]; + cornerFrame.size = cornerLayout.size; + + // Calculate corner's position + CGPoint relativePosition = as_calculatedCornerOriginIn(childFrame, cornerFrame.size, _cornerLocation, _offset); + + // Update corner's position + cornerFrame.origin = relativePosition; + + // Calculate size + CGRect frame = childFrame; + if (_wrapsCorner) { + frame = CGRectUnion(childFrame, cornerFrame); + frame.size = ASSizeRangeClamp(constrainedSize, frame.size); + } + + // Shift sublayouts' positions if they are off the bounds. + if (frame.origin.x != 0) { + CGFloat deltaX = frame.origin.x; + childFrame.origin.x -= deltaX; + cornerFrame.origin.x -= deltaX; + } + + if (frame.origin.y != 0) { + CGFloat deltaY = frame.origin.y; + childFrame.origin.y -= deltaY; + cornerFrame.origin.y -= deltaY; + } + + childLayout.position = childFrame.origin; + cornerLayout.position = cornerFrame.origin; + + return [ASLayout layoutWithLayoutElement:self size:frame.size sublayouts:@[childLayout, cornerLayout]]; +} + +- (void)_validateElement:(id )element +{ + // Validate non-nil element + if (element == nil) { + ASDisplayNodeAssertNotNil(element, @"[%@]: Must have a non-nil child/corner for layout calculation.", self.class); + } + // Validate preferredSize if needed + CGSize size = element.style.preferredSize; + if (!CGSizeEqualToSize(size, CGSizeZero) && !ASIsCGSizeValidForSize(size) && (size.width < 0 || (size.height < 0))) { + ASDisplayNodeFailAssert(@"[%@]: Should give a valid preferredSize value for %@ before corner's position calculation.", self.class, element); + } +} + +@end diff --git a/Tests/ASCornerLayoutSpecSnapshotTests.mm b/Tests/ASCornerLayoutSpecSnapshotTests.mm new file mode 100644 index 0000000000..fcbbfe9850 --- /dev/null +++ b/Tests/ASCornerLayoutSpecSnapshotTests.mm @@ -0,0 +1,219 @@ +// +// ASCornerLayoutSpecSnapshotTests.mm +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASLayoutSpecSnapshotTestsHelper.h" +#import +#import + +typedef NS_ENUM(NSInteger, ASCornerLayoutSpecSnapshotTestsOffsetOption) { + ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter, + ASCornerLayoutSpecSnapshotTestsOffsetOptionInner, + ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter, +}; + + +@interface ASCornerLayoutSpecSnapshotTests : ASLayoutSpecSnapshotTestCase + +@property (nonatomic, strong) UIColor *boxColor; +@property (nonatomic, strong) UIColor *baseColor; +@property (nonatomic, strong) UIColor *cornerColor; +@property (nonatomic, strong) UIColor *contextColor; + +@property (nonatomic, assign) CGSize baseSize; +@property (nonatomic, assign) CGSize cornerSize; +@property (nonatomic, assign) CGSize contextSize; + +@property (nonatomic, assign) ASSizeRange contextSizeRange; + +@end + + +@implementation ASCornerLayoutSpecSnapshotTests + +- (void)setUp +{ + [super setUp]; + + self.recordMode = NO; + + _boxColor = [UIColor greenColor]; + _baseColor = [UIColor blueColor]; + _cornerColor = [UIColor orangeColor]; + _contextColor = [UIColor lightGrayColor]; + + _baseSize = CGSizeMake(60, 60); + _cornerSize = CGSizeMake(20, 20); + _contextSize = CGSizeMake(120, 120); + + _contextSizeRange = ASSizeRangeMake(CGSizeZero, _contextSize); +} + +- (void)testCornerSpecForAllLocations +{ + ASCornerLayoutSpecSnapshotTestsOffsetOption center = ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:center wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:center wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:center wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:center wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:center wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:center wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:center wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:center wrapsCorner:YES]; +} + +- (void)testCornerSpecForAllLocationsWithInnerOffset +{ + ASCornerLayoutSpecSnapshotTestsOffsetOption inner = ASCornerLayoutSpecSnapshotTestsOffsetOptionInner; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:inner wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:inner wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:inner wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:inner wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:inner wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:inner wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:inner wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:inner wrapsCorner:YES]; +} + +- (void)testCornerSpecForAllLocationsWithOuterOffset +{ + ASCornerLayoutSpecSnapshotTestsOffsetOption outer = ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:outer wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopLeft offsetOption:outer wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:outer wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationTopRight offsetOption:outer wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:outer wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomLeft offsetOption:outer wrapsCorner:YES]; + + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:outer wrapsCorner:NO]; + [self testCornerSpecWithLocation:ASCornerLayoutLocationBottomRight offsetOption:outer wrapsCorner:YES]; +} + +- (void)testCornerSpecWithLocation:(ASCornerLayoutLocation)location + offsetOption:(ASCornerLayoutSpecSnapshotTestsOffsetOption)offsetOption + wrapsCorner:(BOOL)wrapsCorner +{ + ASDisplayNode *baseNode = ASDisplayNodeWithBackgroundColor(_baseColor, _baseSize); + ASDisplayNode *cornerNode = ASDisplayNodeWithBackgroundColor(_cornerColor, _cornerSize); + ASDisplayNode *debugBoxNode = ASDisplayNodeWithBackgroundColor(_boxColor); + + baseNode.style.layoutPosition = CGPointMake((_contextSize.width - _baseSize.width) / 2, + (_contextSize.height - _baseSize.height) / 2); + + ASCornerLayoutSpec *cornerSpec = [ASCornerLayoutSpec cornerLayoutSpecWithChild:baseNode + corner:cornerNode + location:location]; + + CGPoint delta = (CGPoint){ _cornerSize.width / 2, _cornerSize.height / 2 }; + cornerSpec.offset = [self offsetForOption:offsetOption location:location delta:delta]; + cornerSpec.wrapsCorner = wrapsCorner; + + ASBackgroundLayoutSpec *backgroundSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:cornerSpec + background:debugBoxNode]; + + [self testLayoutSpec:backgroundSpec + sizeRange:_contextSizeRange + subnodes:@[debugBoxNode, baseNode, cornerNode] + identifier:[self suffixWithLocation:location option:offsetOption wrapsCorner:wrapsCorner]]; +} + +- (CGPoint)offsetForOption:(ASCornerLayoutSpecSnapshotTestsOffsetOption)option + location:(ASCornerLayoutLocation)location + delta:(CGPoint)delta +{ + CGFloat x = delta.x; + CGFloat y = delta.y; + + switch (option) { + + case ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter: + return CGPointZero; + + case ASCornerLayoutSpecSnapshotTestsOffsetOptionInner: + + switch (location) { + case ASCornerLayoutLocationTopLeft: return (CGPoint){ x, y }; + case ASCornerLayoutLocationTopRight: return (CGPoint){ -x, y }; + case ASCornerLayoutLocationBottomLeft: return (CGPoint){ x, -y }; + case ASCornerLayoutLocationBottomRight: return (CGPoint){ -x, -y }; + } + + case ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter: + + switch (location) { + case ASCornerLayoutLocationTopLeft: return (CGPoint){ -x, -y }; + case ASCornerLayoutLocationTopRight: return (CGPoint){ x, -y }; + case ASCornerLayoutLocationBottomLeft: return (CGPoint){ -x, y }; + case ASCornerLayoutLocationBottomRight: return (CGPoint){ x, y }; + } + + } + +} + +- (NSString *)suffixWithLocation:(ASCornerLayoutLocation)location + option:(ASCornerLayoutSpecSnapshotTestsOffsetOption)option + wrapsCorner:(BOOL)wrapsCorner +{ + NSMutableString *desc = [NSMutableString string]; + + switch (location) { + case ASCornerLayoutLocationTopLeft: + [desc appendString:@"topLeft"]; + break; + case ASCornerLayoutLocationTopRight: + [desc appendString:@"topRight"]; + break; + case ASCornerLayoutLocationBottomLeft: + [desc appendString:@"bottomLeft"]; + break; + case ASCornerLayoutLocationBottomRight: + [desc appendString:@"bottomRight"]; + break; + } + + [desc appendString:@"_"]; + + switch (option) { + case ASCornerLayoutSpecSnapshotTestsOffsetOptionCenter: + [desc appendString:@"center"]; + break; + case ASCornerLayoutSpecSnapshotTestsOffsetOptionInner: + [desc appendString:@"inner"]; + break; + case ASCornerLayoutSpecSnapshotTestsOffsetOptionOuter: + [desc appendString:@"outer"]; + break; + } + + [desc appendString:@"_"]; + + if (wrapsCorner) { + [desc appendString:@"fullSize"]; + } else { + [desc appendString:@"childSize"]; + } + + return desc.copy; +} + +@end diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomLeft_inner_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomLeft_inner_childSize@2x.png new file mode 100644 index 0000000000..3b78fb5e7d Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomLeft_inner_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomLeft_inner_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomLeft_inner_fullSize@2x.png new file mode 100644 index 0000000000..3b78fb5e7d Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomLeft_inner_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomRight_inner_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomRight_inner_childSize@2x.png new file mode 100644 index 0000000000..34851067b9 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomRight_inner_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomRight_inner_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomRight_inner_fullSize@2x.png new file mode 100644 index 0000000000..34851067b9 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_bottomRight_inner_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topLeft_inner_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topLeft_inner_childSize@2x.png new file mode 100644 index 0000000000..aa4c3ee8d8 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topLeft_inner_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topLeft_inner_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topLeft_inner_fullSize@2x.png new file mode 100644 index 0000000000..aa4c3ee8d8 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topLeft_inner_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topRight_inner_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topRight_inner_childSize@2x.png new file mode 100644 index 0000000000..23082ede8c Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topRight_inner_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topRight_inner_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topRight_inner_fullSize@2x.png new file mode 100644 index 0000000000..23082ede8c Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithInnerOffset_topRight_inner_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomLeft_outer_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomLeft_outer_childSize@2x.png new file mode 100644 index 0000000000..18e211ae8e Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomLeft_outer_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomLeft_outer_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomLeft_outer_fullSize@2x.png new file mode 100644 index 0000000000..393143a214 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomLeft_outer_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomRight_outer_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomRight_outer_childSize@2x.png new file mode 100644 index 0000000000..18e211ae8e Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomRight_outer_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomRight_outer_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomRight_outer_fullSize@2x.png new file mode 100644 index 0000000000..12498681e0 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_bottomRight_outer_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topLeft_outer_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topLeft_outer_childSize@2x.png new file mode 100644 index 0000000000..18e211ae8e Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topLeft_outer_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topLeft_outer_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topLeft_outer_fullSize@2x.png new file mode 100644 index 0000000000..dc4f1ab2b3 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topLeft_outer_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topRight_outer_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topRight_outer_childSize@2x.png new file mode 100644 index 0000000000..18e211ae8e Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topRight_outer_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topRight_outer_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topRight_outer_fullSize@2x.png new file mode 100644 index 0000000000..fa7e15a554 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocationsWithOuterOffset_topRight_outer_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomLeft_center_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomLeft_center_childSize@2x.png new file mode 100644 index 0000000000..90f411aff2 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomLeft_center_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomLeft_center_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomLeft_center_fullSize@2x.png new file mode 100644 index 0000000000..6d49323c17 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomLeft_center_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomRight_center_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomRight_center_childSize@2x.png new file mode 100644 index 0000000000..9d23e2b646 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomRight_center_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomRight_center_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomRight_center_fullSize@2x.png new file mode 100644 index 0000000000..58257ffef6 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_bottomRight_center_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topLeft_center_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topLeft_center_childSize@2x.png new file mode 100644 index 0000000000..3503fd79dd Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topLeft_center_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topLeft_center_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topLeft_center_fullSize@2x.png new file mode 100644 index 0000000000..263f50d29c Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topLeft_center_fullSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topRight_center_childSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topRight_center_childSize@2x.png new file mode 100644 index 0000000000..492fc049b8 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topRight_center_childSize@2x.png differ diff --git a/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topRight_center_fullSize@2x.png b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topRight_center_fullSize@2x.png new file mode 100644 index 0000000000..9e39a3c5c4 Binary files /dev/null and b/Tests/ReferenceImages_iOS_10/ASCornerLayoutSpecSnapshotTests/testCornerSpecForAllLocations_topRight_center_fullSize@2x.png differ diff --git a/docs/static/images/example-app-screenshots/ASCornerLayoutSpec.png b/docs/static/images/example-app-screenshots/ASCornerLayoutSpec.png new file mode 100644 index 0000000000..2a3ac02101 Binary files /dev/null and b/docs/static/images/example-app-screenshots/ASCornerLayoutSpec.png differ diff --git a/examples/LayoutSpecExamples-Swift/Sample/LayoutExampleNode.swift b/examples/LayoutSpecExamples-Swift/Sample/LayoutExampleNode.swift index 75531d9026..8be3356839 100644 --- a/examples/LayoutSpecExamples-Swift/Sample/LayoutExampleNode.swift +++ b/examples/LayoutSpecExamples-Swift/Sample/LayoutExampleNode.swift @@ -143,3 +143,140 @@ class FlexibleSeparatorSurroundingContent : LayoutExampleNode { return "try rotating me!" } } + +class CornerLayoutSample : PhotoWithOutsetIconOverlay { + let photoNode1 = ASImageNode() + let photoNode2 = ASImageNode() + let dotNode = ASImageNode() + let badgeTextNode = ASTextNode() + let badgeImageNode = ASImageNode() + + struct ImageSize { + static let avatar = CGSize(width: 100, height: 100) + static let icon = CGSize(width: 26, height: 26) + } + + struct ImageColor { + static let avatar = UIColor.lightGray + static let icon = UIColor.red + } + + required init() { + super.init() + + let avatarImage = UIImage.draw(size: ImageSize.avatar, fillColor: ImageColor.avatar) { () -> UIBezierPath in + return UIBezierPath(roundedRect: CGRect(origin: CGPoint.zero, size: ImageSize.avatar), cornerRadius: ImageSize.avatar.width / 20) + } + + let iconImage = UIImage.draw(size: ImageSize.icon, fillColor: ImageColor.icon) { () -> UIBezierPath in + return UIBezierPath(ovalIn: CGRect(origin: CGPoint.zero, size: ImageSize.icon)) + } + + photoNode1.image = avatarImage + photoNode2.image = avatarImage + dotNode.image = iconImage + + badgeTextNode.attributedText = NSAttributedString.attributedString(string: " 999+ ", fontSize: 20, color: .white) + + badgeImageNode.image = UIImage.as_resizableRoundedImage(withCornerRadius: 12, cornerColor: .clear, fill: .red) + } + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + photoNode.style.preferredSize = ImageSize.avatar + iconNode.style.preferredSize = ImageSize.icon + + let badgeSpec = ASBackgroundLayoutSpec(child: badgeTextNode, background: badgeImageNode) + let cornerSpec1 = ASCornerLayoutSpec(child: photoNode1, corner: dotNode, location: .topRight) + let cornerSpec2 = ASCornerLayoutSpec(child: photoNode2, corner: badgeSpec, location: .topRight) + let cornerSpec3 = ASCornerLayoutSpec(child: photoNode, corner: iconNode, location: .topRight) + + cornerSpec1.offset = CGPoint(x: -3, y: 3) + + let stackSpec = ASStackLayoutSpec.vertical() + stackSpec.spacing = 40 + stackSpec.children = [cornerSpec1, cornerSpec2, cornerSpec3] + + return stackSpec + } + + override class func title() -> String { + return "Declarative way for Corner image Layout" + } + + override class func descriptionTitle() -> String? { + return nil + } +} + +class UserProfileSample : LayoutExampleNode { + + let badgeNode = ASImageNode() + let avatarNode = ASImageNode() + let usernameNode = ASTextNode() + let subtitleNode = ASTextNode() + + struct ImageSize { + static let avatar = CGSize(width: 44, height: 44) + static let badge = CGSize(width: 15, height: 15) + } + + struct ImageColor { + static let avatar = UIColor.lightGray + static let badge = UIColor.red + } + + required init() { + super.init() + + avatarNode.image = UIImage.draw(size: ImageSize.avatar, fillColor: ImageColor.avatar) { () -> UIBezierPath in + return UIBezierPath(ovalIn: CGRect(origin: CGPoint.zero, size: ImageSize.avatar)) + } + + badgeNode.image = UIImage.draw(size: ImageSize.badge, fillColor: ImageColor.badge) { () -> UIBezierPath in + return UIBezierPath(ovalIn: CGRect(origin: CGPoint.zero, size: ImageSize.badge)) + } + + makeSingleLine(for: usernameNode, with: "Hello world", fontSize: 17, textColor: .black) + makeSingleLine(for: subtitleNode, with: "This is a long long subtitle, with a long long appended string.", fontSize: 14, textColor: .lightGray) + } + + private func makeSingleLine(for node: ASTextNode, with text: String, fontSize: CGFloat, textColor: UIColor) { + node.attributedText = NSAttributedString.attributedString(string: text, fontSize: fontSize, color: textColor) + node.maximumNumberOfLines = 1 + } + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + let avatarBox = ASCornerLayoutSpec(child: avatarNode, corner: badgeNode, location: .bottomRight) + avatarBox.offset = CGPoint(x: -6, y: -6) + + let textBox = ASStackLayoutSpec.vertical() + textBox.justifyContent = .spaceAround + textBox.children = [usernameNode, subtitleNode] + + let profileBox = ASStackLayoutSpec.horizontal() + profileBox.spacing = 10 + profileBox.children = [avatarBox, textBox] + + // Apply text truncation + let elems: [ASLayoutElement] = [usernameNode, subtitleNode, textBox, profileBox] + for elem in elems { + elem.style.flexShrink = 1 + } + + let insetBox = ASInsetLayoutSpec( + insets: UIEdgeInsets(top: 120, left: 20, bottom: CGFloat.infinity, right: 20), + child: profileBox + ) + + return insetBox + } + + override class func title() -> String { + return "Common user profile layout." + } + + override class func descriptionTitle() -> String? { + return "For corner image layout and text truncation." + } + +} diff --git a/examples/LayoutSpecExamples-Swift/Sample/OverviewViewController.swift b/examples/LayoutSpecExamples-Swift/Sample/OverviewViewController.swift index 6e5240c7d2..e4b6f5d5a4 100644 --- a/examples/LayoutSpecExamples-Swift/Sample/OverviewViewController.swift +++ b/examples/LayoutSpecExamples-Swift/Sample/OverviewViewController.swift @@ -26,7 +26,9 @@ class OverviewViewController: ASViewController { HeaderWithRightAndLeftItems.self, PhotoWithInsetTextOverlay.self, PhotoWithOutsetIconOverlay.self, - FlexibleSeparatorSurroundingContent.self + FlexibleSeparatorSurroundingContent.self, + CornerLayoutSample.self, + UserProfileSample.self ] super.init(node: tableNode) diff --git a/examples/LayoutSpecExamples-Swift/Sample/Utilities.swift b/examples/LayoutSpecExamples-Swift/Sample/Utilities.swift index 683130618b..1f1bad89ce 100644 --- a/examples/LayoutSpecExamples-Swift/Sample/Utilities.swift +++ b/examples/LayoutSpecExamples-Swift/Sample/Utilities.swift @@ -74,7 +74,21 @@ extension UIImage { return roundedImage ?? self } - + + class func draw(size: CGSize, fillColor: UIColor, shapeClosure: () -> UIBezierPath) -> UIImage { + UIGraphicsBeginImageContext(size) + + let path = shapeClosure() + path.addClip() + + fillColor.setFill() + path.fill() + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image! + } } extension NSAttributedString { diff --git a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj index 8027131811..3c9041a2b3 100644 --- a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj +++ b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj @@ -220,13 +220,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/examples/LayoutSpecExamples/Sample.xcworkspace/contents.xcworkspacedata b/examples/LayoutSpecExamples/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/LayoutSpecExamples/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.h b/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.h index 7355285d3c..65241146ba 100644 --- a/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.h +++ b/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.h @@ -33,3 +33,9 @@ @interface FlexibleSeparatorSurroundingContent : LayoutExampleNode @end + +@interface CornerLayoutExample : PhotoWithOutsetIconOverlay +@end + +@interface UserProfileSample : LayoutExampleNode +@end diff --git a/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.m b/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.m index e7f9fbe6f0..9b84cb16e4 100644 --- a/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.m +++ b/examples/LayoutSpecExamples/Sample/LayoutExampleNodes.m @@ -260,6 +260,188 @@ @end +@interface CornerLayoutExample () +@property (nonatomic, strong) ASImageNode *dotNode; +@property (nonatomic, strong) ASImageNode *photoNode1; +@property (nonatomic, strong) ASTextNode *badgeTextNode; +@property (nonatomic, strong) ASImageNode *badgeImageNode; +@property (nonatomic, strong) ASImageNode *photoNode2; +@end + +@implementation CornerLayoutExample + +static CGFloat const kSampleAvatarSize = 100; +static CGFloat const kSampleIconSize = 26; +static CGFloat const kSampleBadgeCornerRadius = 12; + ++ (NSString *)title +{ + return @"Declarative way for Corner image Layout"; +} + ++ (NSString *)descriptionTitle +{ + return nil; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + UIImage *avatarImage = [self avatarImageWithSize:CGSizeMake(kSampleAvatarSize, kSampleAvatarSize)]; + UIImage *cornerImage = [self cornerImageWithSize:CGSizeMake(kSampleIconSize, kSampleIconSize)]; + + NSAttributedString *numberText = [NSAttributedString attributedStringWithString:@" 999+ " fontSize:20 color:UIColor.whiteColor]; + + _dotNode = [ASImageNode new]; + _dotNode.image = cornerImage; + + _photoNode1 = [ASImageNode new]; + _photoNode1.image = avatarImage; + + _badgeTextNode = [ASTextNode new]; + _badgeTextNode.attributedText = numberText; + + _badgeImageNode = [ASImageNode new]; + _badgeImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:kSampleBadgeCornerRadius + cornerColor:UIColor.clearColor + fillColor:UIColor.redColor]; + + _photoNode2 = [ASImageNode new]; + _photoNode2.image = avatarImage; + } + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + + ASBackgroundLayoutSpec *badgeSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:_badgeTextNode + background:_badgeImageNode]; + + ASCornerLayoutSpec *cornerSpec1 = [ASCornerLayoutSpec cornerLayoutSpecWithChild:_photoNode1 corner:_dotNode location:ASCornerLayoutLocationTopRight]; + cornerSpec1.offset = CGPointMake(-3, 3); + + ASCornerLayoutSpec *cornerSpec2 = [ASCornerLayoutSpec cornerLayoutSpecWithChild:_photoNode2 corner:badgeSpec location:ASCornerLayoutLocationTopRight]; + + self.photoNode.style.preferredSize = CGSizeMake(kSampleAvatarSize, kSampleAvatarSize); + self.iconNode.style.preferredSize = CGSizeMake(kSampleIconSize, kSampleIconSize); + + ASCornerLayoutSpec *cornerSpec3 = [ASCornerLayoutSpec cornerLayoutSpecWithChild:self.photoNode corner:self.iconNode location:ASCornerLayoutLocationTopRight]; + + ASStackLayoutSpec *stackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; + stackSpec.spacing = 40; + stackSpec.children = @[cornerSpec1, cornerSpec2, cornerSpec3]; + + return stackSpec; +} + +- (UIImage *)avatarImageWithSize:(CGSize)size +{ + return [UIImage imageWithSize:size fillColor:UIColor.lightGrayColor shapeBlock:^UIBezierPath *{ + CGRect rect = (CGRect){ CGPointZero, size }; + return [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:MIN(size.width, size.height) / 20]; + }]; +} + +- (UIImage *)cornerImageWithSize:(CGSize)size +{ + return [UIImage imageWithSize:size fillColor:UIColor.redColor shapeBlock:^UIBezierPath *{ + return [UIBezierPath bezierPathWithOvalInRect:(CGRect){ CGPointZero, size }]; + }]; +} + +@end + + +@interface UserProfileSample () +@property (nonatomic, strong) ASImageNode *badgeNode; +@property (nonatomic, strong) ASImageNode *avatarNode; +@property (nonatomic, strong) ASTextNode *usernameNode; +@property (nonatomic, strong) ASTextNode *subtitleNode; +@property (nonatomic, assign) CGFloat photoSizeValue; +@property (nonatomic, assign) CGFloat iconSizeValue; +@end + +@implementation UserProfileSample + ++ (NSString *)title +{ + return @"Common user profile layout."; +} + ++ (NSString *)descriptionTitle +{ + return @"For corner image layout and text truncation."; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _photoSizeValue = 44; + _iconSizeValue = 15; + + CGSize iconSize = CGSizeMake(_iconSizeValue, _iconSizeValue); + CGSize photoSize = CGSizeMake(_photoSizeValue, _photoSizeValue); + + _badgeNode = [ASImageNode new]; + _badgeNode.style.preferredSize = iconSize; + _badgeNode.image = [UIImage imageWithSize:iconSize fillColor:UIColor.redColor shapeBlock:^UIBezierPath *{ + return [UIBezierPath bezierPathWithOvalInRect:(CGRect){ CGPointZero, iconSize }]; + }]; + + _avatarNode = [ASImageNode new]; + _avatarNode.style.preferredSize = photoSize; + _avatarNode.image = [UIImage imageWithSize:photoSize fillColor:UIColor.lightGrayColor shapeBlock:^UIBezierPath *{ + return [UIBezierPath bezierPathWithOvalInRect:(CGRect){ CGPointZero, photoSize }]; + }]; + + _usernameNode = [ASTextNode new]; + _usernameNode.attributedText = [NSAttributedString attributedStringWithString:@"Hello World" fontSize:17 color:UIColor.blackColor]; + _usernameNode.maximumNumberOfLines = 1; + + _subtitleNode = [ASTextNode new]; + _subtitleNode.attributedText = [NSAttributedString attributedStringWithString:@"This is a long long subtitle, with a long long appended string." fontSize:14 color:UIColor.lightGrayColor]; + _subtitleNode.maximumNumberOfLines = 1; + } + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + // Apply avatar with badge + // Normally, avatar's box size is the only photo size and it will not include the badge size. + // Otherwise, use includeCornerForSizeCalculation property to increase the box's size if needed. + ASCornerLayoutSpec *avatarBox = [ASCornerLayoutSpec new]; + avatarBox.child = _avatarNode; + avatarBox.corner = _badgeNode; + avatarBox.cornerLocation = ASCornerLayoutLocationBottomRight; + avatarBox.offset = CGPointMake(-6, -6); + + ASStackLayoutSpec *textBox = [ASStackLayoutSpec verticalStackLayoutSpec]; + textBox.justifyContent = ASStackLayoutJustifyContentSpaceAround; + textBox.children = @[_usernameNode, _subtitleNode]; + + ASStackLayoutSpec *profileBox = [ASStackLayoutSpec horizontalStackLayoutSpec]; + profileBox.spacing = 10; + profileBox.children = @[avatarBox, textBox]; + + // Apply text truncation. + NSArray *elems = @[_usernameNode, _subtitleNode, textBox, profileBox]; + for (id elem in elems) { + elem.style.flexShrink = 1; + } + + ASInsetLayoutSpec *profileInsetBox = [ASInsetLayoutSpec new]; + profileInsetBox.insets = UIEdgeInsetsMake(120, 20, INFINITY, 20); + profileInsetBox.child = profileBox; + + return profileInsetBox; +} + +@end + @implementation LayoutExampleNode + (NSString *)title diff --git a/examples/LayoutSpecExamples/Sample/OverviewViewController.m b/examples/LayoutSpecExamples/Sample/OverviewViewController.m index e3f44dfb06..c3cd7f9b6b 100644 --- a/examples/LayoutSpecExamples/Sample/OverviewViewController.m +++ b/examples/LayoutSpecExamples/Sample/OverviewViewController.m @@ -1,11 +1,18 @@ // // OverviewViewController.m -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "OverviewViewController.h" @@ -37,7 +44,10 @@ _layoutExamples = @[[HeaderWithRightAndLeftItems class], [PhotoWithInsetTextOverlay class], [PhotoWithOutsetIconOverlay class], - [FlexibleSeparatorSurroundingContent class]]; + [FlexibleSeparatorSurroundingContent class], + [CornerLayoutExample class], + [UserProfileSample class] + ]; } return self; diff --git a/examples/LayoutSpecExamples/Sample/Utilities.h b/examples/LayoutSpecExamples/Sample/Utilities.h index 0fcb0ece91..b4bf2f824a 100644 --- a/examples/LayoutSpecExamples/Sample/Utilities.h +++ b/examples/LayoutSpecExamples/Sample/Utilities.h @@ -1,11 +1,18 @@ // // Utilities.h -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -18,6 +25,7 @@ @interface UIImage (Additions) - (UIImage *)makeCircularImageWithSize:(CGSize)size withBorderWidth:(CGFloat)width; ++ (UIImage *)imageWithSize:(CGSize)size fillColor:(UIColor *)fillColor shapeBlock:(UIBezierPath *(^)(void))shapeBlock; @end @interface NSAttributedString (Additions) diff --git a/examples/LayoutSpecExamples/Sample/Utilities.m b/examples/LayoutSpecExamples/Sample/Utilities.m index 74b4ae87a6..92e5c4bda7 100644 --- a/examples/LayoutSpecExamples/Sample/Utilities.m +++ b/examples/LayoutSpecExamples/Sample/Utilities.m @@ -1,11 +1,18 @@ // // Utilities.m -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "Utilities.h" @@ -64,6 +71,21 @@ return roundedImage; } ++ (UIImage *)imageWithSize:(CGSize)size fillColor:(UIColor *)fillColor shapeBlock:(UIBezierPath *(^)(void))shapeBlock +{ + UIGraphicsBeginImageContext(size); + [fillColor setFill]; + + UIBezierPath *path = shapeBlock(); + [path addClip]; + [path fill]; + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + @end @implementation NSAttributedString (Additions) diff --git a/examples/README.md b/examples/README.md index e56667c0ce..76c71a75d0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -203,6 +203,20 @@ Featuring: - ASTableView - ASCellNode +### LayoutSpecExamples [ObjC] + +![Layout Spec Example App Screenshot](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/example-app-screenshots/ASCornerLayoutSpec.png) + +Featuring: +- ASStackLayoutSpec +- ASInsetLayoutSpec +- ASOverlayLayoutSpec +- ASAbsoluteLayoutSpec +- ASBackgroundLayoutSpec +- ASCornerLayoutSpec + +There is an associated swift version app: LayoutSpecExamples-Swift with same logic implementation. + ## License This file provided by Facebook is for non-commercial testing and evaluation @@ -214,3 +228,5 @@ Featuring: FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +