[ASCornerLayoutSpec] New layout spec class for declarative corner element layout. (#657)

* Add new layout spec class with snapshot testing. Update examples and CHANGELOG.md

* Code review updates.

* Open curly bracket in a new line.
This commit is contained in:
huang-kun
2017-11-24 21:45:59 +08:00
committed by Huy Nguyen
parent b2539d3531
commit e4b2c05c21
42 changed files with 910 additions and 16 deletions

View File

@@ -33,6 +33,9 @@
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */; }; 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, ); }; }; 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 */; }; 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 */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; };
2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; }; 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */; };
254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */; }; 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, ); }; }; 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, ); }; }; 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, ); }; }; 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 */; }; 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, ); }; }; 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, ); }; }; 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 */; }; 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 = "<group>"; }; 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = "<group>"; };
18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = "<group>"; }; 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = "<group>"; };
1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = "<group>"; }; 1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEqualityHelpers.h; sourceTree = "<group>"; };
1A6C000B1FAB4E2000D05926 /* ASCornerLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCornerLayoutSpec.h; sourceTree = "<group>"; };
1A6C000C1FAB4E2100D05926 /* ASCornerLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCornerLayoutSpec.mm; sourceTree = "<group>"; };
1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCornerLayoutSpecSnapshotTests.mm; sourceTree = "<group>"; };
205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = "<group>"; }; 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionViewLayout+ASConvenience.h"; sourceTree = "<group>"; };
205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionViewLayout+ASConvenience.m"; sourceTree = "<group>"; }; 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionViewLayout+ASConvenience.m"; sourceTree = "<group>"; };
205F0E111B371BD7007741D0 /* ASScrollDirection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollDirection.m; sourceTree = "<group>"; }; 205F0E111B371BD7007741D0 /* ASScrollDirection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollDirection.m; sourceTree = "<group>"; };
@@ -785,8 +791,8 @@
B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutManager.h; path = TextKit/ASLayoutManager.h; sourceTree = "<group>"; }; B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutManager.h; path = TextKit/ASLayoutManager.h; sourceTree = "<group>"; };
B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = "<group>"; }; B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = "<group>"; };
B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; };
BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNavigationControllerTests.m; sourceTree = "<group>"; }; BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNavigationControllerTests.m; sourceTree = "<group>"; };
BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTabBarControllerTests.m; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = "<group>"; }; CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = "<group>"; };
CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = "<group>"; }; CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = "<group>"; };
@@ -1187,6 +1193,7 @@
7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */,
ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */, ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */,
ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */,
1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */,
696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */, 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */,
ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */,
ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */,
@@ -1524,6 +1531,8 @@
ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */, ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */,
ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */, ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */,
ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */, ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */,
1A6C000B1FAB4E2000D05926 /* ASCornerLayoutSpec.h */,
1A6C000C1FAB4E2100D05926 /* ASCornerLayoutSpec.mm */,
ACF6ED071B17843500DA7C62 /* ASDimension.h */, ACF6ED071B17843500DA7C62 /* ASDimension.h */,
ACF6ED081B17843500DA7C62 /* ASDimension.mm */, ACF6ED081B17843500DA7C62 /* ASDimension.mm */,
690C35631E055C7B00069B91 /* ASDimensionInternal.h */, 690C35631E055C7B00069B91 /* ASDimensionInternal.h */,
@@ -1717,6 +1726,7 @@
isa = PBXHeadersBuildPhase; isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1A6C000D1FAB4E2100D05926 /* ASCornerLayoutSpec.h in Headers */,
E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */, E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */,
E5B225281F1790D6001E1431 /* ASHashing.h in Headers */, E5B225281F1790D6001E1431 /* ASHashing.h in Headers */,
CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */, CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */,
@@ -2069,13 +2079,16 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
); );
name = "[CP] Check Pods Manifest.lock"; name = "[CP] Check Pods Manifest.lock";
outputPaths = ( outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-AsyncDisplayKitTests-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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; showEnvVarsInLog = 0;
}; };
3B9D88CDF51B429C8409E4B6 /* [CP] Copy Pods Resources */ = { 3B9D88CDF51B429C8409E4B6 /* [CP] Copy Pods Resources */ = {
@@ -2174,6 +2187,7 @@
ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */,
7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */,
CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */, CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */,
1A6C00111FAB4EDD00D05926 /* ASCornerLayoutSpecSnapshotTests.mm in Sources */,
254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */,
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */,
ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */,
@@ -2334,6 +2348,7 @@
68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */, 68FC85E51CE29B7E00EDD713 /* ASTabBarController.m in Sources */,
CCCCCCDC1EC3EF060087FE10 /* ASTextLine.m in Sources */, CCCCCCDC1EC3EF060087FE10 /* ASTextLine.m in Sources */,
34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */,
1A6C000E1FAB4E2100D05926 /* ASCornerLayoutSpec.mm in Sources */,
CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */, CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */,
690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */,
909C4C761F09C98B00D6B76F /* ASTextNode2.mm in Sources */, 909C4C761F09C98B00D6B76F /* ASTextNode2.mm in Sources */,

View File

@@ -9,6 +9,7 @@
- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler) - 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) - [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) - [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 ## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)

View File

@@ -79,6 +79,7 @@
#import <AsyncDisplayKit/ASLayoutSpec.h> #import <AsyncDisplayKit/ASLayoutSpec.h>
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h> #import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
#import <AsyncDisplayKit/ASCenterLayoutSpec.h> #import <AsyncDisplayKit/ASCenterLayoutSpec.h>
#import <AsyncDisplayKit/ASCornerLayoutSpec.h>
#import <AsyncDisplayKit/ASRelativeLayoutSpec.h> #import <AsyncDisplayKit/ASRelativeLayoutSpec.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h> #import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASOverlayLayoutSpec.h> #import <AsyncDisplayKit/ASOverlayLayoutSpec.h>

View File

@@ -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 <AsyncDisplayKit/ASLayoutSpec.h>
/**
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 <ASLayoutElement>)child corner:(id <ASLayoutElement>)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 <ASLayoutElement>)child corner:(id <ASLayoutElement>)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 <ASLayoutElement> 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

View File

@@ -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 <AsyncDisplayKit/ASCornerLayoutSpec.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASLayoutSpec+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNode.h>
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 <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location
{
self = [super init];
if (self) {
self.child = child;
self.corner = corner;
self.cornerLocation = location;
}
return self;
}
+ (instancetype)cornerLayoutSpecWithChild:(id <ASLayoutElement>)child corner:(id <ASLayoutElement>)corner location:(ASCornerLayoutLocation)location
{
return [[self alloc] initWithChild:child corner:corner location:location];
}
#pragma mark - Children
- (void)setChild:(id<ASLayoutElement>)child
{
ASDisplayNodeAssertNotNil(child, @"Child shouldn't be nil.");
[super setChild:child atIndex:kBaseChildIndex];
}
- (id<ASLayoutElement>)child
{
return [super childAtIndex:kBaseChildIndex];
}
- (void)setCorner:(id<ASLayoutElement>)corner
{
ASDisplayNodeAssertNotNil(corner, @"Corner element cannot be nil.");
[super setChild:corner atIndex:kCornerChildIndex];
}
- (id<ASLayoutElement>)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 <ASLayoutElement> child = self.child;
id <ASLayoutElement> 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 <ASLayoutElement>)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

View File

@@ -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 <AsyncDisplayKit/ASCornerLayoutSpec.h>
#import <AsyncDisplayKit/ASBackgroundLayoutSpec.h>
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -143,3 +143,140 @@ class FlexibleSeparatorSurroundingContent : LayoutExampleNode {
return "try rotating me!" 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."
}
}

View File

@@ -26,7 +26,9 @@ class OverviewViewController: ASViewController<ASTableNode> {
HeaderWithRightAndLeftItems.self, HeaderWithRightAndLeftItems.self,
PhotoWithInsetTextOverlay.self, PhotoWithInsetTextOverlay.self,
PhotoWithOutsetIconOverlay.self, PhotoWithOutsetIconOverlay.self,
FlexibleSeparatorSurroundingContent.self FlexibleSeparatorSurroundingContent.self,
CornerLayoutSample.self,
UserProfileSample.self
] ]
super.init(node: tableNode) super.init(node: tableNode)

View File

@@ -75,6 +75,20 @@ extension UIImage {
return roundedImage ?? self 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 { extension NSAttributedString {

View File

@@ -220,13 +220,16 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
); );
name = "[CP] Check Pods Manifest.lock"; name = "[CP] Check Pods Manifest.lock";
outputPaths = ( outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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; showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Sample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -33,3 +33,9 @@
@interface FlexibleSeparatorSurroundingContent : LayoutExampleNode @interface FlexibleSeparatorSurroundingContent : LayoutExampleNode
@end @end
@interface CornerLayoutExample : PhotoWithOutsetIconOverlay
@end
@interface UserProfileSample : LayoutExampleNode
@end

View File

@@ -260,6 +260,188 @@
@end @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 <ASLayoutElement> 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 @implementation LayoutExampleNode
+ (NSString *)title + (NSString *)title

View File

@@ -1,11 +1,18 @@
// //
// OverviewViewController.m // OverviewViewController.m
// Sample // Texture
// //
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the // 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 // LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// of patent rights can be found in the PATENTS file in the same directory. // 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" #import "OverviewViewController.h"
@@ -37,7 +44,10 @@
_layoutExamples = @[[HeaderWithRightAndLeftItems class], _layoutExamples = @[[HeaderWithRightAndLeftItems class],
[PhotoWithInsetTextOverlay class], [PhotoWithInsetTextOverlay class],
[PhotoWithOutsetIconOverlay class], [PhotoWithOutsetIconOverlay class],
[FlexibleSeparatorSurroundingContent class]]; [FlexibleSeparatorSurroundingContent class],
[CornerLayoutExample class],
[UserProfileSample class]
];
} }
return self; return self;

View File

@@ -1,11 +1,18 @@
// //
// Utilities.h // Utilities.h
// Sample // Texture
// //
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the // 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 // LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// of patent rights can be found in the PATENTS file in the same directory. // 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 <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@@ -18,6 +25,7 @@
@interface UIImage (Additions) @interface UIImage (Additions)
- (UIImage *)makeCircularImageWithSize:(CGSize)size withBorderWidth:(CGFloat)width; - (UIImage *)makeCircularImageWithSize:(CGSize)size withBorderWidth:(CGFloat)width;
+ (UIImage *)imageWithSize:(CGSize)size fillColor:(UIColor *)fillColor shapeBlock:(UIBezierPath *(^)(void))shapeBlock;
@end @end
@interface NSAttributedString (Additions) @interface NSAttributedString (Additions)

View File

@@ -1,11 +1,18 @@
// //
// Utilities.m // Utilities.m
// Sample // Texture
// //
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. // Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the // 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 // LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// of patent rights can be found in the PATENTS file in the same directory. // 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" #import "Utilities.h"
@@ -64,6 +71,21 @@
return roundedImage; 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 @end
@implementation NSAttributedString (Additions) @implementation NSAttributedString (Additions)

View File

@@ -203,6 +203,20 @@ Featuring:
- ASTableView - ASTableView
- ASCellNode - 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 ## License
This file provided by Facebook is for non-commercial testing and evaluation 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 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 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. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.