diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 634a0f5fee..e5101b4a85 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -25,7 +25,8 @@ Pod::Spec.new do |spec| 'AsyncDisplayKit/Details/**/*.h', 'AsyncDisplayKit/Layout/*.h', 'Base/*.h', - 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h', + 'AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h', + 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h', 'AsyncDisplayKit/TextKit/ASTextKitComponents.h' ] diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 86885fe89e..ec5d4d482e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -472,14 +472,24 @@ DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE0702FC1C3671E900D7DE62 /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AC195D04C000B7D73C /* libAsyncDisplayKit.a */; }; - DE4843DB1C93EAB100A1F33B /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; + DE89C1701DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h in Headers */ = {isa = PBXBuildFile; fileRef = DE89C16A1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h */; }; + DE89C1711DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */; }; + DE89C1731DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */; }; + DE89C1741DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DE89C1751DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */; }; + DE89C1771DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */; }; + DE89C1781DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h in Headers */ = {isa = PBXBuildFile; fileRef = DE89C16E1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h */; }; + DE89C1791DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */; }; + DE89C17B1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */ = {isa = PBXBuildFile; fileRef = DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */; }; DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; DE8BEAC31C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */; }; + DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; + DEB8ED7E1DD007F400DBDE55 /* ASLayoutElementInspectorNode.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */; }; DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */; }; DEC146B81C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; DEC146B91C37A16A004A0EE7 /* ASCollectionInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */; }; @@ -662,6 +672,7 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + DEB8ED7E1DD007F400DBDE55 /* ASLayoutElementInspectorNode.h in CopyFiles */, 69127CFE1DD2B387004BF6E2 /* ASEventLog.h in CopyFiles */, 693117CE1DC7C72700DE4784 /* ASDisplayNode+Deprecated.h in CopyFiles */, 69F381A51DA4630D00CF2278 /* NSArray+Diffing.h in CopyFiles */, @@ -1016,8 +1027,8 @@ 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASRangeControllerUpdateRangeProtocol+Beta.h"; sourceTree = ""; }; 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutElementStyleTests.m; sourceTree = ""; }; 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; - 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+Debug.h"; sourceTree = ""; }; - 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+Debug.m"; sourceTree = ""; }; + 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "AsyncDisplayKit+Debug.h"; path = "../AsyncDisplayKit+Debug.h"; sourceTree = ""; }; + 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "AsyncDisplayKit+Debug.m"; path = "../AsyncDisplayKit+Debug.m"; sourceTree = ""; }; 7A06A7381C35F08800FE8DAA /* ASRelativeLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.mm; sourceTree = ""; }; 7A06A7391C35F08800FE8DAA /* ASRelativeLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeLayoutSpec.h; path = AsyncDisplayKit/Layout/ASRelativeLayoutSpec.h; sourceTree = ""; }; 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; @@ -1166,6 +1177,12 @@ DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPagerFlowLayout.h; sourceTree = ""; }; DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPagerFlowLayout.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; + DE89C16A1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementInspectorCell.h; sourceTree = ""; }; + DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutElementInspectorCell.m; sourceTree = ""; }; + DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementInspectorNode.h; sourceTree = ""; }; + DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutElementInspectorNode.m; sourceTree = ""; }; + DE89C16E1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASLayoutSpec+Debug.h"; sourceTree = ""; }; + DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASLayoutSpec+Debug.m"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCollectionInternal.h; path = Details/ASCollectionInternal.h; sourceTree = ""; }; @@ -1360,12 +1377,11 @@ ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */, 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */, 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, - 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */, - 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */, DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */, 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */, 92074A5E1CC8B9DD00918F75 /* tvOS */, + DE89C1691DCEB9CC00D49D74 /* Debug */, 058D09E1195D050800B7D73C /* Details */, 058D0A01195D050800B7D73C /* Private */, AC6456051B0A333200CF11B8 /* Layout */, @@ -1757,6 +1773,21 @@ name = "Supporting Files"; sourceTree = ""; }; + DE89C1691DCEB9CC00D49D74 /* Debug */ = { + isa = PBXGroup; + children = ( + DE89C16A1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h */, + DE89C16B1DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m */, + DE89C16C1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h */, + DE89C16D1DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m */, + DE89C16E1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h */, + DE89C16F1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m */, + 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */, + 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */, + ); + path = Debug; + sourceTree = ""; + }; FD40E2760492F0CAAEAD552D /* Pods */ = { isa = PBXGroup; children = ( @@ -1788,6 +1819,7 @@ 68355B411CB57A6C001D4E68 /* ASImageContainerProtocolCategories.h in Headers */, 7630FFA81C9E267E007A7C0E /* ASVideoNode.h in Headers */, B350623F1B010EFD0018CF92 /* _ASAsyncTransactionContainer.h in Headers */, + DE89C1741DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.h in Headers */, B13CA1011C52004900E031AB /* ASCollectionNode+Beta.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, B35062411B010EFD0018CF92 /* _ASAsyncTransactionGroup.h in Headers */, @@ -1898,6 +1930,7 @@ B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, + DE89C1781DCEB9CC00D49D74 /* ASLayoutSpec+Debug.h in Headers */, DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */, @@ -1925,6 +1958,7 @@ 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, 34EFC7751B701D2400AD841F /* ASStackPositionedLayout.h in Headers */, 69E1006E1CA89CB600D88C1B /* ASEnvironmentInternal.h in Headers */, + DE89C1701DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.h in Headers */, 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */, 9C6BB3B31B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h in Headers */, 34EFC7731B701D0700AD841F /* ASAbsoluteLayoutSpec.h in Headers */, @@ -2152,6 +2186,7 @@ buildActionMask = 2147483647; files = ( 058D0A22195D050800B7D73C /* _ASAsyncTransaction.mm in Sources */, + E52405B31C8FEF03004DC8E7 /* ASLayoutTransition.mm in Sources */, 8B0768B41CE752EC002E1453 /* ASDefaultPlaybackButton.m in Sources */, E55D86321CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, 68FC85E41CE29B7E00EDD713 /* ASTabBarController.m in Sources */, @@ -2185,6 +2220,7 @@ DBC452DC1C5BF64600B16017 /* NSArray+Diffing.m in Sources */, AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */, 9CFFC6C21CCAC768006A6476 /* ASTableNode.mm in Sources */, + DE89C1751DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */, 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, 68FC85EB1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */, 058D0A13195D050800B7D73C /* ASControlNode.mm in Sources */, @@ -2205,6 +2241,7 @@ 0587F9BE1A7309ED00AFF0BA /* ASEditableTextNode.mm in Sources */, 464052231A3F83C40061C0BA /* ASFlowLayoutController.mm in Sources */, 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */, + DE89C1791DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */, 058D0A1A195D050800B7D73C /* ASHighlightOverlayLayer.mm in Sources */, 058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */, CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */, @@ -2231,9 +2268,9 @@ 0442850F1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, 7A06A73A1C35F08800FE8DAA /* ASRelativeLayoutSpec.mm in Sources */, 6907C2591DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */, - E52405B31C8FEF03004DC8E7 /* ASLayoutTransition.mm in Sources */, 69CB62AD1CB8165900024920 /* _ASDisplayViewAccessiblity.mm in Sources */, 257754AB1BEE44CD00737CA5 /* ASTextKitEntityAttribute.m in Sources */, + DE89C1711DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */, 055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */, 044285091BAA63FE00D16268 /* ASBatchFetching.m in Sources */, 257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */, @@ -2339,10 +2376,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + DEB8ED7C1DD003D300DBDE55 /* ASLayoutTransition.mm in Sources */, 9F98C0261DBE29E000476D92 /* ASControlTargetAction.m in Sources */, 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, - DE4843DB1C93EAB100A1F33B /* ASLayoutTransition.mm in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */, @@ -2404,6 +2441,7 @@ B350621E1B010EFD0018CF92 /* ASHighlightOverlayLayer.mm in Sources */, 9CC606651D24DF9E006581A0 /* NSIndexSet+ASHelpers.m in Sources */, 92074A641CC8BA1900918F75 /* ASImageNode+tvOS.m in Sources */, + DE89C17B1DCEB9CC00D49D74 /* ASLayoutSpec+Debug.m in Sources */, B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, @@ -2424,6 +2462,7 @@ B35062071B010EFD0018CF92 /* ASNetworkImageNode.mm in Sources */, 34EFC76D1B701CF100AD841F /* ASOverlayLayoutSpec.mm in Sources */, 044285101BAA64EC00D16268 /* ASMultidimensionalArrayUtils.mm in Sources */, + DE89C1731DCEB9CC00D49D74 /* ASLayoutElementInspectorCell.m in Sources */, B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */, 0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */, 68FC85E61CE29B9400EDD713 /* ASNavigationController.m in Sources */, @@ -2459,6 +2498,7 @@ 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, + DE89C1771DCEB9CC00D49D74 /* ASLayoutElementInspectorNode.m in Sources */, 254C6B8A1BF94F8A003EC431 /* ASTextKitRenderer+TextChecking.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/AsyncDisplayKit/ASControlNode.h b/AsyncDisplayKit/ASControlNode.h index 66e1d7a7a0..d4aa2ff070 100644 --- a/AsyncDisplayKit/ASControlNode.h +++ b/AsyncDisplayKit/ASControlNode.h @@ -10,6 +10,8 @@ #import +#pragma once + NS_ASSUME_NONNULL_BEGIN /** diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index d5a29f7534..32bb359475 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -14,6 +14,8 @@ #import "AsyncDisplayKit+Debug.h" #import "ASInternalHelpers.h" #import "ASControlTargetAction.h" +#import "ASDisplayNode+FrameworkPrivate.h" +#import "ASLayoutElementInspectorNode.h" // UIControl allows dragging some distance outside of the control itself during // tracking. This value depends on the device idiom (25 or 70 points), so @@ -241,6 +243,9 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v { NSParameterAssert(action); NSParameterAssert(controlEventMask != 0); + // This assertion would likely be helpful to users who aren't familiar with the implications of layer-backing. + // However, it would represent an API change (in debug) as it did not used to assert. + // ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair."); ASDN::MutexLocker l(_controlLock); @@ -448,4 +453,21 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v return _debugHighlightOverlay; } +// methods for visualizing ASLayoutSpecs +- (void)setHierarchyState:(ASHierarchyState)hierarchyState +{ + [super setHierarchyState:hierarchyState]; + + if (self.shouldVisualizeLayoutSpecs) { + [self addTarget:self action:@selector(inspectElement) forControlEvents:ASControlNodeEventTouchUpInside]; + } else { + [self removeTarget:self action:@selector(inspectElement) forControlEvents:ASControlNodeEventTouchUpInside]; + } +} + +- (void)inspectElement +{ + [ASLayoutElementInspectorNode sharedInstance].layoutElementToEdit = self; +} + @end diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 2b47c84d3f..dddc908239 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -8,6 +8,8 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#pragma once + #import #import diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 878dced385..c4a55a5f10 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -12,6 +12,7 @@ #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Beta.h" +#import "AsyncDisplayKit+Debug.h" #import @@ -49,6 +50,10 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS @protocol CALayerDelegate; @interface ASDisplayNode () +{ + BOOL _shouldCacheLayoutSpec; + ASLayoutSpec *_layoutSpec; +} /** * @@ -717,6 +722,23 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self cancelLayoutTransition]; + BOOL didCreateNewContext = NO; + BOOL didOverrideExistingContext = NO; + BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); + ASLayoutElementContext context; + if (ASLayoutElementContextIsNull(ASLayoutElementGetCurrentContext())) { + context = ASLayoutElementContextMake(ASLayoutElementContextDefaultTransitionID, shouldVisualizeLayout); + ASLayoutElementSetCurrentContext(context); + didCreateNewContext = YES; + } else { + context = ASLayoutElementGetCurrentContext(); + if (context.needsVisualizeNode != shouldVisualizeLayout) { + context.needsVisualizeNode = shouldVisualizeLayout; + ASLayoutElementSetCurrentContext(context); + didOverrideExistingContext = YES; + } + } + // Prepare for layout transition auto previousLayout = _calculatedDisplayNodeLayout; auto pendingLayout = std::make_shared( @@ -724,6 +746,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) constrainedSize, parentSize ); + + if (didCreateNewContext) { + ASLayoutElementClearCurrentContext(); + } else if (didOverrideExistingContext) { + context.needsVisualizeNode = !context.needsVisualizeNode; + ASLayoutElementSetCurrentContext(context); + } + _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self pendingLayout:pendingLayout previousLayout:previousLayout]; @@ -831,9 +861,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASLayout *newLayout; { - ASLayoutElementSetCurrentContext(ASLayoutElementContextMake(transitionID, NO)); - ASDN::MutexLocker l(__instanceLock__); + + BOOL shouldVisualizeLayout = ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); + ASLayoutElementSetCurrentContext(ASLayoutElementContextMake(transitionID, shouldVisualizeLayout)); + BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO); self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x newLayout = [self calculateLayoutThatFits:constrainedSize @@ -2437,8 +2469,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) // Use an empty layout spec to avoid crashes layoutSpec = [[ASLayoutSpec alloc] init]; } + + if (_shouldCacheLayoutSpec) { + _layoutSpec = layoutSpec; + } else { + ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); + } - ASDisplayNodeAssert(layoutSpec.isMutable, @"Node %@ returned layout spec %@ that has already been used. Layout specs should always be regenerated.", self, layoutSpec); layoutSpec.parent = self; layoutSpec.isMutable = NO; } @@ -2486,7 +2523,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) __ASDisplayNodeCheckForLayoutMethodOverrides; BOOL measureLayoutSpec = _measurementOptions & ASDisplayNodePerformanceMeasurementOptionLayoutSpec; - if (_layoutSpecBlock != NULL) { + + if (_shouldCacheLayoutSpec && _layoutSpec != nil) { + return _layoutSpec; + } else if (_layoutSpecBlock != NULL) { return ({ ASDN::MutexLocker l(__instanceLock__); ASDN::SumScopeTimer t(_layoutSpecTotalTime, measureLayoutSpec); @@ -3700,4 +3740,42 @@ ASLayoutElementStyleForwarding self.automaticallyManagesSubnodes = enabled; } +#pragma mark - ASDisplayNode(LayoutDebugging) + +- (void)setShouldVisualizeLayoutSpecs:(BOOL)shouldVisualizeLayoutSpecs +{ + ASDN::MutexLocker l(__instanceLock__); + if (shouldVisualizeLayoutSpecs != [self shouldVisualizeLayoutSpecs]) { + if (shouldVisualizeLayoutSpecs) { + [self enterHierarchyState:ASHierarchyStateVisualizeLayout]; + } else { + [self exitHierarchyState:ASHierarchyStateVisualizeLayout]; + } + [self setNeedsLayout]; + } +} + +- (BOOL)shouldVisualizeLayoutSpecs +{ + ASDN::MutexLocker l(__instanceLock__); + return ASHierarchyStateIncludesVisualizeLayout(_hierarchyState); +} + +- (void)setShouldCacheLayoutSpec:(BOOL)shouldCacheLayoutSpec +{ + ASDN::MutexLocker l(__instanceLock__); + if (_shouldCacheLayoutSpec != shouldCacheLayoutSpec) { + _shouldCacheLayoutSpec = shouldCacheLayoutSpec; + if (_shouldCacheLayoutSpec == NO) { + _layoutSpec = nil; + } + } +} + +- (BOOL)shouldCacheLayoutSpec +{ + ASDN::MutexLocker l(__instanceLock__); + return _shouldCacheLayoutSpec; +} + @end diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.h b/AsyncDisplayKit/AsyncDisplayKit+Debug.h index 5f6aa72d31..3610d4ecd6 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.h +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.h @@ -10,12 +10,19 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import "ASDisplayNode.h" #import "ASControlNode.h" #import "ASImageNode.h" #import "ASRangeController.h" NS_ASSUME_NONNULL_BEGIN +@interface ASDisplayNode (Visualization) +@property (nonatomic, assign) BOOL shouldVisualizeLayoutSpecs; +@property (nonatomic, assign) BOOL shouldCacheLayoutSpec; + +@end + @interface ASImageNode (Debugging) /** diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h b/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h new file mode 100644 index 0000000000..69bae8c6b9 --- /dev/null +++ b/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.h @@ -0,0 +1,28 @@ +// +// ASLayoutElementInspectorCell.h +// AsyncDisplayKit +// +// Created by Hannah Troisi on 3/27/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +typedef NS_ENUM(NSInteger, ASLayoutElementPropertyType) { + ASLayoutElementPropertyFlexGrow = 0, + ASLayoutElementPropertyFlexShrink, + ASLayoutElementPropertyAlignSelf, + ASLayoutElementPropertyFlexBasis, + ASLayoutElementPropertySpacingBefore, + ASLayoutElementPropertySpacingAfter, + ASLayoutElementPropertyAscender, + ASLayoutElementPropertyDescender, + ASLayoutElementPropertyCount +}; + +@interface ASLayoutElementInspectorCell : ASCellNode + +- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutable NS_DESIGNATED_INITIALIZER; + +@end + diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m b/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m new file mode 100644 index 0000000000..ab5f191f2d --- /dev/null +++ b/AsyncDisplayKit/Debug/ASLayoutElementInspectorCell.m @@ -0,0 +1,569 @@ +// +// ASLayoutElementInspectorCell.m +// AsyncDisplayKit +// +// Created by Hannah Troisi on 3/27/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASLayoutElementInspectorCell.h" +#import + +typedef NS_ENUM(NSInteger, CellDataType) { + CellDataTypeBool, + CellDataTypeFloat, +}; + +__weak static ASLayoutElementInspectorCell *__currentlyOpenedCell = nil; + +@protocol InspectorCellEditingBubbleProtocol +- (void)valueChangedToIndex:(NSUInteger)index; +@end + +@interface ASLayoutElementInspectorCellEditingBubble : ASDisplayNode +@property (nonatomic, strong, readwrite) id delegate; +- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption; +- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current +;@end + +@interface ASLayoutElementInspectorCell () +@end + +@implementation ASLayoutElementInspectorCell +{ + ASLayoutElementPropertyType _propertyType; + CellDataType _dataType; + id _layoutElementToEdit; + + ASButtonNode *_buttonNode; + ASTextNode *_textNode; + ASTextNode *_textNode2; + + ASLayoutElementInspectorCellEditingBubble *_textBubble; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithProperty:(ASLayoutElementPropertyType)property layoutElementToEdit:(id)layoutElement +{ + self = [super init]; + if (self) { + + _propertyType = property; + _dataType = [ASLayoutElementInspectorCell dataTypeForProperty:property]; + _layoutElementToEdit = layoutElement; + + self.automaticallyManagesSubnodes = YES; + + _buttonNode = [self makeBtnNodeWithTitle:[ASLayoutElementInspectorCell propertyStringForPropertyType:property]]; + [_buttonNode addTarget:self action:@selector(buttonTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; + + _textNode = [[ASTextNode alloc] init]; + _textNode.attributedText = [ASLayoutElementInspectorCell propertyValueAttributedStringForProperty:property withLayoutElement:layoutElement]; + + [self updateButtonStateForProperty:property withLayoutElement:layoutElement]; + + _textNode2 = [[ASTextNode alloc] init]; + _textNode2.attributedText = [ASLayoutElementInspectorCell propertyValueDetailAttributedStringForProperty:property withLayoutElement:layoutElement]; + + } + return self; +} + +- (void)updateButtonStateForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement +{ + if (property == ASLayoutElementPropertyFlexGrow) { + _buttonNode.selected = layoutElement.style.flexGrow; + } + else if (property == ASLayoutElementPropertyFlexShrink) { + _buttonNode.selected = layoutElement.style.flexShrink; + } +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASStackLayoutSpec *horizontalSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + horizontalSpec.children = @[_buttonNode, _textNode]; + horizontalSpec.style.flexGrow = 1.0; + horizontalSpec.alignItems = ASStackLayoutAlignItemsCenter; + horizontalSpec.justifyContent = ASStackLayoutJustifyContentSpaceBetween; + + ASLayoutSpec *childSpec; + if (_textBubble) { + ASStackLayoutSpec *verticalSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalSpec.children = @[horizontalSpec, _textBubble]; + verticalSpec.spacing = 8; + verticalSpec.style.flexGrow = 1.0; + _textBubble.style.flexGrow = 1.0; + childSpec = verticalSpec; + } else { + childSpec = horizontalSpec; + } + ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(2, 4, 2, 4) child:childSpec]; + insetSpec.style.flexGrow =1.0; + + return insetSpec; +} + ++ (NSAttributedString *)propertyValueAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement +{ + NSString *valueString; + + switch (property) { + case ASLayoutElementPropertyFlexGrow: + valueString = layoutElement.style.flexGrow ? @"YES" : @"NO"; + break; + case ASLayoutElementPropertyFlexShrink: + valueString = layoutElement.style.flexShrink ? @"YES" : @"NO"; + break; + case ASLayoutElementPropertyAlignSelf: + valueString = [ASLayoutElementInspectorCell alignSelfEnumValueString:layoutElement.style.alignSelf]; + break; + case ASLayoutElementPropertyFlexBasis: + if (layoutElement.style.flexBasis.unit && layoutElement.style.flexBasis.value) { // ENUM TYPE + valueString = [NSString stringWithFormat:@"%0.0f %@", layoutElement.style.flexBasis.value, + [ASLayoutElementInspectorCell ASRelativeDimensionEnumString:layoutElement.style.alignSelf]]; + } else { + valueString = @"0 pts"; + } + break; + case ASLayoutElementPropertySpacingBefore: + valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingBefore]; + break; + case ASLayoutElementPropertySpacingAfter: + valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.spacingAfter]; + break; + case ASLayoutElementPropertyAscender: + valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.ascender]; + break; + case ASLayoutElementPropertyDescender: + valueString = [NSString stringWithFormat:@"%0.0f", layoutElement.style.descender]; + break; + default: + valueString = @"?"; + break; + } + return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; +} + ++ (NSAttributedString *)propertyValueDetailAttributedStringForProperty:(ASLayoutElementPropertyType)property withLayoutElement:(id)layoutElement +{ + NSString *valueString; + + switch (property) { + case ASLayoutElementPropertyFlexGrow: + case ASLayoutElementPropertyFlexShrink: + case ASLayoutElementPropertyAlignSelf: + case ASLayoutElementPropertyFlexBasis: + case ASLayoutElementPropertySpacingBefore: + case ASLayoutElementPropertySpacingAfter: + case ASLayoutElementPropertyAscender: + case ASLayoutElementPropertyDescender: + default: + return nil; + } + return [ASLayoutElementInspectorCell attributedStringFromString:valueString]; +} + +- (void)endEditingValue +{ + _textBubble = nil; + __currentlyOpenedCell = nil; + _buttonNode.selected = NO; + [self setNeedsLayout]; +} + +- (void)beginEditingValue +{ + _textBubble.delegate = self; + __currentlyOpenedCell = self; + [self setNeedsLayout]; +} + +- (void)valueChangedToIndex:(NSUInteger)index +{ + switch (_propertyType) { + + case ASLayoutElementPropertyAlignSelf: + _layoutElementToEdit.style.alignSelf = index; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[ASLayoutElementInspectorCell alignSelfEnumValueString:index]]; + break; + + case ASLayoutElementPropertySpacingBefore: + _layoutElementToEdit.style.spacingBefore = (CGFloat)index; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; + break; + + case ASLayoutElementPropertySpacingAfter: + _layoutElementToEdit.style.spacingAfter = (CGFloat)index; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; + break; + + case ASLayoutElementPropertyAscender: + _layoutElementToEdit.style.ascender = (CGFloat)index; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; + break; + + case ASLayoutElementPropertyDescender: + _layoutElementToEdit.style.descender = (CGFloat)index; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; + break; + + default: + break; + } + + [self setNeedsLayout]; +} + +#pragma mark - gesture handling + +- (void)buttonTapped:(ASButtonNode *)sender +{ + BOOL selfIsEditing = (self == __currentlyOpenedCell); + [__currentlyOpenedCell endEditingValue]; + if (selfIsEditing) { + sender.selected = NO; + return; + } + +// NSUInteger currentAlignSelfValue; +// NSUInteger nextAlignSelfValue; +// CGFloat newValue; + + sender.selected = !sender.selected; + switch (_propertyType) { + + case ASLayoutElementPropertyFlexGrow: + _layoutElementToEdit.style.flexGrow = sender.isSelected ? 1.0 : 0.0; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; + break; + + case ASLayoutElementPropertyFlexShrink: + _layoutElementToEdit.style.flexShrink = sender.isSelected ? 1.0 : 0.0; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:sender.selected ? @"YES" : @"NO"]; + break; + + case ASLayoutElementPropertyAlignSelf: + _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithEnumOptions:YES + enumStrings:[ASLayoutElementInspectorCell alignSelfEnumStringArray] + currentOptionIndex:_layoutElementToEdit.style.alignSelf]; + + [self beginEditingValue]; +// if ([self layoutSpec]) { +// currentAlignSelfValue = [[self layoutSpec] alignSelf]; +// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; +// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; +// +// } else if ([self node]) { +// currentAlignSelfValue = [[self node] alignSelf]; +// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; +// [[self node] setAlignSelf:nextAlignSelfValue]; +// } + break; + + case ASLayoutElementPropertySpacingBefore: + _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingBefore]; + [self beginEditingValue]; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingBefore]]; + break; + + case ASLayoutElementPropertySpacingAfter: + _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.spacingAfter]; + [self beginEditingValue]; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.spacingAfter]]; + break; + + + case ASLayoutElementPropertyAscender: + _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.ascender]; + [self beginEditingValue]; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.ascender]]; + break; + + case ASLayoutElementPropertyDescender: + _textBubble = [[ASLayoutElementInspectorCellEditingBubble alloc] initWithSliderMinValue:0 maxValue:100 currentValue:_layoutElementToEdit.style.descender]; + [self beginEditingValue]; + _textNode.attributedText = [ASLayoutElementInspectorCell attributedStringFromString:[NSString stringWithFormat:@"%0.0f", _layoutElementToEdit.style.descender]]; + break; + + default: + break; + } + [self setNeedsLayout]; +} + +#pragma mark - cast layoutElementToEdit + +- (ASDisplayNode *)node +{ + if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeDisplayNode) { + return (ASDisplayNode *)_layoutElementToEdit; + } + return nil; +} + +- (ASLayoutSpec *)layoutSpec +{ + if (_layoutElementToEdit.layoutElementType == ASLayoutElementTypeLayoutSpec) { + return (ASLayoutSpec *)_layoutElementToEdit; + } + return nil; +} + +#pragma mark - data / property type helper methods + ++ (CellDataType)dataTypeForProperty:(ASLayoutElementPropertyType)property +{ + switch (property) { + + case ASLayoutElementPropertyFlexGrow: + case ASLayoutElementPropertyFlexShrink: + return CellDataTypeBool; + + case ASLayoutElementPropertySpacingBefore: + case ASLayoutElementPropertySpacingAfter: + case ASLayoutElementPropertyAscender: + case ASLayoutElementPropertyDescender: + return CellDataTypeFloat; + + default: + break; + } + return CellDataTypeBool; +} + ++ (NSString *)propertyStringForPropertyType:(ASLayoutElementPropertyType)property +{ + NSString *string; + switch (property) { + case ASLayoutElementPropertyFlexGrow: + string = @"FlexGrow"; + break; + case ASLayoutElementPropertyFlexShrink: + string = @"FlexShrink"; + break; + case ASLayoutElementPropertyAlignSelf: + string = @"AlignSelf"; + break; + case ASLayoutElementPropertyFlexBasis: + string = @"FlexBasis"; + break; + case ASLayoutElementPropertySpacingBefore: + string = @"SpacingBefore"; + break; + case ASLayoutElementPropertySpacingAfter: + string = @"SpacingAfter"; + break; + case ASLayoutElementPropertyAscender: + string = @"Ascender"; + break; + case ASLayoutElementPropertyDescender: + string = @"Descender"; + break; + default: + string = @"Unknown"; + break; + } + return string; +} + ++ (NSDictionary *)alignSelfTypeNames +{ + return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", + @(ASStackLayoutAlignSelfStart) : @"Start", + @(ASStackLayoutAlignSelfEnd) : @"End", + @(ASStackLayoutAlignSelfCenter) : @"Center", + @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; +} + ++ (NSString *)alignSelfEnumValueString:(NSUInteger)type +{ + return [[self class] alignSelfTypeNames][@(type)]; +} + ++ (NSArray *)alignSelfEnumStringArray +{ + return @[@"ASStackLayoutAlignSelfAuto", + @"ASStackLayoutAlignSelfStart", + @"ASStackLayoutAlignSelfEnd", + @"ASStackLayoutAlignSelfCenter", + @"ASStackLayoutAlignSelfStretch"]; +} + ++ (NSDictionary *)ASRelativeDimensionTypeNames +{ + return @{@(ASDimensionUnitPoints) : @"pts", + @(ASDimensionUnitFraction) : @"%"}; +} + ++ (NSString *)ASRelativeDimensionEnumString:(NSUInteger)type +{ + return [[self class] ASRelativeDimensionTypeNames][@(type)]; +} + +#pragma mark - formatting helper methods + ++ (NSAttributedString *)attributedStringFromString:(NSString *)string +{ + return [ASLayoutElementInspectorCell attributedStringFromString:string withTextColor:[UIColor whiteColor]]; +} + ++ (NSAttributedString *)attributedStringFromString:(NSString *)string withTextColor:(nullable UIColor *)color +{ + NSDictionary *attributes = @{NSForegroundColorAttributeName : color, + NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; + + return [[NSAttributedString alloc] initWithString:string attributes:attributes]; +} + +- (ASButtonNode *)makeBtnNodeWithTitle:(NSString *)title +{ + UIColor *orangeColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; + UIImage *orangeStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:orangeColor + borderColor:[UIColor whiteColor] + borderWidth:3]; + UIImage *greyStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor darkGrayColor] + borderColor:[UIColor lightGrayColor] + borderWidth:3]; + UIImage *clearStretchBtnImg = [ASLayoutElementInspectorCell imageForButtonWithBackgroundColor:[UIColor clearColor] + borderColor:[UIColor whiteColor] + borderWidth:3]; + ASButtonNode *btn = [[ASButtonNode alloc] init]; + btn.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5); + [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title] forState:ASControlStateNormal]; + [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:title withTextColor:[UIColor lightGrayColor]] forState:ASControlStateDisabled]; + [btn setBackgroundImage:clearStretchBtnImg forState:ASControlStateNormal]; + [btn setBackgroundImage:orangeStretchBtnImg forState:ASControlStateSelected]; + [btn setBackgroundImage:greyStretchBtnImg forState:ASControlStateDisabled]; + + return btn; +} + +#define CORNER_RADIUS 3 ++ (UIImage *)imageForButtonWithBackgroundColor:(UIColor *)backgroundColor borderColor:(UIColor *)borderColor borderWidth:(CGFloat)width +{ + CGSize unstretchedSize = CGSizeMake(2 * CORNER_RADIUS + 1, 2 * CORNER_RADIUS + 1); + CGRect rect = (CGRect) {CGPointZero, unstretchedSize}; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:CORNER_RADIUS]; + + // create a graphics context for the following status button + UIGraphicsBeginImageContextWithOptions(unstretchedSize, NO, 0); + + [path addClip]; + [backgroundColor setFill]; + [path fill]; + + path.lineWidth = width; + [borderColor setStroke]; + [path stroke]; + + UIImage *btnImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return [btnImage stretchableImageWithLeftCapWidth:CORNER_RADIUS topCapHeight:CORNER_RADIUS]; +} + +@end + + + +@implementation ASLayoutElementInspectorCellEditingBubble +{ + NSMutableArray *_textNodes; + ASDisplayNode *_slider; +} + +- (instancetype)initWithEnumOptions:(BOOL)yes enumStrings:(NSArray *)options currentOptionIndex:(NSUInteger)currentOption +{ + self = [super init]; + if (self) { + self.automaticallyManagesSubnodes = YES; + self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; + + _textNodes = [[NSMutableArray alloc] init]; + int index = 0; + for (NSString *optionStr in options) { + ASButtonNode *btn = [[ASButtonNode alloc] init]; + [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr] forState:ASControlStateNormal]; + [btn setAttributedTitle:[ASLayoutElementInspectorCell attributedStringFromString:optionStr withTextColor:[UIColor redColor]] + forState:ASControlStateSelected]; + [btn addTarget:self action:@selector(enumOptionSelected:) forControlEvents:ASControlNodeEventTouchUpInside]; + btn.selected = (index == currentOption) ? YES : NO; + [_textNodes addObject:btn]; + index++; + } + } + return self; +} + +- (instancetype)initWithSliderMinValue:(CGFloat)min maxValue:(CGFloat)max currentValue:(CGFloat)current +{ + if (self = [super init]) { + self.userInteractionEnabled = YES; + self.automaticallyManagesSubnodes = YES; + self.backgroundColor = [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; + + __weak id weakSelf = self; + _slider = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ + UISlider *slider = [[UISlider alloc] init]; + slider.minimumValue = min; + slider.maximumValue = max; + slider.value = current; + [slider addTarget:weakSelf action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged]; + + return slider; + }]; + _slider.userInteractionEnabled = YES; + } + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + _slider.style.preferredSize = CGSizeMake(constrainedSize.max.width, 25); + + NSMutableArray *children = [[NSMutableArray alloc] init]; + if (_textNodes) { + ASStackLayoutSpec *textStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + textStack.children = _textNodes; + textStack.spacing = 2; + [children addObject:textStack]; + } + if (_slider) { + _slider.style.flexGrow = 1.0; + [children addObject:_slider]; + } + + ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStackSpec.children = children; + verticalStackSpec.spacing = 2; + verticalStackSpec.style.flexGrow = 1.0; + verticalStackSpec.style.alignSelf = ASStackLayoutAlignSelfStretch; + + ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(8, 8, 8, 8) child:verticalStackSpec]; + + return insetSpec; +} + +#pragma mark - gesture handling +- (void)enumOptionSelected:(ASButtonNode *)sender +{ + sender.selected = !sender.selected; + for (ASButtonNode *node in _textNodes) { + if (node != sender) { + node.selected = NO; + } + } + [self.delegate valueChangedToIndex:[_textNodes indexOfObject:sender]]; + [self setNeedsLayout]; +} + +- (void)sliderValueChanged:(UISlider *)sender +{ + [self.delegate valueChangedToIndex:roundf(sender.value)]; +} + +@end + + diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h b/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h new file mode 100644 index 0000000000..5a27945329 --- /dev/null +++ b/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h @@ -0,0 +1,25 @@ +// +// ASLayoutElementInspectorNode.h +// Sample +// +// Created by Hannah Troisi on 3/19/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@protocol ASLayoutElementInspectorNodeDelegate + +- (void)toggleVisualization:(BOOL)toggle; + +@end + +@interface ASLayoutElementInspectorNode : ASDisplayNode + +@property (nonatomic, strong) id layoutElementToEdit; +@property (nonatomic, strong) id delegate; +@property (nonatomic, assign) CGFloat vizNodeInsetSize; + ++ (instancetype)sharedInstance; + +@end diff --git a/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m b/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m new file mode 100644 index 0000000000..58be55813a --- /dev/null +++ b/AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.m @@ -0,0 +1,406 @@ +// +// ASLayoutElementInspectorNode.m +// Sample +// +// Created by Hannah Troisi on 3/19/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASLayoutElementInspectorNode.h" +#import "ASLayoutElementInspectorCell.h" +#import "ASDisplayNode+Beta.h" +#import "ASLayoutSpec+Debug.h" +#import + +@interface ASLayoutElementInspectorNode () +@end + +@implementation ASLayoutElementInspectorNode +{ + ASTableNode *_tableNode; +} + +#pragma mark - class methods ++ (instancetype)sharedInstance +{ + static ASLayoutElementInspectorNode *__inspector = nil; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __inspector = [[ASLayoutElementInspectorNode alloc] init]; + }); + + return __inspector; +} + +#pragma mark - lifecycle +- (instancetype)init +{ + self = [super init]; + if (self) { + + _tableNode = [[ASTableNode alloc] init]; + _tableNode.delegate = self; + _tableNode.dataSource = self; + + [self addSubnode:_tableNode]; // required because of manual layout + } + return self; +} + +- (void)didLoad +{ + [super didLoad]; + _tableNode.view.backgroundColor = [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1]; + _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableNode.view.allowsSelection = NO; + _tableNode.view.sectionHeaderHeight = 40; +} + +- (void)layout +{ + [super layout]; + _tableNode.frame = self.bounds; +} + +#pragma mark - intstance methods +- (void)setLayoutElementToEdit:(id)layoutElementToEdit +{ + if (_layoutElementToEdit != layoutElementToEdit) { + _layoutElementToEdit = layoutElementToEdit; + } + [_tableNode reloadData]; +} + +#pragma mark - ASTableDataSource + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.section == 0) { + NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1], + NSFontAttributeName : [UIFont fontWithName:@"Menlo-Regular" size:12]}; + ASTextCellNode *textCell = [[ASTextCellNode alloc] initWithAttributes:attributes insets:UIEdgeInsetsMake(0, 4, 0, 0)]; + textCell.text = [_layoutElementToEdit description]; + return textCell; + } else { + return [[ASLayoutElementInspectorCell alloc] initWithProperty:(ASLayoutElementPropertyType)indexPath.row layoutElementToEdit:_layoutElementToEdit]; + } +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + if (section == 0) { + return 1; + } else { + return ASLayoutElementPropertyCount; + } +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 2; +} + +- (nullable UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section +{ + UILabel *headerTitle = [[UILabel alloc] initWithFrame:CGRectZero]; + + NSString *title; + if (section == 0) { + title = @" Item"; + } else { + title = @" Properties"; + } + + NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], + NSFontAttributeName : [UIFont fontWithName:@"Menlo-Bold" size:12]}; + headerTitle.attributedText = [[NSAttributedString alloc] initWithString:title attributes:attributes]; + + return headerTitle; +} + +//- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +//{ +// // navigate layout hierarchy +// +// _parentNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; +// _childNodeNavBtn.alignSelf = ASStackLayoutAlignSelfCenter; +// +// ASStackLayoutSpec *horizontalStackNav = [ASStackLayoutSpec horizontalStackLayoutSpec]; +// horizontalStackNav.style.flexGrow = 1.0; +// horizontalStackNav.alignSelf = ASStackLayoutAlignSelfCenter; +// horizontalStackNav.children = @[_siblingNodeLefttNavBtn, _siblingNodeRightNavBtn]; +// +// ASStackLayoutSpec *horizontalStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; +// horizontalStack.style.flexGrow = 1.0; +// ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; +// +// spacer.style.flexGrow = 1.0; +// horizontalStack.children = @[_flexGrowBtn, spacer]; +// _flexGrowValue.alignSelf = ASStackLayoutAlignSelfEnd; // FIXME: make framework give a warning if you use ASAlignmentBottom!!!!! +// +// ASStackLayoutSpec *horizontalStack2 = [ASStackLayoutSpec horizontalStackLayoutSpec]; +// horizontalStack2.style.flexGrow = 1.0; +// horizontalStack2.children = @[_flexShrinkBtn, spacer]; +// _flexShrinkValue.alignSelf = ASStackLayoutAlignSelfEnd; +// +// ASStackLayoutSpec *horizontalStack3 = [ASStackLayoutSpec horizontalStackLayoutSpec]; +// horizontalStack3.style.flexGrow = 1.0; +// horizontalStack3.children = @[_flexBasisBtn, spacer, _flexBasisValue]; +// _flexBasisValue.alignSelf = ASStackLayoutAlignSelfEnd; +// +// ASStackLayoutSpec *itemDescriptionStack = [ASStackLayoutSpec verticalStackLayoutSpec]; +// itemDescriptionStack.children = @[_itemDescription]; +// itemDescriptionStack.spacing = 5; +// itemDescriptionStack.style.flexGrow = 1.0; +// +// ASStackLayoutSpec *layoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; +// layoutableStack.children = @[_layoutablePropertiesSectionTitle, horizontalStack, horizontalStack2, horizontalStack3, _alignSelfBtn]; +// layoutableStack.spacing = 5; +// layoutableStack.style.flexGrow = 1.0; +// +// ASStackLayoutSpec *layoutSpecStack = [ASStackLayoutSpec verticalStackLayoutSpec]; +// layoutSpecStack.children = @[_layoutSpecPropertiesSectionTitle, _alignItemsBtn]; +// layoutSpecStack.spacing = 5; +// layoutSpecStack.style.flexGrow = 1.0; +// +// ASStackLayoutSpec *debugHelpStack = [ASStackLayoutSpec verticalStackLayoutSpec]; +// debugHelpStack.children = @[_debugSectionTitle, _vizNodeInsetSizeBtn, _vizNodeBordersBtn]; +// debugHelpStack.spacing = 5; +// debugHelpStack.style.flexGrow = 1.0; +// +// ASStackLayoutSpec *verticalLayoutableStack = [ASStackLayoutSpec verticalStackLayoutSpec]; +// verticalLayoutableStack.style.flexGrow = 1.0; +// verticalLayoutableStack.spacing = 20; +// verticalLayoutableStack.children = @[_parentNodeNavBtn, horizontalStackNav, _childNodeNavBtn, itemDescriptionStack, layoutableStack, layoutSpecStack, debugHelpStack]; +// verticalLayoutableStack.alignItems = ASStackLayoutAlignItemsStretch; // stretch headerStack to fill horizontal space +// +// ASLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(100, 10, 10, 10) child:verticalLayoutableStack]; +// insetSpec.style.flexGrow = 1.0; +// return insetSpec; +//} +// +//#pragma mark - configure Inspector node for layoutable +//- (void)updateInspectorWithLayoutable +//{ +// _itemDescription.attributedText = [self attributedStringFromLayoutable:_layoutElementToEdit]; +// +// if ([self node]) { +// UIColor *nodeBackgroundColor = [[self node] backgroundColor]; +// UIImage *colorBtnImg = [ASLayoutElementInspectorNode imageForButtonWithBackgroundColor:nodeBackgroundColor +// borderColor:[UIColor whiteColor] +// borderWidth:3]; +// [_itemBackgroundColorBtn setBackgroundImage:colorBtnImg forState:ASControlStateNormal]; +// } else { +// _itemBackgroundColorBtn.enabled = NO; +// } +// +// _flexGrowBtn.selected = [self.layoutElementToEdit flexGrow]; +// _flexGrowValue.attributedText = [self attributedStringFromString: (_flexGrowBtn.selected) ? @"YES" : @"NO"]; +// +// _flexShrinkBtn.selected = self.layoutElementToEdit.style.flexShrink; +// _flexShrinkValue.attributedText = [self attributedStringFromString: (_flexShrinkBtn.selected) ? @"YES" : @"NO"]; +// +// // _flexBasisBtn.selected = self.layoutElementToEdit.style.flexShrink; +// // _flexBasisValue.attributedText = [self attributedStringFromString: (_flexBasisBtn.selected) ? @"YES" : @"NO"]; +// +// +// NSUInteger alignSelfValue = [self.layoutElementToEdit alignSelf]; +// NSString *newTitle = [@"alignSelf:" stringByAppendingString:[self alignSelfName:alignSelfValue]]; +// [_alignSelfBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; +// +// if ([self layoutSpec]) { +// _alignItemsBtn.enabled = YES; +//// NSUInteger alignItemsValue = [[self layoutSpec] alignItems]; +//// newTitle = [@"alignItems:" stringByAppendingString:[self alignSelfName:alignItemsValue]]; +//// [_alignItemsBtn setAttributedTitle:[self attributedStringFromString:newTitle] forState:ASControlStateNormal]; +// } +// +// [self setNeedsLayout]; +//} + + +//- (void)enableInspectorNodesForLayoutable +//{ +// if ([self layoutSpec]) { +// +// _itemBackgroundColorBtn.enabled = YES; +// _flexGrowBtn.enabled = YES; +// _flexShrinkBtn.enabled = YES; +// _flexBasisBtn.enabled = YES; +// _alignSelfBtn.enabled = YES; +// _spacingBeforeBtn.enabled = YES; +// _spacingAfterBtn.enabled = YES; +// _alignItemsBtn.enabled = YES; +// +// } else if ([self node]) { +// +// _itemBackgroundColorBtn.enabled = YES; +// _flexGrowBtn.enabled = YES; +// _flexShrinkBtn.enabled = YES; +// _flexBasisBtn.enabled = YES; +// _alignSelfBtn.enabled = YES; +// _spacingBeforeBtn.enabled = YES; +// _spacingAfterBtn.enabled = YES; +// _alignItemsBtn.enabled = NO; +// +// } else { +// +// _itemBackgroundColorBtn.enabled = NO; +// _flexGrowBtn.enabled = NO; +// _flexShrinkBtn.enabled = NO; +// _flexBasisBtn.enabled = NO; +// _alignSelfBtn.enabled = NO; +// _spacingBeforeBtn.enabled = NO; +// _spacingAfterBtn.enabled = NO; +// _alignItemsBtn.enabled = YES; +// } +//} + +//+ (NSDictionary *)alignSelfTypeNames +//{ +// return @{@(ASStackLayoutAlignSelfAuto) : @"Auto", +// @(ASStackLayoutAlignSelfStart) : @"Start", +// @(ASStackLayoutAlignSelfEnd) : @"End", +// @(ASStackLayoutAlignSelfCenter) : @"Center", +// @(ASStackLayoutAlignSelfStretch) : @"Stretch"}; +//} +// +//- (NSString *)alignSelfName:(NSUInteger)type +//{ +// return [[self class] alignSelfTypeNames][@(type)]; +//} +// +//+ (NSDictionary *)alignItemTypeNames +//{ +// return @{@(ASStackLayoutAlignItemsBaselineFirst) : @"BaselineFirst", +// @(ASStackLayoutAlignItemsBaselineLast) : @"BaselineLast", +// @(ASStackLayoutAlignItemsCenter) : @"Center", +// @(ASStackLayoutAlignItemsEnd) : @"End", +// @(ASStackLayoutAlignItemsStart) : @"Start", +// @(ASStackLayoutAlignItemsStretch) : @"Stretch"}; +//} +// +//- (NSString *)alignItemName:(NSUInteger)type +//{ +// return [[self class] alignItemTypeNames][@(type)]; +//} + +//#pragma mark - gesture handling +//- (void)changeColor:(ASButtonNode *)sender +//{ +// if ([self node]) { +// NSArray *colorArray = @[[UIColor orangeColor], +// [UIColor redColor], +// [UIColor greenColor], +// [UIColor purpleColor]]; +// +// UIColor *nodeBackgroundColor = [(ASDisplayNode *)self.layoutElementToEdit backgroundColor]; +// +// NSUInteger colorIndex = [colorArray indexOfObject:nodeBackgroundColor]; +// colorIndex = (colorIndex + 1 < [colorArray count]) ? colorIndex + 1 : 0; +// +// [[self node] setBackgroundColor: [colorArray objectAtIndex:colorIndex]]; +// } +// +// [self updateInspectorWithLayoutable]; +//} +// +//- (void)setFlexGrowValue:(ASButtonNode *)sender +//{ +// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, +// +// if ([self layoutSpec]) { +// [[self layoutSpec] setFlexGrow:sender.isSelected]; +// } else if ([self node]) { +// [[self node] setFlexGrow:sender.isSelected]; +// } +// +// [self updateInspectorWithLayoutable]; +//} +// +//- (void)setFlexShrinkValue:(ASButtonNode *)sender +//{ +// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, +// +// if ([self layoutSpec]) { +// [[self layoutSpec] setFlexShrink:sender.isSelected]; +// } else if ([self node]) { +// [[self node] setFlexShrink:sender.isSelected]; +// } +// +// [self updateInspectorWithLayoutable]; +//} +// +//- (void)setAlignSelfValue:(ASButtonNode *)sender +//{ +// NSUInteger currentAlignSelfValue; +// NSUInteger nextAlignSelfValue; +// +// if ([self layoutSpec]) { +// currentAlignSelfValue = [[self layoutSpec] alignSelf]; +// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; +// [[self layoutSpec] setAlignSelf:nextAlignSelfValue]; +// +// } else if ([self node]) { +// currentAlignSelfValue = [[self node] alignSelf]; +// nextAlignSelfValue = (currentAlignSelfValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignSelfValue + 1 : 0; +// [[self node] setAlignSelf:nextAlignSelfValue]; +// } +// +// [self updateInspectorWithLayoutable]; +//} +// +//- (void)setAlignItemsValue:(ASButtonNode *)sender +//{ +// NSUInteger currentAlignItemsValue; +// NSUInteger nextAlignItemsValue; +// +// if ([self layoutSpec]) { +// currentAlignItemsValue = [[self layoutSpec] alignSelf]; +// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; +//// [[self layoutSpec] setAlignItems:nextAlignItemsValue]; +// +// } else if ([self node]) { +// currentAlignItemsValue = [[self node] alignSelf]; +// nextAlignItemsValue = (currentAlignItemsValue + 1 <= ASStackLayoutAlignSelfStretch) ? currentAlignItemsValue + 1 : 0; +//// [[self node] setAlignItems:nextAlignItemsValue]; +// } +// +// [self updateInspectorWithLayoutable]; +//} +//- (void)setFlexBasisValue:(ASButtonNode *)sender +//{ +// [sender setSelected:!sender.isSelected]; // FIXME: fix ASControlNode documentation that this is automatic - unlike highlighted, it is up to the application to decide when a button should be selected or not. Selected is a more persistant thing and highlighted is for the moment, like as a user has a finger on it, +// FIXME: finish +//} +// +//- (void)setVizNodeInsets:(ASButtonNode *)sender +//{ +// BOOL newState = !sender.selected; +// +// if (newState == YES) { +// self.vizNodeInsetSize = 0; +// [self.delegate toggleVisualization:NO]; // FIXME +// [self.delegate toggleVisualization:YES]; // FIXME +// _vizNodeBordersBtn.selected = YES; +// +// } else { +// self.vizNodeInsetSize = 10; +// [self.delegate toggleVisualization:NO]; // FIXME +// [self.delegate toggleVisualization:YES]; // FIXME +// } +// +// sender.selected = newState; +//} +// +//- (void)setVizNodeBorders:(ASButtonNode *)sender +//{ +// BOOL newState = !sender.selected; +// +// [self.delegate toggleVisualization:newState]; // FIXME +// +// sender.selected = newState; +//} + +@end diff --git a/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.h b/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.h new file mode 100644 index 0000000000..a6fc0efa02 --- /dev/null +++ b/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.h @@ -0,0 +1,21 @@ +// +// ASLayoutSpec+Debug.h +// AsyncDisplayKit +// +// Created by Hannah Troisi on 3/20/16. +// +// + +#pragma once +#import "ASControlNode.h" + +@class ASLayoutSpec; + +@interface ASLayoutSpecVisualizerNode : ASControlNode + +@property (nonatomic, strong) ASLayoutSpec *layoutSpec; + +- (instancetype)initWithLayoutSpec:(ASLayoutSpec *)layoutSpec; + +@end + diff --git a/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.m b/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.m new file mode 100644 index 0000000000..78549bd636 --- /dev/null +++ b/AsyncDisplayKit/Debug/ASLayoutSpec+Debug.m @@ -0,0 +1,73 @@ +// +// ASLayoutSpec+Debug.m +// AsyncDisplayKit +// +// Created by Hannah Troisi on 3/20/16. +// +// + +#import "ASLayoutSpec+Debug.h" +#import "ASDisplayNode+Beta.h" +#import "AsyncDisplayKit.h" +#import "ASLayoutElementInspectorNode.h" + +@implementation ASLayoutSpecVisualizerNode + +- (instancetype)initWithLayoutSpec:(ASLayoutSpec *)layoutSpec +{ + self = [super init]; + if (self) { + self.borderWidth = 2; + self.borderColor = [[UIColor redColor] CGColor]; + self.layoutSpec = layoutSpec; + self.layoutSpec.neverShouldVisualize = YES; + self.automaticallyManagesSubnodes = YES; + self.shouldCacheLayoutSpec = YES; + [self addTarget:self action:@selector(visualizerNodeTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; + } + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + CGFloat insetFloat = [ASLayoutElementInspectorNode sharedInstance].vizNodeInsetSize; + UIEdgeInsets insets = UIEdgeInsetsMake(insetFloat, insetFloat, insetFloat, insetFloat); + + // FIXME in framework: auto pass properties to children + ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:self.layoutSpec]; + insetSpec.neverShouldVisualize = YES; + + // propogate child's layoutSpec properties to the inset that we are adding +// insetSpec.style.flexGrow = _layoutSpec.style.flexGrow; +// insetSpec.style.flexShrink = _layoutSpec.style.flexShrink; +// insetSpec.alignSelf = _layoutSpec.alignSelf; + +// NSLog(@"%@: vizNode = %f, child = %f", self, insetSpec.style.flexGrow, _layoutSpec.style.flexGrow); + + return insetSpec; +} + +- (void)setLayoutSpec:(ASLayoutSpec *)layoutSpec // FIXME: this is duplicated in InspectorNode - make it a category on ASLayoutSpec? +{ + _layoutSpec = layoutSpec; // FIXME: should copy layoutSpec properities to self? + + if ([layoutSpec isKindOfClass:[ASInsetLayoutSpec class]]) { + self.borderColor = [[UIColor redColor] CGColor]; + + } else if ([layoutSpec isKindOfClass:[ASStackLayoutSpec class]]) { + self.borderColor = [[UIColor greenColor] CGColor]; + } +} + +- (NSString *)description +{ + return [self.layoutSpec description]; // FIXME: expand on layoutSpec description (e.g. have StackLayoutSpec return horz/vert) +} + +- (void)visualizerNodeTapped:(UIGestureRecognizer *)sender +{ + [[ASLayoutElementInspectorNode sharedInstance] setLayoutElementToEdit:self.layoutSpec]; +} + +@end + diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h index 44c662f202..6fd5c6b1d7 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -60,6 +60,10 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, strong, nonatomic) NSArray> *children; +@property (nonatomic, assign) BOOL shouldVisualize; +@property (nonatomic, assign) BOOL neverShouldVisualize; +- (void)recursivelySetShouldVisualize:(BOOL)visualize; + @end /** diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.mm b/AsyncDisplayKit/Layout/ASLayoutSpec.mm index a6be4feb2c..f6e1d94a05 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.mm @@ -12,6 +12,11 @@ #import "ASLayoutSpecPrivate.h" #import "ASLayoutSpec+Subclasses.h" #import "ASLayoutElementStylePrivate.h" +#import "ASLayoutSpec+Debug.h" + +#import +#import +#import @implementation ASLayoutSpec @@ -60,7 +65,34 @@ - (id)finalLayoutElement { - return self; + if (ASLayoutElementGetCurrentContext().needsVisualizeNode && !self.neverShouldVisualize) { + return [[ASLayoutSpecVisualizerNode alloc] initWithLayoutSpec:self]; + } else { + return self; + } +} + +- (void)recursivelySetShouldVisualize:(BOOL)visualize +{ + NSMutableArray *mutableChildren = [self.children mutableCopy]; + + for (idlayoutElement in self.children) { + if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { + ASLayoutSpec *layoutSpec = (ASLayoutSpec *)layoutElement; + + [mutableChildren replaceObjectAtIndex:[mutableChildren indexOfObjectIdenticalTo:layoutSpec] + withObject:[[ASLayoutSpecVisualizerNode alloc] initWithLayoutSpec:layoutSpec]]; + + [layoutSpec recursivelySetShouldVisualize:visualize]; + layoutSpec.shouldVisualize = visualize; + } + } + + if ([mutableChildren count] == 1) { // HACK for wrapper layoutSpecs (e.g. insetLayoutSpec) + self.child = mutableChildren[0]; + } else if ([mutableChildren count] > 1) { + self.children = mutableChildren; + } } #pragma mark - Style @@ -111,8 +143,11 @@ { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); ASDisplayNodeAssert(_childrenArray.count < 2, @"This layout spec does not support more than one child. Use the setChildren: or the setChild:AtIndex: API"); - + if (child) { + if (child.layoutElementType == ASLayoutElementTypeLayoutSpec) { + [(ASLayoutSpec *)child setShouldVisualize:self.shouldVisualize]; + } id finalLayoutElement = [self layoutElementToAddFromLayoutElement:child]; if (finalLayoutElement) { _childrenArray[0] = finalLayoutElement; @@ -136,13 +171,17 @@ - (void)setChildren:(NSArray> *)children { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - + [_childrenArray removeAllObjects]; NSUInteger i = 0; for (id child in children) { ASDisplayNodeAssert([child conformsToProtocol:NSProtocolFromString(@"ASLayoutElement")], @"Child %@ of spec %@ is not an ASLayoutElement!", child, self); - _childrenArray[i] = [self layoutElementToAddFromLayoutElement:child]; + id finalLayoutElement = [self layoutElementToAddFromLayoutElement:child]; + if (finalLayoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) { + [(ASLayoutSpec *)finalLayoutElement setShouldVisualize:self.shouldVisualize]; + } + _childrenArray[i] = finalLayoutElement; i += 1; } } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h index 9177988c12..620d976044 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -46,7 +46,8 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState) ASHierarchyStateTransitioningSupernodes = 1 << 2, /** One of the supernodes of this node is performing a transition. Any layout calculated during this state should not be applied immediately, but pending until later. */ - ASHierarchyStateLayoutPending = 1 << 3 + ASHierarchyStateLayoutPending = 1 << 3, + ASHierarchyStateVisualizeLayout = 1 << 4 }; ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState) @@ -59,6 +60,11 @@ ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged); } +ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesVisualizeLayout(ASHierarchyState hierarchyState) +{ + return ((hierarchyState & ASHierarchyStateVisualizeLayout) == ASHierarchyStateVisualizeLayout); +} + ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRasterized(ASHierarchyState hierarchyState) { return ((hierarchyState & ASHierarchyStateRasterized) == ASHierarchyStateRasterized); diff --git a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm index 6fbc9396e9..99bed04aa8 100644 --- a/AsyncDisplayKit/Private/ASEnvironmentInternal.mm +++ b/AsyncDisplayKit/Private/ASEnvironmentInternal.mm @@ -144,6 +144,9 @@ ASEnvironmentState ASEnvironmentMergeObjectAndState(ASEnvironmentState environme if (parentLayoutOptionsState.flexGrow == defaultState.flexGrow) { parentLayoutOptionsState.flexGrow = layoutOptionsState.flexGrow; } + if (parentLayoutOptionsState.flexShrink == defaultState.flexShrink) { + parentLayoutOptionsState.flexShrink = layoutOptionsState.flexShrink; + } if (ASDimensionEqualToDimension(parentLayoutOptionsState.flexBasis, defaultState.flexBasis)) { parentLayoutOptionsState.flexBasis = layoutOptionsState.flexBasis; } diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm index 16351fd1a9..d8b146bf9c 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm @@ -819,7 +819,7 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) NSDictionary *map = [self sectionToIndexSetMapFromChanges:changes]; NSMutableString *str = [NSMutableString stringWithString:@"{ "]; [map enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull section, NSIndexSet * _Nonnull indexSet, BOOL * _Nonnull stop) { - [str appendFormat:@"@%lu : %@ ", section.integerValue, [indexSet as_smallDescription]]; + [str appendFormat:@"@%lu : %@ ", (long)section.integerValue, [indexSet as_smallDescription]]; }]; [str appendString:@"}"]; return str; diff --git a/examples/LayoutSpecExamples/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/LayoutSpecExamples/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/examples/LayoutSpecExamples/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + 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/LayoutSpecPlayground/Default-568h@2x.png b/examples/LayoutSpecPlayground/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/LayoutSpecPlayground/Default-568h@2x.png differ diff --git a/examples/LayoutSpecPlayground/Default-667h@2x.png b/examples/LayoutSpecPlayground/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/LayoutSpecPlayground/Default-667h@2x.png differ diff --git a/examples/LayoutSpecPlayground/Default-736h@3x.png b/examples/LayoutSpecPlayground/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/LayoutSpecPlayground/Default-736h@3x.png differ diff --git a/examples/LayoutSpecPlayground/Podfile b/examples/LayoutSpecPlayground/Podfile new file mode 100644 index 0000000000..724da8c9e4 --- /dev/null +++ b/examples/LayoutSpecPlayground/Podfile @@ -0,0 +1,6 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '7.1' +target 'Sample' do + pod 'AsyncDisplayKit', :path => '../..' +end + diff --git a/examples/LayoutSpecPlayground/Sample.xcodeproj/project.pbxproj b/examples/LayoutSpecPlayground/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..4f0390c177 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,958 @@ + + + + + archiveVersion + 1 + classes + + objectVersion + 46 + objects + + 0585427F19D4DBE100606EA6 + + isa + PBXFileReference + lastKnownFileType + image.png + name + Default-568h@2x.png + path + ../Default-568h@2x.png + sourceTree + <group> + + 0585428019D4DBE100606EA6 + + fileRef + 0585427F19D4DBE100606EA6 + isa + PBXBuildFile + + 05E2127819D4DB510098F589 + + children + + 05E2128319D4DB510098F589 + 05E2128219D4DB510098F589 + 1A943BF0259746F18D6E423F + 1AE410B73DA5C3BD087ACDD7 + + indentWidth + 2 + isa + PBXGroup + sourceTree + <group> + tabWidth + 2 + usesTabs + 0 + + 05E2127919D4DB510098F589 + + attributes + + LastUpgradeCheck + 0720 + ORGANIZATIONNAME + Facebook + TargetAttributes + + 05E2128019D4DB510098F589 + + CreatedOnToolsVersion + 6.0.1 + + + + buildConfigurationList + 05E2127C19D4DB510098F589 + compatibilityVersion + Xcode 3.2 + developmentRegion + English + hasScannedForEncodings + 0 + isa + PBXProject + knownRegions + + en + Base + + mainGroup + 05E2127819D4DB510098F589 + productRefGroup + 05E2128219D4DB510098F589 + projectDirPath + + projectReferences + + projectRoot + + targets + + 05E2128019D4DB510098F589 + + + 05E2127C19D4DB510098F589 + + buildConfigurations + + 05E212A219D4DB510098F589 + 05E212A319D4DB510098F589 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 05E2127D19D4DB510098F589 + + buildActionMask + 2147483647 + files + + 76466F321C9DFFC4006C4D2D + 05E2128719D4DB510098F589 + 7602C7651CA4F83100D0D917 + 76466F341C9DFFC4006C4D2D + 76F58D5C1C9E15C1004512CC + 76466F351C9DFFC4006C4D2D + 80A6A5181D88F08F00473431 + + isa + PBXSourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 05E2127E19D4DB510098F589 + + buildActionMask + 2147483647 + files + + B971D066CC023A00C53D8575 + + isa + PBXFrameworksBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 05E2127F19D4DB510098F589 + + buildActionMask + 2147483647 + files + + 0585428019D4DBE100606EA6 + 6C2C82AC19EE274300767484 + 6C2C82AD19EE274300767484 + 7602C7671CA4FB5300D0D917 + + isa + PBXResourcesBuildPhase + runOnlyForDeploymentPostprocessing + 0 + + 05E2128019D4DB510098F589 + + buildConfigurationList + 05E212A419D4DB510098F589 + buildPhases + + E080B80F89C34A25B3488E26 + 05E2127D19D4DB510098F589 + 05E2127E19D4DB510098F589 + 05E2127F19D4DB510098F589 + F012A6F39E0149F18F564F50 + 2291CF7AF2D4B273DDAD8AAC + + buildRules + + dependencies + + isa + PBXNativeTarget + name + Sample + productName + Sample + productReference + 05E2128119D4DB510098F589 + productType + com.apple.product-type.application + + 05E2128119D4DB510098F589 + + explicitFileType + wrapper.application + includeInIndex + 0 + isa + PBXFileReference + path + Sample.app + sourceTree + BUILT_PRODUCTS_DIR + + 05E2128219D4DB510098F589 + + children + + 05E2128119D4DB510098F589 + + isa + PBXGroup + name + Products + sourceTree + <group> + + 05E2128319D4DB510098F589 + + children + + 76466F2A1C9DFFC4006C4D2D + 76466F2B1C9DFFC4006C4D2D + 76466F301C9DFFC4006C4D2D + 76466F311C9DFFC4006C4D2D + 76F58D5A1C9E15C1004512CC + 76F58D5B1C9E15C1004512CC + 76466F2E1C9DFFC4006C4D2D + 76466F2F1C9DFFC4006C4D2D + 80A6A5161D88F08F00473431 + 80A6A5171D88F08F00473431 + 7602C7631CA4F83100D0D917 + 7602C7641CA4F83100D0D917 + 05E2128419D4DB510098F589 + + isa + PBXGroup + path + Sample + sourceTree + <group> + + 05E2128419D4DB510098F589 + + children + + 0585427F19D4DBE100606EA6 + 6C2C82AA19EE274300767484 + 7602C7661CA4FB5300D0D917 + 6C2C82AB19EE274300767484 + 05E2128519D4DB510098F589 + 05E2128619D4DB510098F589 + + isa + PBXGroup + name + Supporting Files + sourceTree + <group> + + 05E2128519D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + text.plist.xml + path + Info.plist + sourceTree + <group> + + 05E2128619D4DB510098F589 + + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + main.m + sourceTree + <group> + + 05E2128719D4DB510098F589 + + fileRef + 05E2128619D4DB510098F589 + isa + PBXBuildFile + + 05E212A219D4DB510098F589 + + buildSettings + + ALWAYS_SEARCH_USER_PATHS + NO + CLANG_CXX_LANGUAGE_STANDARD + gnu++0x + CLANG_CXX_LIBRARY + libc++ + CLANG_ENABLE_MODULES + YES + CLANG_ENABLE_OBJC_ARC + YES + CLANG_WARN_BOOL_CONVERSION + YES + CLANG_WARN_CONSTANT_CONVERSION + YES + CLANG_WARN_DIRECT_OBJC_ISA_USAGE + YES_ERROR + CLANG_WARN_EMPTY_BODY + YES + CLANG_WARN_ENUM_CONVERSION + YES + CLANG_WARN_INT_CONVERSION + YES + CLANG_WARN_OBJC_ROOT_CLASS + YES_ERROR + CLANG_WARN_UNREACHABLE_CODE + YES + CLANG_WARN__DUPLICATE_METHOD_MATCH + YES + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + COPY_PHASE_STRIP + NO + ENABLE_STRICT_OBJC_MSGSEND + YES + ENABLE_TESTABILITY + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_DYNAMIC_NO_PIC + NO + GCC_OPTIMIZATION_LEVEL + 0 + GCC_PREPROCESSOR_DEFINITIONS + + DEBUG=1 + $(inherited) + + GCC_SYMBOLS_PRIVATE_EXTERN + NO + GCC_WARN_64_TO_32_BIT_CONVERSION + YES + GCC_WARN_ABOUT_RETURN_TYPE + YES_ERROR + GCC_WARN_UNDECLARED_SELECTOR + YES + GCC_WARN_UNINITIALIZED_AUTOS + YES_AGGRESSIVE + GCC_WARN_UNUSED_FUNCTION + YES + GCC_WARN_UNUSED_VARIABLE + YES + IPHONEOS_DEPLOYMENT_TARGET + 7.1 + MTL_ENABLE_DEBUG_INFO + YES + ONLY_ACTIVE_ARCH + YES + SDKROOT + iphoneos + + isa + XCBuildConfiguration + name + Debug + + 05E212A319D4DB510098F589 + + buildSettings + + ALWAYS_SEARCH_USER_PATHS + NO + CLANG_CXX_LANGUAGE_STANDARD + gnu++0x + CLANG_CXX_LIBRARY + libc++ + CLANG_ENABLE_MODULES + YES + CLANG_ENABLE_OBJC_ARC + YES + CLANG_WARN_BOOL_CONVERSION + YES + CLANG_WARN_CONSTANT_CONVERSION + YES + CLANG_WARN_DIRECT_OBJC_ISA_USAGE + YES_ERROR + CLANG_WARN_EMPTY_BODY + YES + CLANG_WARN_ENUM_CONVERSION + YES + CLANG_WARN_INT_CONVERSION + YES + CLANG_WARN_OBJC_ROOT_CLASS + YES_ERROR + CLANG_WARN_UNREACHABLE_CODE + YES + CLANG_WARN__DUPLICATE_METHOD_MATCH + YES + CODE_SIGN_IDENTITY[sdk=iphoneos*] + iPhone Developer + COPY_PHASE_STRIP + YES + ENABLE_NS_ASSERTIONS + NO + ENABLE_STRICT_OBJC_MSGSEND + YES + GCC_C_LANGUAGE_STANDARD + gnu99 + GCC_WARN_64_TO_32_BIT_CONVERSION + YES + GCC_WARN_ABOUT_RETURN_TYPE + YES_ERROR + GCC_WARN_UNDECLARED_SELECTOR + YES + GCC_WARN_UNINITIALIZED_AUTOS + YES_AGGRESSIVE + GCC_WARN_UNUSED_FUNCTION + YES + GCC_WARN_UNUSED_VARIABLE + YES + IPHONEOS_DEPLOYMENT_TARGET + 7.1 + MTL_ENABLE_DEBUG_INFO + NO + SDKROOT + iphoneos + VALIDATE_PRODUCT + YES + + isa + XCBuildConfiguration + name + Release + + 05E212A419D4DB510098F589 + + buildConfigurations + + 05E212A519D4DB510098F589 + 05E212A619D4DB510098F589 + + defaultConfigurationIsVisible + 0 + defaultConfigurationName + Release + isa + XCConfigurationList + + 05E212A519D4DB510098F589 + + baseConfigurationReference + FDF496F367580DF9280D36EA + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Sample/Info.plist + IPHONEOS_DEPLOYMENT_TARGET + 7.1 + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_BUNDLE_IDENTIFIER + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + PRODUCT_NAME + $(TARGET_NAME) + TARGETED_DEVICE_FAMILY + 1,2 + + isa + XCBuildConfiguration + name + Debug + + 05E212A619D4DB510098F589 + + baseConfigurationReference + 5C5154389F056C672F4E9EEA + buildSettings + + ASSETCATALOG_COMPILER_APPICON_NAME + AppIcon + INFOPLIST_FILE + Sample/Info.plist + IPHONEOS_DEPLOYMENT_TARGET + 7.1 + LD_RUNPATH_SEARCH_PATHS + $(inherited) @executable_path/Frameworks + PRODUCT_BUNDLE_IDENTIFIER + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + PRODUCT_NAME + $(TARGET_NAME) + TARGETED_DEVICE_FAMILY + 1,2 + + isa + XCBuildConfiguration + name + Release + + 088AA6578212BE9BFBB07B70 + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods.release.xcconfig + path + Pods/Target Support Files/Pods/Pods.release.xcconfig + sourceTree + <group> + + 1A943BF0259746F18D6E423F + + children + + 3D24B17D1E4A4E7A9566C5E9 + 54A6EB3DE8D9A9EC4AE2867D + + isa + PBXGroup + name + Frameworks + sourceTree + <group> + + 1AE410B73DA5C3BD087ACDD7 + + children + + C068F1D3F0CC317E895FCDAB + 088AA6578212BE9BFBB07B70 + FDF496F367580DF9280D36EA + 5C5154389F056C672F4E9EEA + + isa + PBXGroup + name + Pods + sourceTree + <group> + + 2291CF7AF2D4B273DDAD8AAC + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + [CP] Embed Pods Frameworks + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh" + + showEnvVarsInLog + 0 + + 3D24B17D1E4A4E7A9566C5E9 + + explicitFileType + archive.ar + includeInIndex + 0 + isa + PBXFileReference + path + libPods.a + sourceTree + BUILT_PRODUCTS_DIR + + 54A6EB3DE8D9A9EC4AE2867D + + explicitFileType + archive.ar + includeInIndex + 0 + isa + PBXFileReference + path + libPods-Sample.a + sourceTree + BUILT_PRODUCTS_DIR + + 5C5154389F056C672F4E9EEA + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods-Sample.release.xcconfig + path + Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig + sourceTree + <group> + + 6C2C82AA19EE274300767484 + + isa + PBXFileReference + lastKnownFileType + image.png + path + Default-667h@2x.png + sourceTree + SOURCE_ROOT + + 6C2C82AB19EE274300767484 + + isa + PBXFileReference + lastKnownFileType + image.png + path + Default-736h@3x.png + sourceTree + SOURCE_ROOT + + 6C2C82AC19EE274300767484 + + fileRef + 6C2C82AA19EE274300767484 + isa + PBXBuildFile + + 6C2C82AD19EE274300767484 + + fileRef + 6C2C82AB19EE274300767484 + isa + PBXBuildFile + + 7602C7631CA4F83100D0D917 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + Utilities.h + sourceTree + <group> + + 7602C7641CA4F83100D0D917 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + Utilities.m + sourceTree + <group> + + 7602C7651CA4F83100D0D917 + + fileRef + 7602C7641CA4F83100D0D917 + isa + PBXBuildFile + + 7602C7661CA4FB5300D0D917 + + isa + PBXFileReference + lastKnownFileType + image.png + path + resizeHandle.png + sourceTree + <group> + + 7602C7671CA4FB5300D0D917 + + fileRef + 7602C7661CA4FB5300D0D917 + isa + PBXBuildFile + + 76466F2A1C9DFFC4006C4D2D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + AppDelegate.h + sourceTree + <group> + + 76466F2B1C9DFFC4006C4D2D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + AppDelegate.m + sourceTree + <group> + + 76466F2E1C9DFFC4006C4D2D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + PhotoPostNode.h + sourceTree + <group> + + 76466F2F1C9DFFC4006C4D2D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + PhotoPostNode.m + sourceTree + <group> + + 76466F301C9DFFC4006C4D2D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + ViewController.h + sourceTree + <group> + + 76466F311C9DFFC4006C4D2D + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + ViewController.m + sourceTree + <group> + + 76466F321C9DFFC4006C4D2D + + fileRef + 76466F2B1C9DFFC4006C4D2D + isa + PBXBuildFile + + 76466F341C9DFFC4006C4D2D + + fileRef + 76466F2F1C9DFFC4006C4D2D + isa + PBXBuildFile + + 76466F351C9DFFC4006C4D2D + + fileRef + 76466F311C9DFFC4006C4D2D + isa + PBXBuildFile + + 76F58D5A1C9E15C1004512CC + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + PlaygroundContainerNode.h + sourceTree + <group> + + 76F58D5B1C9E15C1004512CC + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + PlaygroundContainerNode.m + sourceTree + <group> + + 76F58D5C1C9E15C1004512CC + + fileRef + 76F58D5B1C9E15C1004512CC + isa + PBXBuildFile + + 80A6A5161D88F08F00473431 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.h + path + LayoutExampleNodes.h + sourceTree + <group> + + 80A6A5171D88F08F00473431 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.c.objc + path + LayoutExampleNodes.m + sourceTree + <group> + + 80A6A5181D88F08F00473431 + + fileRef + 80A6A5171D88F08F00473431 + isa + PBXBuildFile + + B971D066CC023A00C53D8575 + + fileRef + 54A6EB3DE8D9A9EC4AE2867D + isa + PBXBuildFile + + C068F1D3F0CC317E895FCDAB + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods.debug.xcconfig + path + Pods/Target Support Files/Pods/Pods.debug.xcconfig + sourceTree + <group> + + E080B80F89C34A25B3488E26 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + [CP] Check Pods Manifest.lock + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + diff "${PODS_ROOT}/../Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null +if [[ $? != 0 ]] ; then + cat << EOM +error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation. +EOM + exit 1 +fi + + showEnvVarsInLog + 0 + + F012A6F39E0149F18F564F50 + + buildActionMask + 2147483647 + files + + inputPaths + + isa + PBXShellScriptBuildPhase + name + [CP] Copy Pods Resources + outputPaths + + runOnlyForDeploymentPostprocessing + 0 + shellPath + /bin/sh + shellScript + "${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh" + + showEnvVarsInLog + 0 + + FDF496F367580DF9280D36EA + + includeInIndex + 1 + isa + PBXFileReference + lastKnownFileType + text.xcconfig + name + Pods-Sample.debug.xcconfig + path + Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig + sourceTree + <group> + + + rootObject + 05E2127919D4DB510098F589 + + diff --git a/examples/LayoutSpecPlayground/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/LayoutSpecPlayground/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..d41d58c5d8 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/LayoutSpecPlayground/Sample.xcworkspace/contents.xcworkspacedata b/examples/LayoutSpecPlayground/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/LayoutSpecPlayground/Sample/AppDelegate.h b/examples/LayoutSpecPlayground/Sample/AppDelegate.h new file mode 100644 index 0000000000..8ec4f8f6fe --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/AppDelegate.h @@ -0,0 +1,16 @@ +// +// AppDelegate.h +// ASLayoutSpecPlayground +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end + diff --git a/examples/LayoutSpecPlayground/Sample/AppDelegate.m b/examples/LayoutSpecPlayground/Sample/AppDelegate.m new file mode 100644 index 0000000000..7b167a8ed1 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/AppDelegate.m @@ -0,0 +1,50 @@ +// +// AppDelegate.m +// ASLayoutSpecPlayground +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "AppDelegate.h" +#import "ViewController.h" +#import "Utilities.h" +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + + UIViewController *rootVC = nil; + + UIDevice *device = [UIDevice currentDevice]; + if (device.userInterfaceIdiom == UIUserInterfaceIdiomPad) { + + ASViewController *masterVC = [[ASViewController alloc] initWithNode:[ASLayoutElementInspectorNode sharedInstance]]; + masterVC.view.backgroundColor = [UIColor customOrangeColor]; + UINavigationController *masterNav = [[UINavigationController alloc] initWithRootViewController:masterVC]; + + ViewController *detailVC = [[ViewController alloc] init]; + UINavigationController *detailNav = [[UINavigationController alloc] initWithRootViewController:detailVC]; + + UISplitViewController *splitVC = [[UISplitViewController alloc] init]; + splitVC.viewControllers = @[masterNav, detailNav]; + splitVC.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible; + splitVC.maximumPrimaryColumnWidth = 250; + + rootVC = splitVC; + + } else { + // FIXME: make this work for iPhones + NSAssert(YES, @"App optimized for iPad only."); + } + + [self.window setRootViewController:rootVC]; + [self.window makeKeyAndVisible]; + + return YES; +} + +@end diff --git a/examples/LayoutSpecPlayground/Sample/Info.plist b/examples/LayoutSpecPlayground/Sample/Info.plist new file mode 100644 index 0000000000..14831a5ae5 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.h b/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.h new file mode 100644 index 0000000000..5ca9987e3c --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.h @@ -0,0 +1,49 @@ +// +// LayoutExampleNodes.h +// Sample +// +// Created by Hannah Troisi on 9/13/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface LayoutExampleNode : ASDisplayNode + +- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size; +- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size; + +@end + +@interface HorizontalStackWithSpacer : LayoutExampleNode + +@property (nonatomic, strong) ASTextNode *usernameNode; +@property (nonatomic, strong) ASTextNode *postLocationNode; +@property (nonatomic, strong) ASTextNode *postTimeNode; + +@end + +@interface PhotoWithInsetTextOverlay : LayoutExampleNode + +@property (nonatomic, strong) ASNetworkImageNode *photoNode; +@property (nonatomic, strong) ASTextNode *titleNode; + +@end + +@interface PhotoWithOutsetIconOverlay : LayoutExampleNode + +@property (nonatomic, strong) ASNetworkImageNode *photoNode; +@property (nonatomic, strong) ASNetworkImageNode *iconNode; + +@end + +@interface FlexibleSeparatorSurroundingContent : LayoutExampleNode + +@property (nonatomic, strong) ASImageNode *topSeparator; +@property (nonatomic, strong) ASImageNode *bottomSeparator; +@property (nonatomic, strong) ASTextNode *textNode; + +@end diff --git a/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.m b/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.m new file mode 100644 index 0000000000..35834ec546 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/LayoutExampleNodes.m @@ -0,0 +1,272 @@ +// +// LayoutExampleNodes.m +// Sample +// +// Created by Hannah Troisi on 9/13/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "LayoutExampleNodes.h" +#import "Utilities.h" +#import "UIImage+ASConvenience.h" + +#define USER_IMAGE_HEIGHT 60 +#define HORIZONTAL_BUFFER 10 +#define VERTICAL_BUFFER 5 +#define FONT_SIZE 20 + +@implementation LayoutExampleNode + +- (instancetype)init +{ + self = [super init]; + if (self) { + self.automaticallyManagesSubnodes = YES; + self.shouldVisualizeLayoutSpecs = NO; + self.shouldCacheLayoutSpec = NO; + self.backgroundColor = [UIColor whiteColor]; + } + return self; +} + +#pragma mark - helper methods + +- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"hannahmbanana" + fontSize:size + color:[UIColor darkBlueColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"San Fransisco, CA" + fontSize:size + color:[UIColor lightBlueColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"30m" + fontSize:size + color:[UIColor lightGrayColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"♥︎ 17 likes" + fontSize:size + color:[UIColor darkBlueColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size +{ + NSString *string = [NSString stringWithFormat:@"hannahmbanana check out this cool pic from the internet!"]; + NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string + fontSize:size + color:[UIColor darkGrayColor] + firstWordColor:[UIColor darkBlueColor]]; + return attrString; +} + +@end + +@implementation HorizontalStackWithSpacer + +- (instancetype)init +{ + self = [super init]; + + if (self) { + _usernameNode = [[ASTextNode alloc] init]; + _usernameNode.attributedText = [self usernameAttributedStringWithFontSize:FONT_SIZE]; + + _postLocationNode = [[ASTextNode alloc] init]; + _postLocationNode.maximumNumberOfLines = 1; + _postLocationNode.attributedText = [self locationAttributedStringWithFontSize:FONT_SIZE]; + + _postTimeNode = [[ASTextNode alloc] init]; + _postTimeNode.attributedText = [self uploadDateAttributedStringWithFontSize:FONT_SIZE]; + } + + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + _usernameNode.style.flexShrink = 1.0; + _postLocationNode.style.flexShrink = 1.0; + + ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStackSpec.style.flexShrink = 1.0; + + // Example: see ASDKgram for how this technique can be used to animate in the location label + // once a separate network request provides the data. + if (_postLocationNode.attributedText) { + [verticalStackSpec setChildren:@[_usernameNode, _postLocationNode]]; + } else { + [verticalStackSpec setChildren:@[_usernameNode]]; + } + + ASLayoutSpec *spacerSpec = [[ASLayoutSpec alloc] init]; + spacerSpec.style.flexGrow = 1.0; + spacerSpec.style.flexShrink = 1.0; + + // horizontal stack + ASStackLayoutSpec *horizontalStackSpec = [ASStackLayoutSpec horizontalStackLayoutSpec]; + horizontalStackSpec.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horiz stack + horizontalStackSpec.justifyContent = ASStackLayoutJustifyContentStart; // justify content to left + horizontalStackSpec.style.flexShrink = 1.0; + horizontalStackSpec.style.flexGrow = 1.0; + [horizontalStackSpec setChildren:@[verticalStackSpec, spacerSpec, _postTimeNode]]; + + // inset horizontal stack + UIEdgeInsets insets = UIEdgeInsetsMake(0, 10, 0, 10); + ASInsetLayoutSpec *headerInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:horizontalStackSpec]; + headerInsetSpec.style.flexShrink = 1.0; + headerInsetSpec.style.flexGrow = 1.0; + + return headerInsetSpec; +} + +@end + + +@implementation PhotoWithInsetTextOverlay + +- (instancetype)init +{ + self = [super init]; + + if (self) { + _photoNode = [[ASNetworkImageNode alloc] init]; + _photoNode.URL = [NSURL URLWithString:@"http://asyncdisplaykit.org/static/images/layout-examples-photo-with-inset-text-overlay-photo.png"]; + + _titleNode = [[ASTextNode alloc] init]; + _titleNode.maximumNumberOfLines = 2; + _titleNode.truncationAttributedText = [NSAttributedString attributedStringWithString:@"..." + fontSize:16 + color:[UIColor whiteColor] + firstWordColor:nil]; + _titleNode.attributedText = [NSAttributedString attributedStringWithString:@"family fall hikes" + fontSize:16 + color:[UIColor whiteColor] + firstWordColor:nil]; + } + + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + _photoNode.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT*2, USER_IMAGE_HEIGHT*2); + + UIEdgeInsets insets = UIEdgeInsetsMake(INFINITY, 12, 12, 12); + ASInsetLayoutSpec *textInsetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets + child:_titleNode]; + + ASOverlayLayoutSpec *textOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:_photoNode + overlay:textInsetSpec]; + + return textOverlaySpec; +} + +@end + + +@implementation PhotoWithOutsetIconOverlay + +- (instancetype)init +{ + self = [super init]; + + if (self) { + + _photoNode = [[ASNetworkImageNode alloc] init]; + _photoNode.URL = [NSURL URLWithString:@"http://asyncdisplaykit.org/static/images/layout-examples-photo-with-outset-icon-overlay-photo.png"]; + + _iconNode = [[ASNetworkImageNode alloc] init]; + _iconNode.URL = [NSURL URLWithString:@"http://asyncdisplaykit.org/static/images/layout-examples-photo-with-outset-icon-overlay-icon.png"]; + + [_iconNode setImageModificationBlock:^UIImage *(UIImage *image) { // FIXME: in framework autocomplete for setImageModificationBlock line seems broken + CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + return [image makeCircularImageWithSize:profileImageSize withBorderWidth:10]; + }]; + } + + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + _iconNode.style.preferredSize = CGSizeMake(40, 40); + _photoNode.style.preferredSize = CGSizeMake(150, 150); + + CGFloat x = 150; + CGFloat y = 0; + + _iconNode.style.layoutPosition = CGPointMake(x, y); + _photoNode.style.layoutPosition = CGPointMake(40 / 2.0, 40 / 2.0); + + ASAbsoluteLayoutSpec *absoluteLayoutSpec = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[_photoNode, _iconNode]]; + + return absoluteLayoutSpec; +} + +@end + + +@implementation FlexibleSeparatorSurroundingContent + +- (instancetype)init +{ + self = [super init]; + + if (self) { + + self.backgroundColor = [UIColor cyanColor]; + + _topSeparator = [[ASImageNode alloc] init]; + _topSeparator.image = [UIImage as_resizableRoundedImageWithCornerRadius:1.0 + cornerColor:[UIColor blackColor] + fillColor:[UIColor blackColor]]; + + _textNode = [[ASTextNode alloc] init]; + _textNode.attributedText = [NSAttributedString attributedStringWithString:@"this is a long text node" + fontSize:16 + color:[UIColor blackColor] + firstWordColor:nil]; + + _bottomSeparator = [[ASImageNode alloc] init]; + _bottomSeparator.image = [UIImage as_resizableRoundedImageWithCornerRadius:1.0 + cornerColor:[UIColor blackColor] + fillColor:[UIColor blackColor]]; + } + + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + _topSeparator.style.flexGrow = 1.0; + _bottomSeparator.style.flexGrow = 1.0; + + UIEdgeInsets contentInsets = UIEdgeInsetsMake(10, 10, 10, 10); + ASInsetLayoutSpec *insetContentSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:contentInsets + child:_textNode]; + // final vertical stack + ASStackLayoutSpec *verticalStackSpec = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStackSpec.direction = ASStackLayoutDirectionVertical; + verticalStackSpec.justifyContent = ASStackLayoutJustifyContentCenter; + verticalStackSpec.alignItems = ASStackLayoutAlignItemsStretch; + verticalStackSpec.children = @[_topSeparator, insetContentSpec, _bottomSeparator]; + + return verticalStackSpec; +} + +@end + diff --git a/examples/LayoutSpecPlayground/Sample/PhotoPostNode.h b/examples/LayoutSpecPlayground/Sample/PhotoPostNode.h new file mode 100644 index 0000000000..3270ab4935 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/PhotoPostNode.h @@ -0,0 +1,15 @@ +// +// PhotoPostNode.h +// ASLayoutSpecPlayground +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "AsyncDisplayKit.h" + +@interface PhotoPostNode : ASDisplayNode + +- (instancetype)initWithIndex:(NSUInteger)index; + +@end diff --git a/examples/LayoutSpecPlayground/Sample/PhotoPostNode.m b/examples/LayoutSpecPlayground/Sample/PhotoPostNode.m new file mode 100644 index 0000000000..644aa5fbee --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/PhotoPostNode.m @@ -0,0 +1,249 @@ +// +// PhotoPostNode.m +// ASLayoutSpecPlayground +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "PhotoPostNode.h" +#import "AsyncDisplayKit+Debug.h" +#import "Utilities.h" + +#define USER_IMAGE_HEIGHT 60 +#define HORIZONTAL_BUFFER 10 +#define VERTICAL_BUFFER 5 +#define FONT_SIZE 20 + +@implementation PhotoPostNode +{ + NSUInteger _index; + ASNetworkImageNode *_userAvatarImageView; + ASNetworkImageNode *_photoImageView; + ASTextNode *_userNameLabel; + ASTextNode *_photoLocationLabel; + ASTextNode *_photoTimeIntervalSincePostLabel; + ASTextNode *_photoLikesLabel; + ASTextNode *_photoDescriptionLabel; +} + +#pragma mark - Lifecycle + +- (instancetype)initWithIndex:(NSUInteger)index +{ + self = [super init]; + + if (self) { + + self.backgroundColor = [UIColor whiteColor]; + self.automaticallyManagesSubnodes = YES; + self.shouldVisualizeLayoutSpecs = YES; + self.shouldCacheLayoutSpec = YES; + + _index = index; + + _userAvatarImageView = [[ASNetworkImageNode alloc] init]; + _userAvatarImageView.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/avatars/503h_1458880322_140.jpg"]; + + [_userAvatarImageView setImageModificationBlock:^UIImage *(UIImage *image) { // FIXME: in framework autocomplete for setImageModificationBlock line seems broken + CGSize profileImageSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + return [image makeCircularImageWithSize:profileImageSize withBorderWidth:0]; + }]; + + _userNameLabel = [[ASTextNode alloc] init]; + _userNameLabel.attributedText = [self usernameAttributedStringWithFontSize:FONT_SIZE]; + + _photoLocationLabel = [[ASTextNode alloc] init]; + _photoLocationLabel.maximumNumberOfLines = 1; + _photoLocationLabel.attributedText = [self locationAttributedStringWithFontSize:FONT_SIZE]; + + _photoTimeIntervalSincePostLabel = [[ASTextNode alloc] init]; + _photoTimeIntervalSincePostLabel.attributedText = [self uploadDateAttributedStringWithFontSize:FONT_SIZE]; + + _photoImageView = [[ASNetworkImageNode alloc] init]; + _photoImageView.URL = [NSURL URLWithString:@"https://s-media-cache-ak0.pinimg.com/564x/9f/5b/3a/9f5b3a35640bc7a5d484b66124c48c46.jpg"]; + + _photoLikesLabel = [[ASTextNode alloc] init]; + _photoLikesLabel.attributedText = [self likesAttributedStringWithFontSize:FONT_SIZE]; + + _photoDescriptionLabel = [[ASTextNode alloc] init]; + _photoDescriptionLabel.attributedText = [self descriptionAttributedStringWithFontSize:FONT_SIZE]; + _photoDescriptionLabel.maximumNumberOfLines = 3; + } + + return self; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + switch (_index) { + case 1: + return [self layoutSpecThatFitsNavBar:constrainedSize]; + case 2: + return [self layoutSpecThatFitsASDKgram:constrainedSize]; + default: + return [self layoutSpecThatFitsASDKgram:constrainedSize]; + break; + } +} + +- (ASLayoutSpec *)layoutSpecThatFitsNavBar:(ASSizeRange)constrainedSize +{ + // username / photo location header vertical stack + + _userNameLabel.style.flexShrink = 1.0; + _photoLocationLabel.style.flexShrink = 1.0; + + ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + headerSubStack.style.flexShrink = 1.0; + + if (_photoLocationLabel.attributedText) { + [headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]]; + } else { + [headerSubStack setChildren:@[_userNameLabel]]; + } + + // header stack + + _userAvatarImageView.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + _photoTimeIntervalSincePostLabel.style.spacingBefore = HORIZONTAL_BUFFER; // hack to remove double spaces around spacer + + UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView]; + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.style.flexGrow = 1.0; + spacer.style.flexShrink = 1.0; // FIXME: this overrides stuff :) THIS IS A SYSTEMIC ISSUE - can we make layoutSpecThatFits only run once? cache layoutSpec, just use new constrainedSize, don't put properties in layoutSpecThatFits + // separate the idea of laying out and rerunning with new constrainedSize + + ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack + headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack + headerStack.style.flexShrink = 1.0; + headerStack.style.flexGrow = 1.0; + + [headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]]; + + // header inset stack + + UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack]; + headerWithInset.style.flexShrink = 1.0; + headerWithInset.style.flexGrow = 1.0; + + return headerWithInset; +} + +- (ASLayoutSpec *)layoutSpecThatFitsASDKgram:(ASSizeRange)constrainedSize +{ + // username / photo location header vertical stack + + _userNameLabel.style.flexShrink = 1.0; + _photoLocationLabel.style.flexShrink = 1.0; + + ASStackLayoutSpec *headerSubStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + headerSubStack.style.flexShrink = 1.0; + + if (_photoLocationLabel.attributedText) { + [headerSubStack setChildren:@[_userNameLabel, _photoLocationLabel]]; + } else { + [headerSubStack setChildren:@[_userNameLabel]]; + } + + // header stack + + _userAvatarImageView.style.preferredSize = CGSizeMake(USER_IMAGE_HEIGHT, USER_IMAGE_HEIGHT); + _photoTimeIntervalSincePostLabel.style.spacingBefore = HORIZONTAL_BUFFER; // hack to remove double spaces around spacer + + UIEdgeInsets avatarInsets = UIEdgeInsetsMake(HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *avatarInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:avatarInsets child:_userAvatarImageView]; + + ASLayoutSpec *spacer = [[ASLayoutSpec alloc] init]; + spacer.style.flexGrow = 1.0; + spacer.style.flexShrink = 1.0; // FIXME: this overrides stuff :) THIS IS A SYSTEMIC ISSUE - can we make layoutSpecThatFits only run once? cache layoutSpec, just use new constrainedSize, don't put properties in layoutSpecThatFits + // separate the idea of laying out and rerunning with new constrainedSize + + ASStackLayoutSpec *headerStack = [ASStackLayoutSpec horizontalStackLayoutSpec]; + headerStack.alignItems = ASStackLayoutAlignItemsCenter; // center items vertically in horizontal stack + headerStack.justifyContent = ASStackLayoutJustifyContentStart; // justify content to the left side of the header stack + headerStack.style.flexShrink = 1.0; + headerStack.style.flexGrow = 1.0; + + [headerStack setChildren:@[avatarInset, headerSubStack, spacer, _photoTimeIntervalSincePostLabel]]; + + // header inset stack + + UIEdgeInsets insets = UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *headerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:insets child:headerStack]; + headerWithInset.style.flexShrink = 1.0; + headerWithInset.style.flexGrow = 1.0; + + // footer stack + + ASStackLayoutSpec *footerStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + footerStack.spacing = VERTICAL_BUFFER; + + [footerStack setChildren:@[_photoLikesLabel, _photoDescriptionLabel]]; + + // footer inset stack + + UIEdgeInsets footerInsets = UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER); + ASInsetLayoutSpec *footerWithInset = [ASInsetLayoutSpec insetLayoutSpecWithInsets:footerInsets child:footerStack]; + + // vertical stack + + ASRatioLayoutSpec *photoRatioSpec = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.0 child:_photoImageView]; + + ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStack.alignItems = ASStackLayoutAlignItemsStretch; // sretch headerStack to fill horizontal space + [verticalStack setChildren:@[headerWithInset, photoRatioSpec, footerWithInset]]; + verticalStack.style.flexShrink = 1.0; + + return verticalStack; +} + +#pragma mark - helper methods + +- (NSAttributedString *)usernameAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"hannahmbanana" + fontSize:size + color:[UIColor darkBlueColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)locationAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"San Fransisco, CA" + fontSize:size + color:[UIColor lightBlueColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)uploadDateAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"30m" + fontSize:size + color:[UIColor lightGrayColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)likesAttributedStringWithFontSize:(CGFloat)size +{ + return [NSAttributedString attributedStringWithString:@"♥︎ 17 likes" + fontSize:size + color:[UIColor darkBlueColor] + firstWordColor:nil]; +} + +- (NSAttributedString *)descriptionAttributedStringWithFontSize:(CGFloat)size +{ + NSString *string = [NSString stringWithFormat:@"hannahmbanana check out this cool pic from the internet!"]; + NSAttributedString *attrString = [NSAttributedString attributedStringWithString:string + fontSize:size + color:[UIColor darkGrayColor] + firstWordColor:[UIColor darkBlueColor]]; + return attrString; +} + +@end diff --git a/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.h b/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.h new file mode 100644 index 0000000000..61d54d0f7e --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.h @@ -0,0 +1,24 @@ +// +// PlaygroundContainerNode.h +// Sample +// +// Created by Hannah Troisi on 3/19/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@protocol PlaygroundContainerNodeDelegate + +- (void)relayoutWithSize:(ASSizeRange)size; + +@end + +@interface PlaygroundContainerNode : ASCellNode + +@property (nonatomic, weak) id delegate; + ++ (NSUInteger)containerNodeCount; +- (instancetype)initWithIndex:(NSUInteger)index; + +@end diff --git a/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.m b/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.m new file mode 100644 index 0000000000..7450d471cc --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/PlaygroundContainerNode.m @@ -0,0 +1,124 @@ +// +// PlaygroundContainerNode.m +// Sample +// +// Created by Hannah Troisi on 3/19/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "PlaygroundContainerNode.h" +#import "LayoutExampleNodes.h" +#import "PhotoPostNode.h" +#import +#import + +#define RESIZE_HANDLE_SIZE 30 + +@implementation PlaygroundContainerNode +{ + ASDisplayNode *_playgroundNode; + ASImageNode *_resizeHandle; + CGPoint _resizeStartLocation; +} + +#pragma mark - Lifecycle + ++ (NSUInteger)containerNodeCount +{ + return 5; +} + ++ (ASDisplayNode *)nodeForIndex:(NSUInteger)index +{ + switch (index) { + case 0: return [[HorizontalStackWithSpacer alloc] init]; + case 1: return [[PhotoWithInsetTextOverlay alloc] init]; + case 2: return [[PhotoWithOutsetIconOverlay alloc] init]; + case 3: return [[FlexibleSeparatorSurroundingContent alloc] init]; + case 4: return [[PhotoPostNode alloc] initWithIndex:0]; + default: return [[PhotoPostNode alloc] initWithIndex:1]; + } +} + +- (instancetype)initWithIndex:(NSUInteger)index +{ + self = [super init]; + + if (self) { + self.backgroundColor = [UIColor whiteColor]; //[UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; + self.automaticallyManagesSubnodes = YES; + + _playgroundNode = [[self class] nodeForIndex:index]; + + _resizeHandle = [[ASImageNode alloc] init]; + _resizeHandle.image = [UIImage imageNamed:@"resizeHandle"]; + _resizeHandle.userInteractionEnabled = YES; +// [self addSubnode:_resizeHandle]; + + [ASLayoutElementInspectorNode sharedInstance].style.flexBasis = ASDimensionMakeWithFraction(1.0); + [ASLayoutElementInspectorNode sharedInstance].vizNodeInsetSize = 10.0; + + self.shouldVisualizeLayoutSpecs = NO; + self.shouldCacheLayoutSpec = NO; + } + + return self; +} + +- (void)didLoad +{ + [super didLoad]; + UIPanGestureRecognizer *gr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(resizePlayground:)]; + [_resizeHandle.view addGestureRecognizer:gr]; +} + +// manually layout _resizeHandle // FIXME: add this to an overlayStack in layoutSpecThatFits? +- (void)layout +{ + [super layout]; + [self.view bringSubviewToFront:_resizeHandle.view]; + + CGSize playgroundSize = _playgroundNode.calculatedLayout.size; + CGRect rect = CGRectZero; + rect.size = CGSizeMake(RESIZE_HANDLE_SIZE, RESIZE_HANDLE_SIZE); + rect.origin = CGPointMake(playgroundSize.width - rect.size.width, playgroundSize.height - rect.size.height); + _resizeHandle.frame = rect; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + _playgroundNode.style.flexGrow = 1.0; + _playgroundNode.style.flexShrink = 1.0; + + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) + child:_playgroundNode]; +} + +#pragma mark - Gesture Handling + +- (void)resizePlayground:(UIPanGestureRecognizer *)sender +{ + if (sender.state == UIGestureRecognizerStateBegan) { + _resizeStartLocation = [sender locationInView:sender.view]; + } + else if (sender.state == UIGestureRecognizerStateChanged) { + CGPoint location = [sender locationInView:sender.view]; + CGPoint translation = CGPointMake(location.x - _resizeStartLocation.x, location.y - _resizeStartLocation.y); + [self changePlaygroundFrameWithTranslation:translation]; + } + else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateFailed) { + _resizeStartLocation = CGPointZero; + } +} + +- (void)changePlaygroundFrameWithTranslation:(CGPoint)translation +{ + ASSizeRange constrainedSize = self.constrainedSizeForCalculatedLayout; + + constrainedSize.max.width = MAX(0, constrainedSize.max.width + translation.x); + constrainedSize.max.height = MAX(0, constrainedSize.max.height + translation.y); + + [self.delegate relayoutWithSize:constrainedSize]; +} + +@end diff --git a/examples/LayoutSpecPlayground/Sample/Utilities.h b/examples/LayoutSpecPlayground/Sample/Utilities.h new file mode 100644 index 0000000000..7f0396e8a8 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/Utilities.h @@ -0,0 +1,32 @@ +// +// Utilities.h +// Flickrgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import +#import + +@interface UIColor (Additions) + ++ (UIColor *)darkBlueColor; ++ (UIColor *)lightBlueColor; ++ (UIColor *)duskColor; ++ (UIColor *)customOrangeColor; + +@end + +@interface UIImage (Additions) + +- (UIImage *)makeCircularImageWithSize:(CGSize)size withBorderWidth:(CGFloat)width; + +@end + +@interface NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size + color:(UIColor *)color firstWordColor:(UIColor *)firstWordColor; + +@end \ No newline at end of file diff --git a/examples/LayoutSpecPlayground/Sample/Utilities.m b/examples/LayoutSpecPlayground/Sample/Utilities.m new file mode 100644 index 0000000000..9b13152a97 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/Utilities.m @@ -0,0 +1,98 @@ +// +// Utilities.m +// Flickrgram +// +// Created by Hannah Troisi on 3/9/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "Utilities.h" +#import + +#define StrokeRoundedImages 0 + +@implementation UIColor (Additions) + ++ (UIColor *)darkBlueColor +{ + return [UIColor colorWithRed:18.0/255.0 green:86.0/255.0 blue:136.0/255.0 alpha:1.0]; +} + ++ (UIColor *)lightBlueColor +{ + return [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0]; +} + ++ (UIColor *)duskColor +{ + return [UIColor colorWithRed:255/255.0 green:181/255.0 blue:68/255.0 alpha:1]; +} + ++ (UIColor *)customOrangeColor +{ + return [UIColor colorWithRed:40/255.0 green:43/255.0 blue:53/255.0 alpha:1.0]; +} + + +@end + +@implementation UIImage (Additions) + +- (UIImage *)makeCircularImageWithSize:(CGSize)size withBorderWidth:(CGFloat)width +{ + // make a CGRect with the image's size + CGRect circleRect = (CGRect) {CGPointZero, size}; + + // begin the image context since we're not in a drawRect: + UIGraphicsBeginImageContextWithOptions(circleRect.size, NO, 0); + + // create a UIBezierPath circle + UIBezierPath *circle = [UIBezierPath bezierPathWithRoundedRect:circleRect cornerRadius:circleRect.size.width/2]; + + // clip to the circle + [circle addClip]; + + [[UIColor whiteColor] set]; + [circle fill]; + + // draw the image in the circleRect *AFTER* the context is clipped + [self drawInRect:circleRect]; + + // create a border (for white background pictures) + if (width > 0) { + circle.lineWidth = width; + [[UIColor whiteColor] set]; + [circle stroke]; + } + + // get an image from the image context + UIImage *roundedImage = UIGraphicsGetImageFromCurrentImageContext(); + + // end the image context since we're not in a drawRect: + UIGraphicsEndImageContext(); + + return roundedImage; +} + +@end + +@implementation NSAttributedString (Additions) + ++ (NSAttributedString *)attributedStringWithString:(NSString *)string fontSize:(CGFloat)size + color:(nullable UIColor *)color firstWordColor:(nullable UIColor *)firstWordColor +{ + NSDictionary *attributes = @{NSForegroundColorAttributeName: color ? : [UIColor blackColor], + NSFontAttributeName: [UIFont boldSystemFontOfSize:size]}; + NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string]; + [attributedString addAttributes:attributes range:NSMakeRange(0, string.length)]; + + if (firstWordColor) { + NSRange firstSpaceRange = [string rangeOfCharacterFromSet:[NSCharacterSet whitespaceCharacterSet]]; + NSRange firstWordRange = NSMakeRange(0, firstSpaceRange.location); + [attributedString addAttribute:NSForegroundColorAttributeName value:firstWordColor range:firstWordRange]; + } + + return attributedString; +} + +@end diff --git a/examples/LayoutSpecPlayground/Sample/ViewController.h b/examples/LayoutSpecPlayground/Sample/ViewController.h new file mode 100644 index 0000000000..6e8c8d8ac1 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/ViewController.h @@ -0,0 +1,14 @@ +// +// ViewController.h +// ASLayoutSpecPlayground +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "AsyncDisplayKit.h" + +@interface ViewController : ASViewController + +@end + diff --git a/examples/LayoutSpecPlayground/Sample/ViewController.m b/examples/LayoutSpecPlayground/Sample/ViewController.m new file mode 100644 index 0000000000..1fe3c96084 --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/ViewController.m @@ -0,0 +1,92 @@ +// +// ViewController.m +// ASLayoutSpecPlayground +// +// Created by Hannah Troisi on 3/11/16. +// Copyright © 2016 Hannah Troisi. All rights reserved. +// + +#import "ViewController.h" +#import "PlaygroundContainerNode.h" +#import "ASLayoutElementInspectorNode.h" + +@interface ViewController () +@end + +@implementation ViewController +{ + ASPagerNode *_pagerNode; + ASSizeRange _sizeRange; +} + +#pragma mark - Lifecycle + +- (instancetype)init +{ + _pagerNode = [[ASPagerNode alloc] init]; + self = [super initWithNode:_pagerNode]; + + if (self) { + _pagerNode.delegate = self; + _pagerNode.dataSource = self; + self.navigationItem.title = @"ASLayoutSpec Playground"; + self.edgesForExtendedLayout = UIRectEdgeNone; + [ASLayoutElementInspectorNode sharedInstance].delegate = self; + } + + return self; +} + +#pragma mark - ASPagerNodeDataSource + +- (NSInteger)numberOfPagesInPagerNode:(ASPagerNode *)pagerNode +{ + return [PlaygroundContainerNode containerNodeCount]; +} + +- (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index +{ + return ^{ + PlaygroundContainerNode *containerCellNode = [[PlaygroundContainerNode alloc] initWithIndex:index]; + containerCellNode.delegate = self; + return containerCellNode; + }; +} + +// [ASViewController] Override this method to provide a custom size range to the backing node. +// Neccessary to allow the user to stretch / shrink the size of playground container. +- (ASSizeRange)nodeConstrainedSize +{ + if (CGSizeEqualToSize(_sizeRange.max, CGSizeZero)) { + return [super nodeConstrainedSize]; + } + return _sizeRange; +} + +- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath +{ + if (CGSizeEqualToSize(_sizeRange.max, CGSizeZero)) { + return [super nodeConstrainedSize]; + } + return _sizeRange; +} + +#pragma mark - PlaygroundContainerNodeDelegate + +- (void)relayoutWithSize:(ASSizeRange)size +{ +// NSLog(@"DELEGATE constrainedSize = %@", NSStringFromCGSize(size.max)); + _sizeRange = size; + [self.view setNeedsLayout]; + [_pagerNode reloadData]; +} + +#pragma mark - ASLayoutElementInspectorNodeDelegate + +- (void)toggleVisualization:(BOOL)toggle +{ + NSLog(@"shouldVisualizeLayoutSpecs:%d", toggle); + [self.node setShouldVisualizeLayoutSpecs:toggle]; +} + +@end diff --git a/examples/LayoutSpecPlayground/Sample/main.m b/examples/LayoutSpecPlayground/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/LayoutSpecPlayground/Sample/main.m @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * 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. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/LayoutSpecPlayground/Sample/resizeHandle.png b/examples/LayoutSpecPlayground/Sample/resizeHandle.png new file mode 100644 index 0000000000..86404bcb72 Binary files /dev/null and b/examples/LayoutSpecPlayground/Sample/resizeHandle.png differ