diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 0000000000..821cb2c82f --- /dev/null +++ b/.buckconfig @@ -0,0 +1,22 @@ +[cxx] + default_platform = iphonesimulator-x86_64 + combined_preprocess_and_compile = true + +[apple] + iphonesimulator_target_sdk_version = 8.0 + iphoneos_target_sdk_version = 8.0 + xctool_default_destination_specifier = platform=iOS Simulator, name=iPhone 6, OS=10.2 + +[alias] + lib = //:AsyncDisplayKit + tests = //:Tests + +[httpserver] + port = 8080 + +[project] + ide = xcode + ignore = .buckd, \ + .hg, \ + .git, \ + buck-out, \ diff --git a/.buckversion b/.buckversion new file mode 100644 index 0000000000..5dab7b7198 --- /dev/null +++ b/.buckversion @@ -0,0 +1 @@ +c948c20ebb155904909af05cfd16428a6992b98d diff --git a/.gitignore b/.gitignore index 7669d5a445..adb7e5eecc 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,8 @@ build timeline.xctimeline playground.xcworkspace +# Buck +/buck-out +/.buckconfig.local +/.buckd + diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index dbe5b7abbd..ed06972a21 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -26,7 +26,7 @@ Pod::Spec.new do |spec| 'AsyncDisplayKit/Layout/*.h', 'Base/*.h', 'AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h', - 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h', + 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h', 'AsyncDisplayKit/TextKit/ASTextKitComponents.h' ] diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ecf6969bba..7feb53b53d 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -93,7 +93,7 @@ 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; }; 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; }; 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; }; - 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */; }; 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; }; @@ -112,7 +112,7 @@ 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; }; 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; }; 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; - 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; + 257754BE1BEE458E00737CA5 /* ASTextKitComponents.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */; }; 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; 25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -203,8 +203,16 @@ 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */; settings = {ATTRIBUTES = (Public, ); }; }; 6907C2591DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; }; 6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; }; + 690C35611E055C5D00069B91 /* ASDimensionInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */; }; + 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */; }; + 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C35631E055C7B00069B91 /* ASDimensionInternal.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 690C35661E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */; }; + 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */; }; + 690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; 69127CFE1DD2B387004BF6E2 /* ASEventLog.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */; }; 693117CE1DC7C72700DE4784 /* ASDisplayNode+Deprecated.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */; }; + 693DA50F1E2536A600F66DF4 /* ASDimensionInternal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 690C35631E055C7B00069B91 /* ASDimensionInternal.h */; }; + 693DA5141E25373100F66DF4 /* ASDimensionDeprecated.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */; }; 69527B121DC84292004785FB /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */; }; 6959433E1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */; }; 6959433F1D70815300B0EE1F /* ASDisplayNodeLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */; }; @@ -673,6 +681,8 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + 693DA5141E25373100F66DF4 /* ASDimensionDeprecated.h in CopyFiles */, + 693DA50F1E2536A600F66DF4 /* ASDimensionInternal.h in CopyFiles */, 68C2155C1DE11AA80019C4BC /* ASObjectDescriptionHelpers.h in CopyFiles */, 68C2155B1DE11A790019C4BC /* ASCollectionViewLayoutInspector.h in CopyFiles */, DEB8ED7E1DD007F400DBDE55 /* ASLayoutElementInspectorNode.h in CopyFiles */, @@ -956,7 +966,7 @@ 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTailTruncater.h; path = TextKit/ASTextKitTailTruncater.h; sourceTree = ""; }; 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitTailTruncater.mm; path = TextKit/ASTextKitTailTruncater.mm; sourceTree = ""; }; 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTruncating.h; path = TextKit/ASTextKitTruncating.h; sourceTree = ""; }; - 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitComponents.m; path = TextKit/ASTextKitComponents.m; sourceTree = ""; }; + 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitComponents.mm; path = TextKit/ASTextKitComponents.mm; sourceTree = ""; }; 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitCoreTextAdditions.m; path = TextKit/ASTextKitCoreTextAdditions.m; sourceTree = ""; }; 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeWordKerner.h; path = TextKit/ASTextNodeWordKerner.h; sourceTree = ""; }; 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitComponents.h; path = TextKit/ASTextKitComponents.h; sourceTree = ""; }; @@ -1003,6 +1013,10 @@ 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASVisibilityProtocols.m; sourceTree = ""; }; 6907C2561DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASObjectDescriptionHelpers.h; sourceTree = ""; }; 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASObjectDescriptionHelpers.m; sourceTree = ""; }; + 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASDimensionInternal.mm; path = AsyncDisplayKit/Layout/ASDimensionInternal.mm; sourceTree = ""; }; + 690C35631E055C7B00069B91 /* ASDimensionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDimensionInternal.h; path = AsyncDisplayKit/Layout/ASDimensionInternal.h; sourceTree = ""; }; + 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASDimensionDeprecated.mm; path = AsyncDisplayKit/Layout/ASDimensionDeprecated.mm; sourceTree = ""; }; + 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDimensionDeprecated.h; path = AsyncDisplayKit/Layout/ASDimensionDeprecated.h; sourceTree = ""; }; 69527B111DC84292004785FB /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutElementStylePrivate.h; path = AsyncDisplayKit/Layout/ASLayoutElementStylePrivate.h; sourceTree = SOURCE_ROOT; }; 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeLayout.mm; sourceTree = ""; }; 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeLayout.h; sourceTree = ""; }; @@ -1658,7 +1672,7 @@ B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */, B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */, 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */, - 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */, + 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */, 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */, 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */, 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */, @@ -1731,6 +1745,10 @@ ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */, ACF6ED071B17843500DA7C62 /* ASDimension.h */, ACF6ED081B17843500DA7C62 /* ASDimension.mm */, + 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */, + 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */, + 690C35631E055C7B00069B91 /* ASDimensionInternal.h */, + 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */, ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */, ACF6ED0A1B17843500DA7C62 /* ASInsetLayoutSpec.mm */, ACF6ED0B1B17843500DA7C62 /* ASLayout.h */, @@ -1808,6 +1826,8 @@ buildActionMask = 2147483647; files = ( 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */, + 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */, + 690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */, 683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */, 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */, 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */, @@ -2195,6 +2215,7 @@ 8B0768B41CE752EC002E1453 /* ASDefaultPlaybackButton.m in Sources */, E55D86321CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, 68FC85E41CE29B7E00EDD713 /* ASTabBarController.m in Sources */, + 690C35661E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */, 058D0A23195D050800B7D73C /* _ASAsyncTransactionContainer.m in Sources */, 058D0A24195D050800B7D73C /* _ASAsyncTransactionGroup.m in Sources */, 68355B3A1CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m in Sources */, @@ -2287,7 +2308,7 @@ 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, - 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */, + 257754BE1BEE458E00737CA5 /* ASTextKitComponents.mm in Sources */, 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */, ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, @@ -2299,6 +2320,7 @@ 92074A691CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, ACF6ED321B17843500DA7C62 /* ASAbsoluteLayoutSpec.mm in Sources */, AC026B6B1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, + 690C35611E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 68355B311CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */, 68C215591DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */, 9CFFC6C01CCAC73C006A6476 /* ASViewController.mm in Sources */, @@ -2386,6 +2408,7 @@ 9C70F2091CDABA36007D6C76 /* ASViewController.mm in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, + 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */, 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */, @@ -2449,7 +2472,7 @@ B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, - 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */, + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */, 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */, 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */, AC6145441D8AFD4F003D62A2 /* ASSection.m in Sources */, @@ -2490,6 +2513,7 @@ AC026B6C1BD57D6F00BBC17E /* ASChangeSetDataController.mm in Sources */, 34EFC7741B701D0A00AD841F /* ASAbsoluteLayoutSpec.mm in Sources */, 92074A6A1CC8BADA00918F75 /* ASControlNode+tvOS.m in Sources */, + 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */, 68C2155A1DE10D330019C4BC /* ASCollectionViewLayoutInspector.m in Sources */, DB78412E1C6BCE1600A9E2B4 /* _ASTransitionContext.m in Sources */, B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, diff --git a/AsyncDisplayKit/ASCellNode.h b/AsyncDisplayKit/ASCellNode.h index 255db70ff5..f653ece0b3 100644 --- a/AsyncDisplayKit/ASCellNode.h +++ b/AsyncDisplayKit/ASCellNode.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN -@class ASCellNode; +@class ASCellNode, ASTextNode; typedef NSUInteger ASCellNodeAnimation; @@ -92,12 +92,6 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ @property (nonatomic, strong, readonly, nullable) UICollectionViewLayoutAttributes *layoutAttributes; -/* - * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. - */ -//@property (nonatomic, retain) UIColor *backgroundColor; -@property (nonatomic) UITableViewCellSelectionStyle selectionStyle; - /** * A Boolean value that is synchronized with the underlying collection or tableView cell property. * Setting this value is equivalent to calling selectItem / deselectItem on the collection or table. @@ -169,6 +163,25 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ - (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(nullable UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame; +#pragma mark - UITableViewCell specific passthrough properties + +/* @abstract The selection style when a tap on a cell occurs + * @default UITableViewCellSelectionStyleDefault + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UITableViewCellSelectionStyle selectionStyle; + +/* @abstract The accessory type view on the right side of the cell. Please take care of your ASLayoutSpec so that doesn't overlay the accessoryView + * @default UITableViewCellAccessoryNone + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UITableViewCellAccessoryType accessoryType; + +/* @abstract The seperator inset of the cell seperator line + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UIEdgeInsets seperatorInset; + @end @interface ASCellNode (Unavailable) @@ -207,6 +220,11 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ @property (nonatomic, assign) UIEdgeInsets textInsets; +/** + * The text node used by this cell node. + */ +@property (nonatomic, strong, readonly) ASTextNode *textNode; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index c55e2f25a1..37f6d9f0f4 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -397,13 +397,6 @@ static NSMutableSet *__cellClassesForVisibilityNotifications = nil; // See +init #pragma mark - #pragma mark ASTextCellNode -@interface ASTextCellNode () - -@property (nonatomic, strong) ASTextNode *textNode; - -@end - - @implementation ASTextCellNode static const CGFloat kASTextCellNodeDefaultFontSize = 18.0f; @@ -422,7 +415,7 @@ static const CGFloat kASTextCellNodeDefaultVerticalPadding = 11.0f; _textInsets = textInsets; _textAttributes = [textAttributes copy]; _textNode = [[ASTextNode alloc] init]; - [self addSubnode:_textNode]; + self.automaticallyManagesSubnodes = YES; } return self; } diff --git a/AsyncDisplayKit/ASCollectionNode.h b/AsyncDisplayKit/ASCollectionNode.h index fdb6b3a6c5..3c77aed7c9 100644 --- a/AsyncDisplayKit/ASCollectionNode.h +++ b/AsyncDisplayKit/ASCollectionNode.h @@ -173,7 +173,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. @@ -184,7 +184,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 580e0ef4ce..9069614a05 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -227,7 +227,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); /** * Perform a batch of updates asynchronously. This method must be called from the main thread. @@ -238,7 +238,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); /** * Reload everything from scratch, destroying the working range and all cached nodes. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index aeaf1ed431..81d8d6a0c5 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -78,6 +78,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; ASDisplayNodeAssertMainThread(); node.layoutAttributes = _layoutAttributes; _node = node; + self.backgroundColor = node.backgroundColor; + self.clipsToBounds = node.clipsToBounds; [node __setSelectedFromUIKit:self.selected]; [node __setHighlightedFromUIKit:self.highlighted]; } @@ -121,6 +123,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; self.layoutAttributes = layoutAttributes; } +/** + * Keep our node filling our content view. + */ +- (void)layoutSubviews +{ + [super layoutSubviews]; + self.node.frame = self.contentView.bounds; +} + @end #pragma mark - @@ -282,7 +293,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; - + + // Disable UICollectionView prefetching. + // Experiments done by Instagram show that this option being YES (default) + // when unused causes a significant hit to scroll performance. + // https://github.com/Instagram/IGListKit/issues/318 + if (AS_AT_LEAST_IOS10) { + self.prefetchingEnabled = NO; + } + _layoutController = [[ASCollectionViewLayoutController alloc] initWithCollectionView:self]; _rangeController = [[ASRangeController alloc] init]; @@ -614,6 +633,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (NSIndexPath *)convertIndexPathFromCollectionNode:(NSIndexPath *)indexPath waitingIfNeeded:(BOOL)wait { + if (indexPath == nil) { + return nil; + } + // If this is a section index path, we don't currently have a method // to do a mapping. if (indexPath.item == NSNotFound) { @@ -751,7 +774,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (void)performBatchUpdates:(void (^)())updates completion:(void (^)(BOOL))completion { - [self performBatchAnimated:YES updates:updates completion:completion]; + // We capture the current state of whether animations are enabled if they don't provide us with one. + [self performBatchAnimated:[UIView areAnimationsEnabled] updates:updates completion:completion]; } - (void)registerSupplementaryNodeOfKind:(NSString *)elementKind diff --git a/AsyncDisplayKit/ASControlNode.mm b/AsyncDisplayKit/ASControlNode.mm index d89bf412fb..794087f968 100644 --- a/AsyncDisplayKit/ASControlNode.mm +++ b/AsyncDisplayKit/ASControlNode.mm @@ -63,6 +63,14 @@ id _ASControlNodeEventKeyForControlEvent(ASControlNodeEvent controlEv */ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, void (^block)(ASControlNodeEvent anEvent)); +/** + @abstract Returns the expanded bounds used to determine if a touch is considered 'inside' during tracking. + @param controlNode A control node. + @result The expanded bounds of the node. + */ +CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode); + + @end @implementation ASControlNode @@ -158,7 +166,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v BOOL dragIsInsideBounds = [self pointInside:touchLocation withEvent:nil]; // Update our highlighted state. - CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); + CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self); BOOL dragIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); self.touchInside = dragIsInsideExpandedBounds; self.highlighted = dragIsInsideExpandedBounds; @@ -216,7 +224,7 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v [self endTrackingWithTouch:theTouch withEvent:event]; // Send the appropriate touch-up control event. - CGRect expandedBounds = CGRectInset(self.view.bounds, kASControlNodeExpandedInset, kASControlNodeExpandedInset); + CGRect expandedBounds = _ASControlNodeGetExpandedBounds(self); BOOL touchUpIsInsideExpandedBounds = CGRectContainsPoint(expandedBounds, touchLocation); [self sendActionsForControlEvents:(touchUpIsInsideExpandedBounds ? ASControlNodeEventTouchUpInside : ASControlNodeEventTouchUpOutside) @@ -428,6 +436,10 @@ void _ASEnumerateControlEventsIncludedInMaskWithBlock(ASControlNodeEvent mask, v } } +CGRect _ASControlNodeGetExpandedBounds(ASControlNode *controlNode) { + return CGRectInset(UIEdgeInsetsInsetRect(controlNode.view.bounds, controlNode.hitTestSlop), kASControlNodeExpandedInset, kASControlNodeExpandedInset); +} + #pragma mark - For Subclasses - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)touchEvent { diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 7cc1272af5..c759bee0a3 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -195,7 +195,7 @@ NS_ASSUME_NONNULL_BEGIN * @note Called on the display queue and/or main queue (MUST BE THREAD SAFE) */ + (void)drawRect:(CGRect)bounds withParameters:(nullable id )parameters - isCancelled:(__attribute((noescape)) asdisplaynode_iscancelled_block_t)isCancelledBlock + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; /** @@ -212,7 +212,7 @@ NS_ASSUME_NONNULL_BEGIN * @note Called on the display queue and/or main queue (MUST BE THREAD SAFE) */ + (nullable UIImage *)displayWithParameters:(nullable id)parameters - isCancelled:(__attribute((noescape)) asdisplaynode_iscancelled_block_t)isCancelledBlock; + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; /** * @abstract Delegate override for drawParameters diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 65a9bd001a..d2244d1305 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1805,6 +1805,21 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + /** + * When passed node=nil, all methods in this family use the UIView-style + * behavior – that is, convert from/to window coordinates if there's a window, + * otherwise return the point untransformed. + */ + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertPoint:point fromLayer:window.layer]; + } else { + return point; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); @@ -1820,6 +1835,16 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertPoint:point toLayer:window.layer]; + } else { + return point; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); @@ -1835,6 +1860,16 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertRect:rect fromLayer:window.layer]; + } else { + return rect; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); @@ -1850,6 +1885,16 @@ static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNo - (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); + + if (node == nil && self.nodeLoaded) { + CALayer *layer = self.layer; + if (UIWindow *window = ASFindWindowOfLayer(layer)) { + return [layer convertRect:rect toLayer:window.layer]; + } else { + return rect; + } + } + // Get root node of the accessible node hierarchy, if node not specified node = node ? : ASDisplayNodeUltimateParentOfNode(self); diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index 9993fa91cb..82affa2ec5 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -125,6 +125,11 @@ extern ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernode(ASDisplayNode * */ extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSupernodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT; +/** + * Given a layer, find the window it lives in, if any. + */ +extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) AS_WARN_UNUSED_RESULT; + /** * Given two nodes, finds their most immediate common parent. Used for geometry conversion methods. * NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 45d64bcd7e..aa0588c646 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -249,6 +249,21 @@ static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possible return NO; } +extern UIWindow * _Nullable ASFindWindowOfLayer(CALayer *layer) +{ + while (layer != nil) { + if (UIView *view = ASDynamicCast(layer.delegate, UIView)) { + if ([view isKindOfClass:[UIWindow class]]) { + return (UIWindow *)view; + } else { + return view.window; + } + } + layer = layer.superlayer; + } + return nil; +} + extern ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2) { ASDisplayNode *possibleAncestor = node1; diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index c9f61d88f7..7d2602aec3 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -93,16 +93,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readwrite) UIEdgeInsets textContainerInset; /** - @abstract properties. + @abstract The maximum number of lines to display. Additional lines will require scrolling. + @default 0 (No limit) */ -@property(nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences -@property(nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault -@property(nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault; -@property(nonatomic, readwrite, assign) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault -@property(nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault -@property(nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum) -@property(nonatomic, readwrite, assign) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents) -@property(nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO +@property (nonatomic, assign) NSUInteger maximumLinesToDisplay; /** @abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user. @@ -125,6 +119,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGRect)frameForTextRange:(NSRange)textRange AS_WARN_UNUSED_RESULT; +/** + @abstract properties. + */ +@property(nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences +@property(nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault +@property(nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault; +@property(nonatomic, readwrite, assign) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault +@property(nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault +@property(nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum) +@property(nonatomic, readwrite, assign) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents) +@property(nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO + @end @interface ASEditableTextNode (Unavailable) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 4fd2dfdc8f..8507189bdf 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -238,7 +238,16 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; - CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; + + CGSize textSize; + + if (_maximumLinesToDisplay > 0) { + textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width + forMaxNumberOfLines: _maximumLinesToDisplay]; + } else { + textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; + } + CGFloat width = std::ceil(textSize.width + _textContainerInset.left + _textContainerInset.right); CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom); return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height)); @@ -313,6 +322,12 @@ return _textKitComponents.textView; } +- (void)setMaximumLinesToDisplay:(NSUInteger)maximumLines +{ + _maximumLinesToDisplay = maximumLines; + [self setNeedsLayout]; +} + #pragma mark - @dynamic typingAttributes; diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 7ff1bb131d..51f59398c4 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -27,6 +27,7 @@ #import "ASEqualityHelpers.h" #import "ASEqualityHashHelpers.h" #import "ASWeakMap.h" +#import "CoreGraphics+ASConvenience.h" // TODO: It would be nice to remove this dependency; it's the only subclass using more than +FrameworkSubclasses.h #import "ASDisplayNodeInternal.h" @@ -183,6 +184,26 @@ struct ASImageNodeDrawParameters { [self invalidateAnimatedImage]; } +- (UIImage *)placeholderImage +{ + // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. + // This would completely eliminate the memory and performance cost of the backing store. + CGSize size = self.calculatedSize; + if ((size.width * size.height) < CGFLOAT_EPSILON) { + return nil; + } + + ASDN::MutexLocker l(__instanceLock__); + + UIGraphicsBeginImageContext(size); + [self.placeholderColor setFill]; + UIRectFill(CGRectMake(0, 0, size.width, size.height)); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + + return image; +} + #pragma mark - Layout and Sizing - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 1b4488d922..a5abf93b3c 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -27,9 +27,6 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; @interface ASNetworkImageNode () { - __weak id _cache; - __weak id _downloader; - // Only access any of these with __instanceLock__. __weak id _delegate; @@ -55,6 +52,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; } _delegateFlags; //set on init only + __weak id _downloader; + __weak id _cache; + struct { unsigned int downloaderImplementsSetProgress:1; unsigned int downloaderImplementsSetPriority:1; @@ -238,11 +238,13 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages { - ASDN::MutexLocker l(__instanceLock__); - if (shouldRenderProgressImages == _shouldRenderProgressImages) { - return; + { + ASDN::MutexLocker l(__instanceLock__); + if (shouldRenderProgressImages == _shouldRenderProgressImages) { + return; + } + _shouldRenderProgressImages = shouldRenderProgressImages; } - _shouldRenderProgressImages = shouldRenderProgressImages; [self _updateProgressImageBlockOnDownloaderIfNeeded]; } @@ -295,28 +297,41 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)didEnterVisibleState { [super didEnterVisibleState]; - ASDN::MutexLocker l(__instanceLock__); + + id downloadIdentifier = nil; + + { + ASDN::MutexLocker l(__instanceLock__); - if (_downloaderFlags.downloaderImplementsSetPriority) { - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier]; + if (_downloaderFlags.downloaderImplementsSetPriority) { + downloadIdentifier = _downloadIdentifier; } } + if (downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:downloadIdentifier]; + } + [self _updateProgressImageBlockOnDownloaderIfNeeded]; } - (void)didExitVisibleState { [super didExitVisibleState]; - ASDN::MutexLocker l(__instanceLock__); + + id downloadIdentifier = nil; - if (_downloaderFlags.downloaderImplementsSetPriority) { - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier]; + { + ASDN::MutexLocker l(__instanceLock__); + if (_downloaderFlags.downloaderImplementsSetPriority) { + downloadIdentifier = _downloadIdentifier; } } + if (downloadIdentifier != nil) { + [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:downloadIdentifier]; + } + [self _updateProgressImageBlockOnDownloaderIfNeeded]; } @@ -372,9 +387,16 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; } // Read state. - BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); - id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; - id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; + BOOL shouldRender; + id oldDownloadIDForProgressBlock; + id newDownloadIDForProgressBlock; + BOOL clearAndReattempt = NO; + { + ASDN::MutexLocker l(__instanceLock__); + shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); + oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; + newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; + } // If we're already bound to the correct download, we're done. if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) { @@ -395,7 +417,19 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; } // Update state. - _downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock; + { + ASDN::MutexLocker l(__instanceLock__); + if (_downloadIdentifierForProgressBlock == oldDownloadIDForProgressBlock) { + _downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock; + } else { + clearAndReattempt = YES; + } + } + + if (clearAndReattempt) { + [_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock]; + [self _updateProgressImageBlockOnDownloaderIfNeeded]; + } } - (void)_cancelDownloadAndClearImage @@ -445,19 +479,46 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished { ASPerformBlockOnBackgroundThread(^{ + NSURL *url; + id downloadIdentifier; + BOOL cancelAndReattempt = NO; - ASDN::MutexLocker l(__instanceLock__); - _downloadIdentifier = [_downloader downloadImageWithURL:_URL - callbackQueue:dispatch_get_main_queue() - downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier); - } - }]; + // Below, to avoid performance issues, we're calling downloadImageWithURL without holding the lock. This is a bit ugly because + // We need to reobtain the lock after and ensure that the task we've kicked off still matches our URL. If not, we need to cancel + // it and try again. + { + ASDN::MutexLocker l(__instanceLock__); + url = _URL; + } + + downloadIdentifier = [_downloader downloadImageWithURL:url + callbackQueue:dispatch_get_main_queue() + downloadProgress:NULL + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { + if (finished != NULL) { + finished(imageContainer, error, downloadIdentifier); + } + }]; + { + ASDN::MutexLocker l(__instanceLock__); + if ([_URL isEqual:url]) { + // The download we kicked off is correct, no need to do any more work. + _downloadIdentifier = downloadIdentifier; + } else { + // The URL changed since we kicked off our download task. This shouldn't happen often so we'll pay the cost and + // cancel that request and kick off a new one. + cancelAndReattempt = YES; + } + } + + if (cancelAndReattempt) { + [_downloader cancelImageDownloadForIdentifier:downloadIdentifier]; + [self _downloadImageWithCompletion:finished]; + return; + } + [self _updateProgressImageBlockOnDownloaderIfNeeded]; - }); } diff --git a/AsyncDisplayKit/ASRunLoopQueue.mm b/AsyncDisplayKit/ASRunLoopQueue.mm index 7e50424a59..076b6cc56a 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.mm +++ b/AsyncDisplayKit/ASRunLoopQueue.mm @@ -60,19 +60,21 @@ static void runLoopSourceCallback(void *info) { // 100ms timer. No resources are wasted in between, as the thread sleeps, and each check is fast. // This time is fast enough for most use cases without excessive churn. CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(NULL, -1, 0.1, 0, 0, ^(CFRunLoopTimerRef timer) { + @autoreleasepool { #if ASRunLoopQueueLoggingEnabled - NSLog(@"ASDeallocQueue Processing: %d objects destroyed", weakSelf->_queue.size()); + NSLog(@"ASDeallocQueue Processing: %d objects destroyed", weakSelf->_queue.size()); #endif - weakSelf->_queueLock.lock(); - std::deque currentQueue = weakSelf->_queue; - if (currentQueue.size() == 0) { + weakSelf->_queueLock.lock(); + std::deque currentQueue = weakSelf->_queue; + if (currentQueue.size() == 0) { + weakSelf->_queueLock.unlock(); + return; + } + // Sometimes we release 10,000 objects at a time. Don't hold the lock while releasing. + weakSelf->_queue = std::deque(); weakSelf->_queueLock.unlock(); - return; + currentQueue.clear(); } - // Sometimes we release 10,000 objects at a time. Don't hold the lock while releasing. - weakSelf->_queue = std::deque(); - weakSelf->_queueLock.unlock(); - currentQueue.clear(); }); CFRunLoopRef runloop = CFRunLoopGetCurrent(); diff --git a/AsyncDisplayKit/ASScrollNode.mm b/AsyncDisplayKit/ASScrollNode.mm index 8e6c719d8f..50a3302366 100644 --- a/AsyncDisplayKit/ASScrollNode.mm +++ b/AsyncDisplayKit/ASScrollNode.mm @@ -92,7 +92,7 @@ } // Don't provide a position, as that should be set by the parent. layout = [ASLayout layoutWithLayoutElement:self - size:parentSize + size:selfSize sublayouts:layout.sublayouts]; } return layout; diff --git a/AsyncDisplayKit/ASTableNode.h b/AsyncDisplayKit/ASTableNode.h index e2d93309cb..a2fed7726d 100644 --- a/AsyncDisplayKit/ASTableNode.h +++ b/AsyncDisplayKit/ASTableNode.h @@ -150,7 +150,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. @@ -161,7 +161,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 99d491ea8e..eb819ff854 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -76,6 +76,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)setNode:(ASCellNode *)node { _node = node; + + self.backgroundColor = node.backgroundColor; + self.selectionStyle = node.selectionStyle; + self.accessoryType = node.accessoryType; + self.separatorInset = node.seperatorInset; + self.clipsToBounds = node.clipsToBounds; + [node __setSelectedFromUIKit:self.selected]; [node __setHighlightedFromUIKit:self.highlighted]; } @@ -593,6 +600,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode waitingIfNeeded:(BOOL)wait { + if (cellNode == nil) { + return nil; + } + NSIndexPath *indexPath = [_dataController completedIndexPathForNode:cellNode]; indexPath = [self validateIndexPath:indexPath]; if (indexPath == nil && wait) { @@ -627,7 +638,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)endUpdates { - [self endUpdatesAnimated:YES completion:nil]; + // We capture the current state of whether animations are enabled if they don't provide us with one. + [self endUpdatesAnimated:[UIView areAnimationsEnabled] completion:nil]; } - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL completed))completion; @@ -801,13 +813,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [_rangeController configureContentView:cell.contentView forCellNode:node]; cell.node = node; - cell.backgroundColor = node.backgroundColor; - cell.selectionStyle = node.selectionStyle; - - // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) - // This is actually a workaround for a bug we are seeing in some rare cases (selected background view - // overlaps other cells if size of ASCellNode has changed.) - cell.clipsToBounds = node.clipsToBounds; } return cell; @@ -816,7 +821,18 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - return node.calculatedSize.height; + CGFloat height = node.calculatedSize.height; + + /** + * Weirdly enough, Apple expects the return value here to _include_ the height + * of the separator, if there is one! So if our node wants to be 43.5, we need + * to return 44. UITableView will make a cell of height 44 with a content view + * of height 43.5. + */ + if (tableView.separatorStyle != UITableViewCellSeparatorStyleNone) { + height += 1.0 / ASScreenScale(); + } + return height; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 9c9ccdfbc9..7dfc43ca10 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) int32_t periodicTimeObserverTimescale; //! Defaults to AVLayerVideoGravityResizeAspect -@property (nonatomic) NSString *gravity; +@property (nonatomic, copy) NSString *gravity; @property (nullable, nonatomic, weak, readwrite) id delegate; diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index d098499ed5..18463df245 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -43,7 +43,6 @@ static NSString * const kRate = @"rate"; @interface ASVideoNode () { - __weak id _delegate; struct { unsigned int delegateVideNodeShouldChangePlayerStateTo:1; unsigned int delegateVideoDidPlayToEnd:1; @@ -88,6 +87,8 @@ static NSString * const kRate = @"rate"; @implementation ASVideoNode +@dynamic delegate; + // TODO: Support preview images with HTTP Live Streaming videos. #pragma mark - Construction and Layout @@ -163,7 +164,7 @@ static NSString * const kRate = @"rate"; } if (_delegateFlags.delegateVideoNodeDidSetCurrentItem) { - [_delegate videoNode:self didSetCurrentItem:playerItem]; + [self.delegate videoNode:self didSetCurrentItem:playerItem]; } if (self.image == nil && self.URL == nil) { @@ -316,6 +317,9 @@ static NSString * const kRate = @"rate"; if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { if (self.playerState != ASVideoNodePlayerStatePlaying) { self.playerState = ASVideoNodePlayerStateReadyToPlay; + if (_shouldBePlaying && ASInterfaceStateIncludesVisible(self.interfaceState)) { + [self play]; + } } // If we don't yet have a placeholder image update it now that we should have data available for it if (self.image == nil && self.URL == nil) { @@ -334,7 +338,7 @@ static NSString * const kRate = @"rate"; } if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) { if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [_delegate videoNodeDidRecoverFromStall:self]; + [self.delegate videoNodeDidRecoverFromStall:self]; } [self play]; // autoresume after buffer catches up } @@ -359,7 +363,7 @@ static NSString * const kRate = @"rate"; - (void)tapped { if (_delegateFlags.delegateDidTapVideoNode) { - [_delegate didTapVideoNode:self]; + [self.delegate didTapVideoNode:self]; } else { if (_shouldBePlaying) { @@ -383,14 +387,14 @@ static NSString * const kRate = @"rate"; self.playerState = ASVideoNodePlayerStateLoading; if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { - [_delegate videoNodeDidStartInitialLoading:self]; + [self.delegate videoNodeDidStartInitialLoading:self]; } NSArray *requestedKeys = @[@"playable"]; [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler:^{ ASPerformBlockOnMainThread(^{ if (_delegateFlags.delegateVideoNodeDidFinishInitialLoading) { - [_delegate videoNodeDidFinishInitialLoading:self]; + [self.delegate videoNodeDidFinishInitialLoading:self]; } [self prepareToPlayAsset:asset withKeys:requestedKeys]; }); @@ -405,7 +409,7 @@ static NSString * const kRate = @"rate"; } if (_delegateFlags.delegateVideoNodeDidPlayToTimeInterval) { - [_delegate videoNode:self didPlayToTimeInterval:timeInSeconds]; + [self.delegate videoNode:self didPlayToTimeInterval:timeInSeconds]; } } @@ -465,7 +469,7 @@ static NSString * const kRate = @"rate"; } if (_delegateFlags.delegateVideoNodeWillChangePlayerStateToState) { - [_delegate videoNode:self willChangePlayerState:oldState toState:playerState]; + [self.delegate videoNode:self willChangePlayerState:oldState toState:playerState]; } _playerState = playerState; @@ -564,28 +568,23 @@ static NSString * const kRate = @"rate"; return (AVPlayerLayer *)_playerNode.layer; } -- (id)delegate{ - return _delegate; -} - - (void)setDelegate:(id)delegate { [super setDelegate:delegate]; - _delegate = delegate; - if (_delegate == nil) { + if (delegate == nil) { memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { - _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [_delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)]; - _delegateFlags.delegateVideoDidPlayToEnd = [_delegate respondsToSelector:@selector(videoDidPlayToEnd:)]; - _delegateFlags.delegateDidTapVideoNode = [_delegate respondsToSelector:@selector(didTapVideoNode:)]; - _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [_delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)]; - _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidStartInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [_delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; - _delegateFlags.delegateVideoNodeDidSetCurrentItem = [_delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; - _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [_delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; - _delegateFlags.delegateVideoNodeDidRecoverFromStall = [_delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; + _delegateFlags.delegateVideNodeShouldChangePlayerStateTo = [delegate respondsToSelector:@selector(videoNode:shouldChangePlayerStateTo:)]; + _delegateFlags.delegateVideoDidPlayToEnd = [delegate respondsToSelector:@selector(videoDidPlayToEnd:)]; + _delegateFlags.delegateDidTapVideoNode = [delegate respondsToSelector:@selector(didTapVideoNode:)]; + _delegateFlags.delegateVideoNodeWillChangePlayerStateToState = [delegate respondsToSelector:@selector(videoNode:willChangePlayerState:toState:)]; + _delegateFlags.delegateVideoNodeDidPlayToTimeInterval = [delegate respondsToSelector:@selector(videoNode:didPlayToTimeInterval:)]; + _delegateFlags.delegateVideoNodeDidStartInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidStartInitialLoading:)]; + _delegateFlags.delegateVideoNodeDidFinishInitialLoading = [delegate respondsToSelector:@selector(videoNodeDidFinishInitialLoading:)]; + _delegateFlags.delegateVideoNodeDidSetCurrentItem = [delegate respondsToSelector:@selector(videoNode:didSetCurrentItem:)]; + _delegateFlags.delegateVideoNodeDidStallAtTimeInterval = [delegate respondsToSelector:@selector(videoNode:didStallAtTimeInterval:)]; + _delegateFlags.delegateVideoNodeDidRecoverFromStall = [delegate respondsToSelector:@selector(videoNodeDidRecoverFromStall:)]; } } @@ -677,7 +676,7 @@ static NSString * const kRate = @"rate"; - (BOOL)isStateChangeValid:(ASVideoNodePlayerState)state { if (_delegateFlags.delegateVideNodeShouldChangePlayerStateTo) { - if (![_delegate videoNode:self shouldChangePlayerStateTo:state]) { + if (![self.delegate videoNode:self shouldChangePlayerStateTo:state]) { return NO; } } @@ -704,7 +703,7 @@ static NSString * const kRate = @"rate"; { self.playerState = ASVideoNodePlayerStateFinished; if (_delegateFlags.delegateVideoDidPlayToEnd) { - [_delegate videoDidPlayToEnd:self]; + [self.delegate videoDidPlayToEnd:self]; } if (_shouldAutorepeat) { @@ -719,7 +718,7 @@ static NSString * const kRate = @"rate"; { self.playerState = ASVideoNodePlayerStateLoading; if (_delegateFlags.delegateVideoNodeDidStallAtTimeInterval) { - [_delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; + [self.delegate videoNode:self didStallAtTimeInterval:CMTimeGetSeconds(_player.currentItem.currentTime)]; } } diff --git a/AsyncDisplayKit/ASVideoPlayerNode.h b/AsyncDisplayKit/ASVideoPlayerNode.h index 60ca7da5c8..cbe6ba65ad 100644 --- a/AsyncDisplayKit/ASVideoPlayerNode.h +++ b/AsyncDisplayKit/ASVideoPlayerNode.h @@ -58,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN //! Defaults to 100 @property (nonatomic, assign) int32_t periodicTimeObserverTimescale; //! Defaults to AVLayerVideoGravityResizeAspect -@property (nonatomic) NSString *gravity; +@property (nonatomic, copy) NSString *gravity; - (instancetype)initWithUrl:(NSURL*)url; - (instancetype)initWithAsset:(AVAsset*)asset; diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 136016e4c3..fe33989ca9 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -48,6 +48,8 @@ #import #import +#import +#import #import #import #import diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index d7beb4f23f..1a17e3dd3f 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -76,12 +76,11 @@ extern NSString * const ASCollectionInvalidUpdateException; */ @protocol ASDataControllerDelegate -@optional - /** Called for batch update. */ - (void)dataControllerBeginUpdates:(ASDataController *)dataController; +- (void)dataControllerWillDeleteAllData:(ASDataController *)dataController; - (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL))completion; /** diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 83c6b8af4e..fa688f4938 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -454,6 +454,7 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat // -beginUpdates [_mainSerialQueue performBlockOnMainThread:^{ [_delegate dataControllerBeginUpdates:self]; + [_delegate dataControllerWillDeleteAllData:self]; }]; // deleteSections: @@ -673,40 +674,7 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - moveSection"); - - if (!_initialReloadDataHasBeenCalled) { - return; - } - - NSMutableArray *rowContexts = _nodeContexts[ASDataControllerRowNodeKind]; - NSArray *contexts = rowContexts[section]; - [rowContexts removeObjectAtIndex:section]; - [rowContexts insertObject:contexts atIndex:section]; - - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willMoveSection:section toSection:newSection]; - - // remove elements - - LOG(@"Edit Transaction - moveSection"); - NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // update the section of indexpaths - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection]; - [updatedIndexPaths addObject:updatedIndexPath]; - } - - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the move will be processed along with the current batch of updates.", NSStringFromSelector(_cmd)); } @@ -879,29 +847,7 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - ASDisplayNodeAssertMainThread(); - if (!_initialReloadDataHasBeenCalled) { - return; - } - - NSMutableArray *contexts = _nodeContexts[ASDataControllerRowNodeKind]; - ASIndexedNodeContext *context = contexts[indexPath.section][indexPath.item]; - [contexts[indexPath.section] removeObjectAtIndex:indexPath.item]; - [contexts[newIndexPath.section] insertObject:context atIndex:newIndexPath.item]; - - LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); - NSArray *indexPaths = @[indexPath]; - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - - // Don't re-calculate size for moving - NSArray *newIndexPaths = @[newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; - }); + ASDisplayNodeAssert(NO, @"ASDataController does not support %@. Call this on ASChangeSetDataController and the move will be processed along with the current batch of updates.", NSStringFromSelector(_cmd)); } #pragma mark - Data Querying (Subclass API) @@ -953,6 +899,9 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); + if (indexPath == nil) { + return nil; + } NSArray *contexts = _nodeContexts[ASDataControllerRowNodeKind]; NSInteger section = indexPath.section; @@ -972,6 +921,9 @@ NSString * const ASCollectionInvalidUpdateException = @"ASCollectionInvalidUpdat - (ASCellNode *)nodeAtCompletedIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); + if (indexPath == nil) { + return nil; + } NSArray *completedNodes = [self completedNodes]; NSInteger section = indexPath.section; diff --git a/AsyncDisplayKit/Details/ASFlowLayoutController.mm b/AsyncDisplayKit/Details/ASFlowLayoutController.mm index a96c0e6815..69a7a8745f 100644 --- a/AsyncDisplayKit/Details/ASFlowLayoutController.mm +++ b/AsyncDisplayKit/Details/ASFlowLayoutController.mm @@ -59,17 +59,9 @@ ASRangeTuningParameters tuningParameters = [self tuningParametersForRangeMode:rangeMode rangeType:rangeType]; if (_layoutDirection == ASFlowLayoutDirectionHorizontal) { - ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || - scrollDirection == ASScrollDirectionLeft || - scrollDirection == ASScrollDirectionRight, @"Invalid scroll direction"); - viewportDirectionalSize = viewportSize.width; directionalBuffer = ASDirectionalScreenfulBufferHorizontal(scrollDirection, tuningParameters); } else { - ASDisplayNodeAssert(scrollDirection == ASScrollDirectionNone || - scrollDirection == ASScrollDirectionUp || - scrollDirection == ASScrollDirectionDown, @"Invalid scroll direction"); - viewportDirectionalSize = viewportSize.height; directionalBuffer = ASDirectionalScreenfulBufferVertical(scrollDirection, tuningParameters); } diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index b7576e4efa..6c18674b02 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -41,6 +41,12 @@ BOOL _preserveCurrentRangeMode; BOOL _didRegisterForNodeDisplayNotifications; CFTimeInterval _pendingDisplayNodesTimestamp; + + // If the user is not currently scrolling, we will keep our ranges + // configured to match their previous scroll direction. Defaults + // to [.right, .down] so that when the user first opens a screen + // the ranges point down into the content. + ASScrollDirection _previousScrollDirection; #if AS_RANGECONTROLLER_LOG_UPDATE_FREQ NSUInteger _updateCountThisFrame; @@ -65,6 +71,7 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; _rangeIsValid = YES; _currentRangeMode = ASLayoutRangeModeInvalid; _preserveCurrentRangeMode = NO; + _previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight; [[[self class] allRangeControllersWeakSet] addObject:self]; @@ -216,7 +223,13 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; } ASProfilingSignpostStart(1, self); + // Get the scroll direction. Default to using the previous one, if they're not scrolling. ASScrollDirection scrollDirection = [_dataSource scrollDirectionForRangeController:self]; + if (scrollDirection == ASScrollDirectionNone) { + scrollDirection = _previousScrollDirection; + } + _previousScrollDirection = scrollDirection; + if (_layoutControllerImplementsSetViewportSize) { [_layoutController setViewportSize:[_dataSource viewportSizeForRangeController:self]]; } @@ -478,6 +491,11 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; [_delegate didBeginUpdatesInRangeController:self]; } +- (void)dataControllerWillDeleteAllData:(ASDataController *)dataController +{ + [self _setVisibleNodes:nil]; +} + - (void)dataController:(ASDataController *)dataController endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.h b/AsyncDisplayKit/Details/_ASDisplayLayer.h index a518f61828..ce77ffd3ce 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.h +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.h @@ -9,6 +9,7 @@ // #import +#import @class ASDisplayNode; @protocol _ASDisplayLayerDelegate; @@ -103,7 +104,7 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store. */ -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(__attribute((noescape)) asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; ++ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; /** @summary Delegate override to provide new layer contents as a UIImage. @@ -111,19 +112,19 @@ typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here. */ -+ (UIImage *)displayWithParameters:(id)parameters isCancelled:(__attribute((noescape)) asdisplaynode_iscancelled_block_t)isCancelledBlock; ++ (UIImage *)displayWithParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; /** * @abstract instance version of drawRect class method * @see drawRect:withParameters:isCancelled:isRasterizing class method */ -- (void)drawRect:(CGRect)bounds withParameters:(id )parameters isCancelled:(__attribute((noescape)) asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; +- (void)drawRect:(CGRect)bounds withParameters:(id )parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; /** * @abstract instance version of display class method * @see displayWithParameters:isCancelled class method */ -- (UIImage *)displayWithParameters:(id )parameters isCancelled:(__attribute((noescape)) asdisplaynode_iscancelled_block_t)isCancelled; +- (UIImage *)displayWithParameters:(id )parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled; // Called on the main thread only diff --git a/AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h b/AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h index 8d97647132..2c222ae1af 100644 --- a/AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h +++ b/AsyncDisplayKit/Layout/ASAbsoluteLayoutElement.h @@ -9,7 +9,7 @@ // #import -#import +#import NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h index c7ff156c62..5bc21215a9 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -13,6 +13,11 @@ #import #import +ASDISPLAYNODE_EXTERN_C_BEGIN +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - + ASDISPLAYNODE_INLINE BOOL AS_WARN_UNUSED_RESULT ASPointsValidForLayout(CGFloat points) { return ((isnormal(points) || points == 0.0) && points >= 0.0 && points < (CGFLOAT_MAX / 2.0)); @@ -33,6 +38,8 @@ ASDISPLAYNODE_INLINE BOOL AS_WARN_UNUSED_RESULT ASIsCGSizeValidForSize(CGSize si return (ASPointsValidForSize(size.width) && ASPointsValidForSize(size.height)); } +#pragma mark - ASDimension + /** * A dimension relative to constraints to be provided in the future. * A ASDimension can be one of three types: @@ -58,42 +65,10 @@ typedef struct { } ASDimension; /** - * Expresses an inclusive range of sizes. Used to provide a simple constraint to layout. + * Represents auto as ASDimension */ -typedef struct { - CGSize min; - CGSize max; -} ASSizeRange; - -/** - * A struct specifying a ASLayoutElement's size. Example: - * - * ASLayoutElementSize size = (ASLayoutElementSize){ - * .width = ASDimensionMakeWithFraction(0.25), - * .maxWidth = ASDimensionMakeWithPoints(200), - * .minHeight = ASDimensionMakeWithFraction(0.50) - * }; - * - * Description: - * - */ -typedef struct { - ASDimension width; - ASDimension height; - ASDimension minWidth; - ASDimension maxWidth; - ASDimension minHeight; - ASDimension maxHeight; -} ASLayoutElementSize; - extern ASDimension const ASDimensionAuto; -ASDISPLAYNODE_EXTERN_C_BEGIN -NS_ASSUME_NONNULL_BEGIN - - -#pragma mark - ASDimension - /** * Returns a dimension with the specified type and value. */ @@ -173,15 +148,6 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT CGFloat ASDimensionResolve(ASDimensio } } - -#pragma mark - NSNumber+ASDimension - -@interface NSNumber (ASDimension) -@property (nonatomic, readonly) ASDimension as_pointDimension; -@property (nonatomic, readonly) ASDimension as_fractionDimension; -@end - - #pragma mark - ASLayoutSize /** @@ -205,6 +171,15 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutSize ASLayoutSizeMake(ASDimen return size; } +/** + * Resolve this relative size relative to a parent size. + */ +ASDISPLAYNODE_INLINE CGSize ASLayoutSizeResolveSize(ASLayoutSize layoutSize, CGSize parentSize, CGSize autoSize) +{ + return CGSizeMake(ASDimensionResolve(layoutSize.width, parentSize.width, autoSize.width), + ASDimensionResolve(layoutSize.height, parentSize.height, autoSize.height)); +} + /* * Returns a string representation of a relative size. */ @@ -217,6 +192,14 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutSize(AS #pragma mark - ASSizeRange +/** + * Expresses an inclusive range of sizes. Used to provide a simple constraint to layout. + */ +typedef struct { + CGSize min; + CGSize max; +} ASSizeRange; + /** * Creates an ASSizeRange with provided min and max size. */ @@ -272,149 +255,5 @@ ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASSizeRangeEqualToSizeRange(ASSi */ extern AS_WARN_UNUSED_RESULT NSString *NSStringFromASSizeRange(ASSizeRange sizeRange); - -#pragma mark - ASLayoutElementSize - -/** - * Returns an ASLayoutElementSize with default values. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMake() -{ - return (ASLayoutElementSize){ - .width = ASDimensionAuto, - .height = ASDimensionAuto, - .minWidth = ASDimensionAuto, - .maxWidth = ASDimensionAuto, - .minHeight = ASDimensionAuto, - .maxHeight = ASDimensionAuto - }; -} - -/** - * Returns an ASLayoutElementSize with the specified CGSize values as width and height. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMakeFromCGSize(CGSize size) -{ - ASLayoutElementSize s = ASLayoutElementSizeMake(); - s.width = ASDimensionMakeWithPoints(size.width); - s.height = ASDimensionMakeWithPoints(size.height); - return s; -} - -/** - * Returns whether two sizes are equal. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutElementSizeEqualToLayoutElementSize(ASLayoutElementSize lhs, ASLayoutElementSize rhs) -{ - return (ASDimensionEqualToDimension(lhs.width, rhs.width) - && ASDimensionEqualToDimension(lhs.height, rhs.height) - && ASDimensionEqualToDimension(lhs.minWidth, rhs.minWidth) - && ASDimensionEqualToDimension(lhs.maxWidth, rhs.maxWidth) - && ASDimensionEqualToDimension(lhs.minHeight, rhs.minHeight) - && ASDimensionEqualToDimension(lhs.maxHeight, rhs.maxHeight)); -} - -/** - * Returns a string formatted to contain the data from an ASLayoutElementSize. - */ -extern AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size); - -/** - * Resolve the given size relative to a parent size and an auto size. - * From the given size uses width, height to resolve the exact size constraint, uses the minHeight and minWidth to - * resolve the min size constraint and the maxHeight and maxWidth to resolve the max size constraint. For every - * dimension with unit ASDimensionUnitAuto the given autoASSizeRange value will be used. - * Based on the calculated exact, min and max size constraints the final size range will be calculated. - */ -extern AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange); - -/** - * Resolve the given size to a parent size. Uses internally ASLayoutElementSizeResolveAutoSize with {INFINITY, INFINITY} as - * as autoASSizeRange. For more information look at ASLayoutElementSizeResolveAutoSize. - */ -ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolve(ASLayoutElementSize size, const CGSize parentSize) -{ - return ASLayoutElementSizeResolveAutoSize(size, parentSize, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); -} - - -#pragma mark - Deprecated - -/** - * A dimension relative to constraints to be provided in the future. - * A ASDimension can be one of three types: - * - * "Auto" - This indicated "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. - * - * "Points" - Just a number. It will always resolve to exactly this amount. - * - * "Percent" - Multiplied to a provided parent amount to resolve a final amount. - */ -typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { - /** This indicates "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. */ - ASRelativeDimensionTypeAuto, - /** Just a number. It will always resolve to exactly this amount. This is the default type. */ - ASRelativeDimensionTypePoints, - /** Multiplied to a provided parent amount to resolve a final amount. */ - ASRelativeDimensionTypeFraction, -}; - -#define ASRelativeDimension ASDimension -#define ASRelativeSize ASLayoutSize -#define ASRelativeDimensionMakeWithPoints ASDimensionMakeWithPoints -#define ASRelativeDimensionMakeWithFraction ASDimensionMakeWithFraction - -/** - * Function is deprecated. Use ASSizeRangeMake instead. - */ -extern AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMakeExactSize(CGSize size) ASDISPLAYNODE_DEPRECATED_MSG("Use ASSizeRangeMake instead."); - -/** - Expresses an inclusive range of relative sizes. Used to provide additional constraint to layout. - Used by ASStaticLayoutSpec. - */ -typedef struct { - ASLayoutSize min; - ASLayoutSize max; -} ASRelativeSizeRange; - -extern ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained; - -#pragma mark - ASRelativeDimension - -extern ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) ASDISPLAYNODE_DEPRECATED; - -#pragma mark - ASRelativeSize - -extern ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) ASDISPLAYNODE_DEPRECATED; - -/** Convenience constructor to provide size in points. */ -extern ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) ASDISPLAYNODE_DEPRECATED; - -/** Convenience constructor to provide size as a fraction. */ -extern ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; - -extern BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) ASDISPLAYNODE_DEPRECATED; - -extern NSString *NSStringFromASRelativeSize(ASLayoutSize size) ASDISPLAYNODE_DEPRECATED; - -#pragma mark - ASRelativeSizeRange - -extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) ASDISPLAYNODE_DEPRECATED; - -#pragma mark Convenience constructors to provide an exact size (min == max). -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) ASDISPLAYNODE_DEPRECATED; - -extern BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) ASDISPLAYNODE_DEPRECATED; - -/** Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. */ -extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, CGSize parentSize) ASDISPLAYNODE_DEPRECATED; - NS_ASSUME_NONNULL_END ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm index cb8c358d9f..f78057e312 100644 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -52,91 +52,10 @@ NSString *NSStringFromASDimension(ASDimension dimension) } } - -#pragma mark - NSNumber+ASDimension - -@implementation NSNumber (ASDimension) - -- (ASDimension)as_pointDimension -{ - return ASDimensionMake(ASDimensionUnitPoints, ASCGFloatFromNumber(self)); -} - -- (ASDimension)as_fractionDimension -{ - return ASDimensionMake(ASDimensionUnitFraction, ASCGFloatFromNumber(self)); -} - -@end - - #pragma mark - ASLayoutSize ASLayoutSize const ASLayoutSizeAuto = {ASDimensionAuto, ASDimensionAuto}; -// ** Resolve this relative size relative to a parent size. */ -ASDISPLAYNODE_INLINE CGSize ASLayoutSizeResolveSize(ASLayoutSize layoutSize, CGSize parentSize, CGSize autoSize) -{ - return CGSizeMake(ASDimensionResolve(layoutSize.width, parentSize.width, autoSize.width), - ASDimensionResolve(layoutSize.height, parentSize.height, autoSize.height)); -} - - -#pragma mark - ASLayoutElementSize - -NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size) -{ - return [NSString stringWithFormat: - @"", - NSStringFromASLayoutSize(ASLayoutSizeMake(size.width, size.height)), - NSStringFromASLayoutSize(ASLayoutSizeMake(size.minWidth, size.minHeight)), - NSStringFromASLayoutSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight))]; -} - -ASDISPLAYNODE_INLINE void ASLayoutElementSizeConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax) -{ - NSCAssert(!isnan(minVal), @"minVal must not be NaN"); - NSCAssert(!isnan(maxVal), @"maxVal must not be NaN"); - // Avoid use of min/max primitives since they're harder to reason - // about in the presence of NaN (in exactVal) - // Follow CSS: min overrides max overrides exact. - - // Begin with the min/max range - *outMin = minVal; - *outMax = maxVal; - if (maxVal <= minVal) { - // min overrides max and exactVal is irrelevant - *outMax = minVal; - return; - } - if (isnan(exactVal)) { - // no exact value, so leave as a min/max range - return; - } - if (exactVal > maxVal) { - // clip to max value - *outMin = maxVal; - } else if (exactVal < minVal) { - // clip to min value - *outMax = minVal; - } else { - // use exact value - *outMin = *outMax = exactVal; - } -} - -ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange) -{ - CGSize resolvedExact = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.width, size.height), parentSize, {NAN, NAN}); - CGSize resolvedMin = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.minWidth, size.minHeight), parentSize, autoASSizeRange.min); - CGSize resolvedMax = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight), parentSize, autoASSizeRange.max); - - CGSize rangeMin, rangeMax; - ASLayoutElementSizeConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width); - ASLayoutElementSizeConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height); - return {rangeMin, rangeMax}; -} - #pragma mark - ASSizeRange @@ -178,90 +97,3 @@ NSString *NSStringFromASSizeRange(ASSizeRange sizeRange) NSStringFromCGSize(sizeRange.min), NSStringFromCGSize(sizeRange.max)]; } - - -#pragma mark - Deprecated - -ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) -{ - if (type == ASRelativeDimensionTypePoints) { - return ASDimensionMakeWithPoints(value); - } else if (type == ASRelativeDimensionTypeFraction) { - return ASDimensionMakeWithFraction(value); - } - - ASDisplayNodeCAssert(NO, @"ASRelativeDimensionMake does not support the given ASRelativeDimensionType"); - return ASDimensionMakeWithPoints(0); -} - -ASSizeRange ASSizeRangeMakeExactSize(CGSize size) -{ - return ASSizeRangeMake(size); -} - -ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained = {}; - -#pragma mark - ASRelativeSize - -ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) -{ - return ASLayoutSizeMake(width, height); -} - -ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) -{ - return ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(size.width), - ASRelativeDimensionMakeWithPoints(size.height)); -} - -ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) -{ - return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction), - ASRelativeDimensionMakeWithFraction(fraction)); -} - -BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) -{ - return ASDimensionEqualToDimension(lhs.width, rhs.width) - && ASDimensionEqualToDimension(lhs.height, rhs.height); -} - - -#pragma mark - ASRelativeSizeRange - -ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) -{ - ASRelativeSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) -{ - return ASRelativeSizeRangeMake(exact, exact); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithFraction(fraction)); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMake(exactWidth, exactHeight)); -} - -BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) -{ - return ASRelativeSizeEqualToRelativeSize(lhs.min, rhs.min) && ASRelativeSizeEqualToRelativeSize(lhs.max, rhs.max); -} - -ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, - CGSize parentSize) -{ - return ASSizeRangeMake(ASLayoutSizeResolveSize(relativeSizeRange.min, parentSize, parentSize), - ASLayoutSizeResolveSize(relativeSizeRange.max, parentSize, parentSize)); -} diff --git a/AsyncDisplayKit/Layout/ASDimensionDeprecated.h b/AsyncDisplayKit/Layout/ASDimensionDeprecated.h new file mode 100644 index 0000000000..7ddd438dbd --- /dev/null +++ b/AsyncDisplayKit/Layout/ASDimensionDeprecated.h @@ -0,0 +1,95 @@ +// +// ASDimensionDeprecated.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#pragma once +#import +#import + +ASDISPLAYNODE_EXTERN_C_BEGIN +NS_ASSUME_NONNULL_BEGIN + +/** + * A dimension relative to constraints to be provided in the future. + * A ASDimension can be one of three types: + * + * "Auto" - This indicated "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. + * + * "Points" - Just a number. It will always resolve to exactly this amount. + * + * "Percent" - Multiplied to a provided parent amount to resolve a final amount. + */ +typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { + /** This indicates "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. */ + ASRelativeDimensionTypeAuto, + /** Just a number. It will always resolve to exactly this amount. This is the default type. */ + ASRelativeDimensionTypePoints, + /** Multiplied to a provided parent amount to resolve a final amount. */ + ASRelativeDimensionTypeFraction, +}; + +#define ASRelativeDimension ASDimension +#define ASRelativeSize ASLayoutSize +#define ASRelativeDimensionMakeWithPoints ASDimensionMakeWithPoints +#define ASRelativeDimensionMakeWithFraction ASDimensionMakeWithFraction + +/** + * Function is deprecated. Use ASSizeRangeMake instead. + */ +extern AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMakeExactSize(CGSize size) ASDISPLAYNODE_DEPRECATED_MSG("Use ASSizeRangeMake instead."); + +/** + Expresses an inclusive range of relative sizes. Used to provide additional constraint to layout. + Used by ASStaticLayoutSpec. + */ +typedef struct { + ASLayoutSize min; + ASLayoutSize max; +} ASRelativeSizeRange; + +extern ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained; + +#pragma mark - ASRelativeDimension + +extern ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) ASDISPLAYNODE_DEPRECATED; + +#pragma mark - ASRelativeSize + +extern ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) ASDISPLAYNODE_DEPRECATED; + +/** Convenience constructor to provide size in points. */ +extern ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) ASDISPLAYNODE_DEPRECATED; + +/** Convenience constructor to provide size as a fraction. */ +extern ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; + +extern BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) ASDISPLAYNODE_DEPRECATED; + +extern NSString *NSStringFromASRelativeSize(ASLayoutSize size) ASDISPLAYNODE_DEPRECATED; + +#pragma mark - ASRelativeSizeRange + +extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) ASDISPLAYNODE_DEPRECATED; + +#pragma mark Convenience constructors to provide an exact size (min == max). +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) ASDISPLAYNODE_DEPRECATED; + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) ASDISPLAYNODE_DEPRECATED; + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; + +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) ASDISPLAYNODE_DEPRECATED; + +extern BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) ASDISPLAYNODE_DEPRECATED; + +/** Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. */ +extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, CGSize parentSize) ASDISPLAYNODE_DEPRECATED; + +NS_ASSUME_NONNULL_END +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Layout/ASDimensionDeprecated.mm b/AsyncDisplayKit/Layout/ASDimensionDeprecated.mm new file mode 100644 index 0000000000..b08c9d03e3 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASDimensionDeprecated.mm @@ -0,0 +1,95 @@ +// +// ASDimensionDeprecated.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "ASDimensionDeprecated.h" + +ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) +{ + if (type == ASRelativeDimensionTypePoints) { + return ASDimensionMakeWithPoints(value); + } else if (type == ASRelativeDimensionTypeFraction) { + return ASDimensionMakeWithFraction(value); + } + + ASDisplayNodeCAssert(NO, @"ASRelativeDimensionMake does not support the given ASRelativeDimensionType"); + return ASDimensionMakeWithPoints(0); +} + +ASSizeRange ASSizeRangeMakeExactSize(CGSize size) +{ + return ASSizeRangeMake(size); +} + +ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained = {}; + +#pragma mark - ASRelativeSize + +ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) +{ + return ASLayoutSizeMake(width, height); +} + +ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) +{ + return ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(size.width), + ASRelativeDimensionMakeWithPoints(size.height)); +} + +ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) +{ + return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction), + ASRelativeDimensionMakeWithFraction(fraction)); +} + +BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) +{ + return ASDimensionEqualToDimension(lhs.width, rhs.width) + && ASDimensionEqualToDimension(lhs.height, rhs.height); +} + + +#pragma mark - ASRelativeSizeRange + +ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) +{ + ASRelativeSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) +{ + return ASRelativeSizeRangeMake(exact, exact); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithFraction(fraction)); +} + +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) +{ + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMake(exactWidth, exactHeight)); +} + +BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) +{ + return ASRelativeSizeEqualToRelativeSize(lhs.min, rhs.min) && ASRelativeSizeEqualToRelativeSize(lhs.max, rhs.max); +} + +ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, + CGSize parentSize) +{ + return ASSizeRangeMake(ASLayoutSizeResolveSize(relativeSizeRange.min, parentSize, parentSize), + ASLayoutSizeResolveSize(relativeSizeRange.max, parentSize, parentSize)); +} diff --git a/AsyncDisplayKit/Layout/ASDimensionInternal.h b/AsyncDisplayKit/Layout/ASDimensionInternal.h new file mode 100644 index 0000000000..e4ef18ec5d --- /dev/null +++ b/AsyncDisplayKit/Layout/ASDimensionInternal.h @@ -0,0 +1,105 @@ +// +// ASDimensionInternal.h +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#pragma once +#import +#import + +ASDISPLAYNODE_EXTERN_C_BEGIN +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - ASLayoutElementSize + +/** + * A struct specifying a ASLayoutElement's size. Example: + * + * ASLayoutElementSize size = (ASLayoutElementSize){ + * .width = ASDimensionMakeWithFraction(0.25), + * .maxWidth = ASDimensionMakeWithPoints(200), + * .minHeight = ASDimensionMakeWithFraction(0.50) + * }; + * + * Description: + * + */ +typedef struct { + ASDimension width; + ASDimension height; + ASDimension minWidth; + ASDimension maxWidth; + ASDimension minHeight; + ASDimension maxHeight; +} ASLayoutElementSize; + +/** + * Returns an ASLayoutElementSize with default values. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMake() +{ + return (ASLayoutElementSize){ + .width = ASDimensionAuto, + .height = ASDimensionAuto, + .minWidth = ASDimensionAuto, + .maxWidth = ASDimensionAuto, + .minHeight = ASDimensionAuto, + .maxHeight = ASDimensionAuto + }; +} + +/** + * Returns an ASLayoutElementSize with the specified CGSize values as width and height. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASLayoutElementSize ASLayoutElementSizeMakeFromCGSize(CGSize size) +{ + ASLayoutElementSize s = ASLayoutElementSizeMake(); + s.width = ASDimensionMakeWithPoints(size.width); + s.height = ASDimensionMakeWithPoints(size.height); + return s; +} + +/** + * Returns whether two sizes are equal. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT BOOL ASLayoutElementSizeEqualToLayoutElementSize(ASLayoutElementSize lhs, ASLayoutElementSize rhs) +{ + return (ASDimensionEqualToDimension(lhs.width, rhs.width) + && ASDimensionEqualToDimension(lhs.height, rhs.height) + && ASDimensionEqualToDimension(lhs.minWidth, rhs.minWidth) + && ASDimensionEqualToDimension(lhs.maxWidth, rhs.maxWidth) + && ASDimensionEqualToDimension(lhs.minHeight, rhs.minHeight) + && ASDimensionEqualToDimension(lhs.maxHeight, rhs.maxHeight)); +} + +/** + * Returns a string formatted to contain the data from an ASLayoutElementSize. + */ +extern AS_WARN_UNUSED_RESULT NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size); + +/** + * Resolve the given size relative to a parent size and an auto size. + * From the given size uses width, height to resolve the exact size constraint, uses the minHeight and minWidth to + * resolve the min size constraint and the maxHeight and maxWidth to resolve the max size constraint. For every + * dimension with unit ASDimensionUnitAuto the given autoASSizeRange value will be used. + * Based on the calculated exact, min and max size constraints the final size range will be calculated. + */ +extern AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange); + +/** + * Resolve the given size to a parent size. Uses internally ASLayoutElementSizeResolveAutoSize with {INFINITY, INFINITY} as + * as autoASSizeRange. For more information look at ASLayoutElementSizeResolveAutoSize. + */ +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASSizeRange ASLayoutElementSizeResolve(ASLayoutElementSize size, const CGSize parentSize) +{ + return ASLayoutElementSizeResolveAutoSize(size, parentSize, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); +} + + +NS_ASSUME_NONNULL_END +ASDISPLAYNODE_EXTERN_C_END diff --git a/AsyncDisplayKit/Layout/ASDimensionInternal.mm b/AsyncDisplayKit/Layout/ASDimensionInternal.mm new file mode 100644 index 0000000000..a6435ca558 --- /dev/null +++ b/AsyncDisplayKit/Layout/ASDimensionInternal.mm @@ -0,0 +1,66 @@ +// +// ASDimensionInternal.mm +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "ASDimensionInternal.h" + +#pragma mark - ASLayoutElementSize + +NSString *NSStringFromASLayoutElementSize(ASLayoutElementSize size) +{ + return [NSString stringWithFormat: + @"", + NSStringFromASLayoutSize(ASLayoutSizeMake(size.width, size.height)), + NSStringFromASLayoutSize(ASLayoutSizeMake(size.minWidth, size.minHeight)), + NSStringFromASLayoutSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight))]; +} + +ASDISPLAYNODE_INLINE void ASLayoutElementSizeConstrain(CGFloat minVal, CGFloat exactVal, CGFloat maxVal, CGFloat *outMin, CGFloat *outMax) +{ + NSCAssert(!isnan(minVal), @"minVal must not be NaN"); + NSCAssert(!isnan(maxVal), @"maxVal must not be NaN"); + // Avoid use of min/max primitives since they're harder to reason + // about in the presence of NaN (in exactVal) + // Follow CSS: min overrides max overrides exact. + + // Begin with the min/max range + *outMin = minVal; + *outMax = maxVal; + if (maxVal <= minVal) { + // min overrides max and exactVal is irrelevant + *outMax = minVal; + return; + } + if (isnan(exactVal)) { + // no exact value, so leave as a min/max range + return; + } + if (exactVal > maxVal) { + // clip to max value + *outMin = maxVal; + } else if (exactVal < minVal) { + // clip to min value + *outMax = minVal; + } else { + // use exact value + *outMin = *outMax = exactVal; + } +} + +ASSizeRange ASLayoutElementSizeResolveAutoSize(ASLayoutElementSize size, const CGSize parentSize, ASSizeRange autoASSizeRange) +{ + CGSize resolvedExact = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.width, size.height), parentSize, {NAN, NAN}); + CGSize resolvedMin = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.minWidth, size.minHeight), parentSize, autoASSizeRange.min); + CGSize resolvedMax = ASLayoutSizeResolveSize(ASLayoutSizeMake(size.maxWidth, size.maxHeight), parentSize, autoASSizeRange.max); + + CGSize rangeMin, rangeMax; + ASLayoutElementSizeConstrain(resolvedMin.width, resolvedExact.width, resolvedMax.width, &rangeMin.width, &rangeMax.width); + ASLayoutElementSizeConstrain(resolvedMin.height, resolvedExact.height, resolvedMax.height, &rangeMin.height, &rangeMax.height); + return {rangeMin, rangeMax}; +} diff --git a/AsyncDisplayKit/Layout/ASLayoutElement.h b/AsyncDisplayKit/Layout/ASLayoutElement.h index cb21447507..cd974ea425 100644 --- a/AsyncDisplayKit/Layout/ASLayoutElement.h +++ b/AsyncDisplayKit/Layout/ASLayoutElement.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import #import #import #import @@ -315,7 +315,7 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; @protocol ASLayoutElementStylability -- (instancetype)styledWithBlock:(__attribute__((noescape)) void (^)(__kindof ASLayoutElementStyle *style))styleBlock; +- (instancetype)styledWithBlock:(AS_NOESCAPE void (^)(__kindof ASLayoutElementStyle *style))styleBlock; @end diff --git a/AsyncDisplayKit/Private/ASCollectionView+Undeprecated.h b/AsyncDisplayKit/Private/ASCollectionView+Undeprecated.h index d8d45bd11c..e32f0c6262 100644 --- a/AsyncDisplayKit/Private/ASCollectionView+Undeprecated.h +++ b/AsyncDisplayKit/Private/ASCollectionView+Undeprecated.h @@ -133,7 +133,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously. This method must be called from the main thread. @@ -144,7 +144,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable __attribute((noescape)) void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** * Reload everything from scratch, destroying the working range and all cached nodes. diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 1fdef8eb0b..a7cce88570 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -39,14 +39,12 @@ static CGFloat crossOffset(const ASStackLayoutSpecStyle &style, * @param style The layout style of the overall stack layout * @param firstChildOffset Offset of the first child * @param extraSpacing Spacing between children, in addition to spacing set to the stack's layout style - * @param lastChildOffset Offset of the last child * @param unpositionedLayout Unpositioned children of the stack * @param constrainedSize Constrained size of the stack */ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style, const CGFloat firstChildOffset, const CGFloat extraSpacing, - const CGFloat lastChildOffset, const ASStackUnpositionedLayout &unpositionedLayout, const ASSizeRange &constrainedSize) { @@ -63,14 +61,11 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style CGPoint p = directionPoint(style.direction, firstChildOffset, 0); BOOL first = YES; - const auto lastChild = unpositionedLayout.items.back().child; - CGFloat offset = 0; - + // Adjust the position of the unpositioned layouts to be positioned const auto stackedChildren = unpositionedLayout.items; for (const auto &l : stackedChildren) { - offset = (l.child.element == lastChild.element) ? lastChildOffset : 0; - p = p + directionPoint(style.direction, l.child.style.spacingBefore + offset, 0); + p = p + directionPoint(style.direction, l.child.style.spacingBefore, 0); if (!first) { p = p + directionPoint(style.direction, style.spacing + extraSpacing, 0); } @@ -83,14 +78,6 @@ static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style return {std::move(stackedChildren), crossSize}; } -static ASStackPositionedLayout stackedLayout(const ASStackLayoutSpecStyle &style, - const CGFloat firstChildOffset, - const ASStackUnpositionedLayout &unpositionedLayout, - const ASSizeRange &constrainedSize) -{ - return stackedLayout(style, firstChildOffset, 0, 0, unpositionedLayout, constrainedSize); -} - ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnpositionedLayout &unpositionedLayout, const ASStackLayoutSpecStyle &style, const ASSizeRange &constrainedSize) @@ -109,23 +96,23 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition switch (justifyContent) { case ASStackLayoutJustifyContentStart: { - return stackedLayout(style, 0, unpositionedLayout, constrainedSize); + return stackedLayout(style, 0, 0, unpositionedLayout, constrainedSize); } case ASStackLayoutJustifyContentCenter: { - return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize); + return stackedLayout(style, std::floor(violation / 2), 0, unpositionedLayout, constrainedSize); } case ASStackLayoutJustifyContentEnd: { - return stackedLayout(style, violation, unpositionedLayout, constrainedSize); + return stackedLayout(style, violation, 0, unpositionedLayout, constrainedSize); } case ASStackLayoutJustifyContentSpaceBetween: { // Spacing between the items, no spaces at the edges, evenly distributed const auto numOfSpacings = numOfItems - 1; - return stackedLayout(style, 0, violation / numOfSpacings, 0, unpositionedLayout, constrainedSize); + return stackedLayout(style, 0, violation / numOfSpacings, unpositionedLayout, constrainedSize); } case ASStackLayoutJustifyContentSpaceAround: { // Spacing between items are twice the spacing on the edges CGFloat spacingUnit = violation / (numOfItems * 2); - return stackedLayout(style, spacingUnit, spacingUnit * 2, 0, unpositionedLayout, constrainedSize); + return stackedLayout(style, spacingUnit, spacingUnit * 2, unpositionedLayout, constrainedSize); } } } diff --git a/AsyncDisplayKit/TextKit/ASTextKitComponents.h b/AsyncDisplayKit/TextKit/ASTextKitComponents.h index 9da19d7061..5cdad469f3 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitComponents.h +++ b/AsyncDisplayKit/TextKit/ASTextKitComponents.h @@ -44,6 +44,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth; + +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth + forMaxNumberOfLines:(NSInteger)numberOfLines; + @property (nonatomic, strong, readonly) NSTextStorage *textStorage; @property (nonatomic, strong, readonly) NSTextContainer *textContainer; @property (nonatomic, strong, readonly) NSLayoutManager *layoutManager; diff --git a/AsyncDisplayKit/TextKit/ASTextKitComponents.m b/AsyncDisplayKit/TextKit/ASTextKitComponents.mm similarity index 59% rename from AsyncDisplayKit/TextKit/ASTextKitComponents.m rename to AsyncDisplayKit/TextKit/ASTextKitComponents.mm index 93eb429ba4..e78b67823a 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitComponents.m +++ b/AsyncDisplayKit/TextKit/ASTextKitComponents.mm @@ -10,6 +10,8 @@ #import "ASTextKitComponents.h" +#import + @interface ASTextKitComponents () // read-write redeclarations @@ -66,4 +68,55 @@ return textSize; } +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth + forMaxNumberOfLines:(NSInteger)maxNumberOfLines +{ + if (maxNumberOfLines == 0) { + return [self sizeForConstrainedWidth:constrainedWidth]; + } + + ASTextKitComponents *components = self; + + // Always use temporary stack in case of threading issues + components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)]; + + // Force glyph generation and layout, which may not have happened yet (and isn't triggered by - usedRectForTextContainer:). + [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; + + CGFloat width = [components.layoutManager usedRectForTextContainer:components.textContainer].size.width; + + // Calculate height based on line fragments + // Based on calculating number of lines from: http://asciiwwdc.com/2013/sessions/220 + NSRange glyphRange, lineRange = NSMakeRange(0, 0); + CGRect rect = CGRectZero; + CGFloat height = 0; + CGFloat lastOriginY = -1.0; + NSUInteger numberOfLines = 0; + + glyphRange = [components.layoutManager glyphRangeForTextContainer:components.textContainer]; + + while (lineRange.location < NSMaxRange(glyphRange)) { + rect = [components.layoutManager lineFragmentRectForGlyphAtIndex:lineRange.location + effectiveRange:&lineRange]; + + if (CGRectGetMinY(rect) > lastOriginY) { + ++numberOfLines; + if (numberOfLines == maxNumberOfLines) { + height = rect.origin.y + rect.size.height; + break; + } + } + + lastOriginY = CGRectGetMinY(rect); + lineRange.location = NSMaxRange(lineRange); + } + + CGFloat fragmentHeight = rect.origin.y + rect.size.height; + CGFloat finalHeight = std::ceil(std::fmax(height, fragmentHeight)); + + CGSize size = CGSizeMake(width, finalHeight); + + return size; +} + @end diff --git a/AsyncDisplayKit/UIImage+ASConvenience.h b/AsyncDisplayKit/UIImage+ASConvenience.h index 0dc0668115..4edb6c726a 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.h +++ b/AsyncDisplayKit/UIImage+ASConvenience.h @@ -70,6 +70,27 @@ NS_ASSUME_NONNULL_BEGIN roundedCorners:(UIRectCorner)roundedCorners scale:(CGFloat)scale AS_WARN_UNUSED_RESULT; +/** + * A version of imageNamed that caches results because loading an image is expensive. + * Calling with the same name value will usually return the same object. A UIImage, + * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. + * + * @param imageName The name of the image to load + * @return The loaded image or nil + */ ++ (UIImage *)as_imageNamed:(NSString *)imageName; + +/** + * A version of imageNamed that caches results because loading an image is expensive. + * Calling with the same name value will usually return the same object. A UIImage, + * after creation, is immutable and thread-safe so it's fine to share these objects across multiple threads. + * + * @param imageName The name of the image to load + * @param traitCollection The traits associated with the intended environment for the image. + * @return The loaded image or nil + */ ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/UIImage+ASConvenience.m b/AsyncDisplayKit/UIImage+ASConvenience.m index b6ae5adba1..3af1435acb 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.m +++ b/AsyncDisplayKit/UIImage+ASConvenience.m @@ -122,4 +122,47 @@ return result; } +#pragma mark - as_imageNamed + +UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) +{ + static NSCache *imageCache = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Because NSCache responds to memory warnings, we do not need an explicit limit. + // all of these objects contain compressed image data and are relatively small + // compared to the backing stores of text and image views. + imageCache = [[NSCache alloc] init]; + }); + + UIImage *image = nil; + if ([imageName length] > 0) { + NSString *imageKey = imageName; + if (traitCollection) { + char imageKeyBuffer[256]; + snprintf(imageKeyBuffer, sizeof(imageKeyBuffer), "%s|%ld|%ld", imageName.UTF8String, (long)traitCollection.horizontalSizeClass, (long)traitCollection.verticalSizeClass); + imageKey = [NSString stringWithUTF8String:imageKeyBuffer]; + } + + image = [imageCache objectForKey:imageKey]; + if (!image) { + image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:traitCollection]; + if (image) { + [imageCache setObject:image forKey:imageKey]; + } + } + } + return image; +} + ++ (UIImage *)as_imageNamed:(NSString *)imageName +{ + return cachedImageNamed(imageName, nil); +} + ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection +{ + return cachedImageNamed(imageName, traitCollection); +} + @end diff --git a/AsyncDisplayKitTests/ASCollectionViewTests.mm b/AsyncDisplayKitTests/ASCollectionViewTests.mm index 12f4a89fa2..60ae5961d1 100644 --- a/AsyncDisplayKitTests/ASCollectionViewTests.mm +++ b/AsyncDisplayKitTests/ASCollectionViewTests.mm @@ -94,16 +94,6 @@ }; } -- (void)collectionView:(ASCollectionView *)collectionView willDisplayNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertNotNil(node.layoutAttributes, @"Expected layout attributes for node in %@ to be non-nil.", NSStringFromSelector(_cmd)); -} - -- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNode:(ASCellNode *)node forItemAtIndexPath:(NSIndexPath *)indexPath -{ - ASDisplayNodeAssertNotNil(node.layoutAttributes, @"Expected layout attributes for node in %@ to be non-nil.", NSStringFromSelector(_cmd)); -} - - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return _itemCounts.size(); } @@ -890,7 +880,6 @@ ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; window.rootViewController = testController; - // Start with 1 item so that our content does not fill bounds. testController.asyncDelegate->_itemCounts = {}; [window makeKeyAndVisible]; [window layoutIfNeeded]; @@ -1002,4 +991,65 @@ XCTAssertLessThanOrEqual(contentHeight, requiredContentHeight + 2 * itemHeight, @"Loaded too much content."); } +- (void)testInitialRangeBounds +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + ASCollectionNode *cn = testController.collectionNode; + [cn setTuningParameters:{ .leadingBufferScreenfuls = 2, .trailingBufferScreenfuls = 0 } forRangeMode:ASLayoutRangeModeMinimum rangeType:ASLayoutRangeTypePreload]; + window.rootViewController = testController; + + [window makeKeyAndVisible]; + [window layoutIfNeeded]; + + [cn waitUntilAllUpdatesAreCommitted]; + + CGRect preloadBounds = ({ + CGRect r = CGRectNull; + for (NSInteger s = 0; s < cn.numberOfSections; s++) { + NSInteger c = [cn numberOfItemsInSection:s]; + for (NSInteger i = 0; i < c; i++) { + NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; + ASCellNode *node = [cn nodeForItemAtIndexPath:ip]; + if (node.inPreloadState) { + CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame; + r = CGRectUnion(r, frame); + } + } + } + r; + }); + CGFloat expectedHeight = cn.bounds.size.height * 3; + XCTAssertEqualWithAccuracy(CGRectGetHeight(preloadBounds), expectedHeight, 100); + XCTAssertEqual([[cn valueForKeyPath:@"rangeController.currentRangeMode"] integerValue], ASLayoutRangeModeMinimum, @"Expected range mode to be minimum before scrolling begins."); +} + +/** + * This tests an issue where, since subnode insertions aren't applied until the UIKit layout pass, + * which we trigger during the display phase, subnodes like network image nodes are not preloading + * until this layout pass happens which is too late. + */ +- (void)DISABLED_testThatAutomaticallyManagedSubnodesGetPreloadCallBeforeDisplay +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASCollectionViewTestController *testController = [[ASCollectionViewTestController alloc] initWithNibName:nil bundle:nil]; + window.rootViewController = testController; + ASCollectionNode *cn = testController.collectionNode; + + __block NSInteger itemCount = 100; + testController.asyncDelegate->_itemCounts = {itemCount}; + [window makeKeyAndVisible]; + [window layoutIfNeeded]; + + [cn waitUntilAllUpdatesAreCommitted]; + for (NSInteger i = 0; i < itemCount; i++) { + ASTextCellNodeWithSetSelectedCounter *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; + XCTAssert(node.automaticallyManagesSubnodes, @"Expected test cell node to use automatic subnode management. Can modify the test with a different class if needed."); + ASDisplayNode *subnode = node.textNode; + XCTAssertEqualObjects(NSStringFromASInterfaceState(subnode.interfaceState), NSStringFromASInterfaceState(node.interfaceState), @"Subtree interface state should match cell node interface state for ASM nodes."); + XCTAssert(node.inDisplayState || !node.nodeLoaded, @"Only nodes in the display range should be loaded."); + } + +} + @end diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index 504d6e76a0..c62a82758e 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -2283,4 +2283,25 @@ static bool stringContainsPointer(NSString *description, id p) { XCTAssertLessThan(underlayIndex, overlayIndex); } +- (void)testThatConvertPointGoesToWindowWhenPassedNil +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.frame = CGRectMake(10, 10, 10, 10); + [window addSubnode:node]; + CGPoint expectedOrigin = CGPointMake(10, 10); + ASXCTAssertEqualPoints([node convertPoint:node.bounds.origin toNode:nil], expectedOrigin); +} + +- (void)testThatConvertPointGoesToWindowWhenPassedNil_layerBacked +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 20, 20)]; + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.layerBacked = YES; + node.frame = CGRectMake(10, 10, 10, 10); + [window addSubnode:node]; + CGPoint expectedOrigin = CGPointMake(10, 10); + ASXCTAssertEqualPoints([node convertPoint:node.bounds.origin toNode:nil], expectedOrigin); +} + @end diff --git a/AsyncDisplayKitTests/ASLayoutElementStyleTests.m b/AsyncDisplayKitTests/ASLayoutElementStyleTests.m index 5a7e7cbe8b..bbcf1fe78b 100644 --- a/AsyncDisplayKitTests/ASLayoutElementStyleTests.m +++ b/AsyncDisplayKitTests/ASLayoutElementStyleTests.m @@ -8,6 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#import #import "ASXCTExtensions.h" #import "ASLayoutElement.h" diff --git a/AsyncDisplayKitTests/ASPagerNodeTests.m b/AsyncDisplayKitTests/ASPagerNodeTests.m index ad4a83ec6c..9117cf372e 100644 --- a/AsyncDisplayKitTests/ASPagerNodeTests.m +++ b/AsyncDisplayKitTests/ASPagerNodeTests.m @@ -93,7 +93,8 @@ return testController; } -- (void)testThatRootPagerNodeDoesGetTheRightInsetWhilePoppingBack +// Disabled due to flakiness https://github.com/facebook/AsyncDisplayKit/issues/2818 +- (void)DISABLED_testThatRootPagerNodeDoesGetTheRightInsetWhilePoppingBack { UICollectionViewCell *cell = nil; diff --git a/AsyncDisplayKitTests/ASPerformanceTestContext.h b/AsyncDisplayKitTests/ASPerformanceTestContext.h index 2fe0523afa..3d80eb7d74 100644 --- a/AsyncDisplayKitTests/ASPerformanceTestContext.h +++ b/AsyncDisplayKitTests/ASPerformanceTestContext.h @@ -8,6 +8,7 @@ #import #import +#import #define ASXCTAssertRelativePerformanceInRange(test, caseName, min, max) \ _XCTPrimitiveAssertLessThanOrEqual(self, test.results[caseName].relativePerformance, @#caseName, max, @#max);\ @@ -32,7 +33,7 @@ typedef void (^ASTestPerformanceCaseBlock)(NSUInteger i, dispatch_block_t startM /** * The first case you add here will be considered the reference case. */ -- (void)addCaseWithName:(NSString *)caseName block:(__attribute((noescape)) ASTestPerformanceCaseBlock)block; +- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block; @property (nonatomic, copy, readonly) NSDictionary *results; diff --git a/AsyncDisplayKitTests/ASPerformanceTestContext.m b/AsyncDisplayKitTests/ASPerformanceTestContext.m index 628999922d..575ac20ac9 100644 --- a/AsyncDisplayKitTests/ASPerformanceTestContext.m +++ b/AsyncDisplayKitTests/ASPerformanceTestContext.m @@ -74,7 +74,7 @@ return YES; } -- (void)addCaseWithName:(NSString *)caseName block:(__attribute((noescape)) ASTestPerformanceCaseBlock)block +- (void)addCaseWithName:(NSString *)caseName block:(AS_NOESCAPE ASTestPerformanceCaseBlock)block { ASDisplayNodeAssert(_results[caseName] == nil, @"Already have a case named %@", caseName); ASPerformanceTestResult *result = [[ASPerformanceTestResult alloc] init]; @@ -91,7 +91,7 @@ } /// Returns total work time -- (CFTimeInterval)_testPerformanceForCaseWithBlock:(__attribute((noescape)) ASTestPerformanceCaseBlock)block +- (CFTimeInterval)_testPerformanceForCaseWithBlock:(AS_NOESCAPE ASTestPerformanceCaseBlock)block { __block CFTimeInterval time = 0; for (NSInteger i = 0; i < _iterationCount; i++) { diff --git a/AsyncDisplayKitTests/ASTableViewTests.mm b/AsyncDisplayKitTests/ASTableViewTests.mm index 6f62bd1a24..99df044364 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.mm +++ b/AsyncDisplayKitTests/ASTableViewTests.mm @@ -19,6 +19,7 @@ #import "ASTableView+Undeprecated.h" #import #import "ASXCTExtensions.h" +#import "ASInternalHelpers.h" #define NumberOfSections 10 #define NumberOfReloadIterations 50 @@ -234,7 +235,7 @@ - (void)testConstrainedSizeForRowAtIndexPath { // Initial width of the table view is non-zero and all nodes are measured with this size. - // Any subsequence size change must trigger a relayout. + // Any subsequent size change must trigger a relayout. // Width and height are swapped so that a later size change will simulate a rotation ASTestTableView *tableView = [[ASTestTableView alloc] __initWithFrame:CGRectMake(0, 0, 100, 400) style:UITableViewStylePlain]; @@ -249,12 +250,13 @@ [tableView setNeedsLayout]; [tableView layoutIfNeeded]; + CGFloat separatorHeight = 1.0 / ASScreenScale(); for (int section = 0; section < NumberOfSections; section++) { for (int row = 0; row < [tableView numberOfRowsInSection:section]; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; CGRect rect = [tableView rectForRowAtIndexPath:indexPath]; XCTAssertEqual(rect.size.width, 100); // specified width should be ignored for table - XCTAssertEqual(rect.size.height, 42); + XCTAssertEqual(rect.size.height, 42 + separatorHeight); } } } @@ -775,7 +777,7 @@ [window layoutIfNeeded]; [node waitUntilAllUpdatesAreCommitted]; XCTAssertEqual(node.view.numberOfSections, NumberOfSections); - ASXCTAssertEqualRects(CGRectMake(0, 32, 375, 43.5), [node rectForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"This text requires very specific geometry. The rect for the first row should match up."); + ASXCTAssertEqualRects(CGRectMake(0, 32, 375, 44), [node rectForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"This text requires very specific geometry. The rect for the first row should match up."); __unused XCTestExpectation *e = [self expectationWithDescription:@"Did a bunch of rounds of updates."]; NSInteger totalCount = 20; diff --git a/AsyncDisplayKitTests/ASTableViewThrashTests.m b/AsyncDisplayKitTests/ASTableViewThrashTests.m index 07f23f5f8e..eea003b7b0 100644 --- a/AsyncDisplayKitTests/ASTableViewThrashTests.m +++ b/AsyncDisplayKitTests/ASTableViewThrashTests.m @@ -6,7 +6,7 @@ // Copyright © 2016 Facebook. All rights reserved. // -@import XCTest; +#import #import #import "ASTableViewInternal.h" #import "ASTableView+Undeprecated.h" diff --git a/AsyncDisplayKitTests/ASVideoNodeTests.m b/AsyncDisplayKitTests/ASVideoNodeTests.m index 3c835c5e2f..c8944bd5ef 100644 --- a/AsyncDisplayKitTests/ASVideoNodeTests.m +++ b/AsyncDisplayKitTests/ASVideoNodeTests.m @@ -25,12 +25,6 @@ } @end -@interface ASNetworkImageNode () { - @public __weak id _delegate; -} -@end - - @interface ASVideoNode () { ASDisplayNode *_playerNode; AVPlayer *_player; @@ -419,11 +413,9 @@ XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASVideoNodeDelegate)]); XCTAssertTrue([_videoNode.delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); XCTAssertTrue([((ASNetworkImageNode*)_videoNode).delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); - XCTAssertTrue([((ASNetworkImageNode*)_videoNode)->_delegate conformsToProtocol:@protocol(ASNetworkImageNodeDelegate)]); XCTAssertEqual(_videoNode.delegate, self); XCTAssertEqual(((ASNetworkImageNode*)_videoNode).delegate, self); - XCTAssertEqual(((ASNetworkImageNode*)_videoNode)->_delegate, self); } @end diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png index d5c6cdfcb3..5e2062b8e7 100644 Binary files a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png and b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetIsIncludedWithSmallerConstrainedSize@2x.png differ diff --git a/BUCK b/BUCK new file mode 100755 index 0000000000..dc31a9beaf --- /dev/null +++ b/BUCK @@ -0,0 +1,204 @@ +##################################### +# Defines +##################################### +COMMON_PREPROCESSOR_FLAGS = [ + '-fobjc-arc', + '-DDEBUG=1', +] + +COMMON_LANG_PREPROCESSOR_FLAGS = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=c++11', '-stdlib=libc++'], + 'OBJCXX': ['-std=c++11', '-stdlib=libc++'], +} + +COMMON_LINKER_FLAGS = ['-ObjC++'] + +ASYNCDISPLAYKIT_EXPORTED_HEADERS = glob([ + 'AsyncDisplayKit/*.h', + 'AsyncDisplayKit/Details/**/*.h', + 'AsyncDisplayKit/Layout/*.h', + 'Base/*.h', + 'AsyncDisplayKit/Debug/ASLayoutElementInspectorNode.h', + # Most TextKit components are not public because the C++ content + # in the headers will cause build errors when using + # `use_frameworks!` on 0.39.0 & Swift 2.1. + # See https://github.com/facebook/AsyncDisplayKit/issues/1153 + 'AsyncDisplayKit/TextKit/ASTextNodeTypes.h', + 'AsyncDisplayKit/TextKit/ASTextKitComponents.h' +]) + +ASYNCDISPLAYKIT_PRIVATE_HEADERS = glob([ + 'AsyncDisplayKit/**/*.h' + ], + excludes = ASYNCDISPLAYKIT_EXPORTED_HEADERS, +) + +def asyncdisplaykit_library( + name, + additional_preprocessor_flags = [], + deps = [], + additional_frameworks = []): + + apple_library( + name = name, + prefix_header = 'AsyncDisplayKit/AsyncDisplayKit-Prefix.pch', + header_path_prefix = 'AsyncDisplayKit', + exported_headers = ASYNCDISPLAYKIT_EXPORTED_HEADERS, + headers = ASYNCDISPLAYKIT_PRIVATE_HEADERS, + srcs = glob([ + 'AsyncDisplayKit/**/*.m', + 'AsyncDisplayKit/**/*.mm', + 'Base/*.m' + ]), + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS + additional_preprocessor_flags, + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = COMMON_LINKER_FLAGS + [ + '-weak_framework', + 'Photos', + '-weak_framework', + 'MapKit', + ], + deps = deps, + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + '$SDKROOT/System/Library/Frameworks/AssetsLibrary.framework', + + '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', + '$SDKROOT/System/Library/Frameworks/CoreMedia.framework', + '$SDKROOT/System/Library/Frameworks/CoreText.framework', + '$SDKROOT/System/Library/Frameworks/CoreGraphics.framework', + '$SDKROOT/System/Library/Frameworks/CoreLocation.framework', + '$SDKROOT/System/Library/Frameworks/AVFoundation.framework', + ] + additional_frameworks, + visibility = ['PUBLIC'], + ) + +##################################### +# AsyncDisplayKit targets +##################################### +asyncdisplaykit_library( + name = 'AsyncDisplayKit-Core', +) + +# (Default) AsyncDisplayKit and AsyncDisplayKit-PINRemoteImage targets are basically the same library with different names +for name in ['AsyncDisplayKit', 'AsyncDisplayKit-PINRemoteImage']: + asyncdisplaykit_library( + name = name, + additional_preprocessor_flags = ['-DPIN_REMOTE_IMAGE=1'], + deps = [ + '//Pods/PINRemoteImage:PINRemoteImage-PINCache', + ], + additional_frameworks = [ + '$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework', + ] + ) + +##################################### +# Test Host +# TODO: Split to smaller BUCK files and parse in parallel +##################################### +apple_resource( + name = 'TestHostResources', + files = ['Default-568h@2x.png'], + dirs = [], +) + +apple_bundle( + name = 'TestHost', + binary = ':TestHostBinary', + extension = 'app', + info_plist = 'AsyncDisplayKitTestHost/Info.plist', + info_plist_substitutions = { + 'PRODUCT_BUNDLE_IDENTIFIER': 'com.facebook.AsyncDisplayKitTestHost', + }, + tests = [':Tests'], +) + +apple_binary( + name = 'TestHostBinary', + headers = glob(['AsyncDisplayKitTestHost/*.h']), + srcs = glob([ + 'AsyncDisplayKitTestHost/*.m', + 'AsyncDisplayKitTestHost/*.mm', + ]), + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = COMMON_LINKER_FLAGS, + deps = [ + ':TestHostResources', + ':AsyncDisplayKit-Core', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Photos.framework', + '$SDKROOT/System/Library/Frameworks/MapKit.framework', + ], +) + +apple_package( + name = 'TestHostPackage', + bundle = ':TestHost', +) + +##################################### +# Tests +##################################### +apple_resource( + name = 'TestsResources', + files = ['AsyncDisplayKitTests/en.lproj/InfoPlist.strings'], + dirs = ['AsyncDisplayKitTests/TestResources'], +) + +apple_resource( + name = 'SnapshotTestsResources', + files = [], + dirs = [ + 'AsyncDisplayKitTests/ReferenceImages_32', + 'AsyncDisplayKitTests/ReferenceImages_64', + 'AsyncDisplayKitTests/ReferenceImages_iOS_10', + ], +) + +apple_test( + name = 'Tests', + test_host_app = ':TestHost', + info_plist = 'AsyncDisplayKitTests/AsyncDisplayKitTests-Info.plist', + info_plist_substitutions = { + 'PRODUCT_BUNDLE_IDENTIFIER': 'com.facebook.AsyncDisplayKitTests', + }, + prefix_header = 'AsyncDisplayKitTests/AsyncDisplayKitTests-Prefix.pch', + # Expose all ASDK headers to tests + headers = ASYNCDISPLAYKIT_EXPORTED_HEADERS + ASYNCDISPLAYKIT_PRIVATE_HEADERS + glob([ + 'AsyncDisplayKitTests/*.h', + ]), + srcs = glob([ + 'AsyncDisplayKitTests/*.m', + 'AsyncDisplayKitTests/*.mm' + ], + # ASTextNodePerformanceTests are excluded (#2173) + excludes = ['AsyncDisplayKitTests/ASTextNodePerformanceTests.m*'] + ), + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS + [ + '-Wno-implicit-function-declaration', + ], + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = COMMON_LINKER_FLAGS, + deps = [ + ':TestsResources', + ':SnapshotTestsResources', + '//Pods/OCMock:OCMock', + '//Pods/FBSnapshotTestCase:FBSnapshotTestCase', + '//Pods/JGMethodSwizzler:JGMethodSwizzler', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + + '$SDKROOT/System/Library/Frameworks/CoreMedia.framework', + '$SDKROOT/System/Library/Frameworks/CoreText.framework', + '$SDKROOT/System/Library/Frameworks/CoreGraphics.framework', + '$SDKROOT/System/Library/Frameworks/AVFoundation.framework', + + '$PLATFORM_DIR/Developer/Library/Frameworks/XCTest.framework', + ], +) diff --git a/Base/ASBaseDefines.h b/Base/ASBaseDefines.h index 88cc188130..b0f45cc3fb 100755 --- a/Base/ASBaseDefines.h +++ b/Base/ASBaseDefines.h @@ -183,5 +183,15 @@ #define ASOVERLOADABLE __attribute__((overloadable)) + +#if __has_attribute(noescape) +#define AS_NOESCAPE __attribute__((noescape)) +#else +#define AS_NOESCAPE +#endif + /// Ensure that class is of certain kind -#define ASDynamicCast(x, c) ((c *) ([x isKindOfClass:[c class]] ? x : nil)) +#define ASDynamicCast(x, c) ({ \ + id __val = x;\ + ((c *) ([__val isKindOfClass:[c class]] ? __val : nil));\ +}) diff --git a/Podfile b/Podfile index d1b954c71b..e1bdbb8b62 100644 --- a/Podfile +++ b/Podfile @@ -6,4 +6,26 @@ target :'AsyncDisplayKitTests' do pod 'OCMock', '~> 2.2' pod 'FBSnapshotTestCase/Core', '~> 2.1' pod 'JGMethodSwizzler', :git => 'https://github.com/JonasGessner/JGMethodSwizzler', :branch => 'master' + + # Only for buck build + pod 'PINRemoteImage', '3.0.0-beta.7' + + #TODO CocoaPods plugin instead? + post_install do |installer| + require 'fileutils' + + # Assuming we're at the root dir + buck_files_dir = 'buck-files' + if File.directory?(buck_files_dir) + installer.pod_targets.flat_map do |pod_target| + pod_name = pod_target.pod_name + # Copy the file at buck-files/BUCK_pod_name to Pods/pod_name/BUCK, + # override existing file if needed + buck_file = buck_files_dir + '/BUCK_' + pod_name + if File.file?(buck_file) + FileUtils.cp(buck_file, 'Pods/' + pod_name + '/BUCK', :preserve => false) + end + end + end + end end diff --git a/README.md b/README.md index 883f6de8f3..5f42670c59 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![AsyncDisplayKit](https://github.com/AsyncDisplayKit/Documentation/raw/master/docs/static/images/logo.png) -[![Apps Using](https://img.shields.io/badge/Apps%20Using%20ASDK-%3E4,974-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) -[![Downloads](https://img.shields.io/badge/Total%20Downloads-%3E512,000-28B9FE.svg)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Apps Using](https://img.shields.io/cocoapods/at/AsyncDisplayKit.svg?label=Apps%20Using%20ASDK&colorB=28B9FE)](http://cocoapods.org/pods/AsyncDisplayKit) +[![Downloads](https://img.shields.io/cocoapods/dt/AsyncDisplayKit.svg?label=Total%20Downloads&colorB=28B9FE)](http://cocoapods.org/pods/AsyncDisplayKit) [![Platform](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-orange.svg)](http://AsyncDisplayKit.org) [![Languages](https://img.shields.io/badge/languages-ObjC%20%7C%20Swift-orange.svg)](http://AsyncDisplayKit.org) diff --git a/buck-files/BUCK_FBSnapshotTestCase b/buck-files/BUCK_FBSnapshotTestCase new file mode 100755 index 0000000000..c8b969639e --- /dev/null +++ b/buck-files/BUCK_FBSnapshotTestCase @@ -0,0 +1,12 @@ +apple_library( + name = 'FBSnapshotTestCase', + exported_headers = glob(['FBSnapshotTestCase' + '/**/*.h']), + srcs = glob(['FBSnapshotTestCase' + '/**/*.m']), + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', + '$PLATFORM_DIR/Developer/Library/Frameworks/XCTest.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_FLAnimatedImage b/buck-files/BUCK_FLAnimatedImage new file mode 100755 index 0000000000..f04abd396b --- /dev/null +++ b/buck-files/BUCK_FLAnimatedImage @@ -0,0 +1,18 @@ +apple_library( + name = 'FLAnimatedImage', + exported_headers = glob(['FLAnimatedImage/*.h']), + srcs = glob(['FLAnimatedImage/*.m']), + preprocessor_flags = ['-fobjc-arc', '-Wno-deprecated-declarations'], + lang_preprocessor_flags = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=gnu++11', '-stdlib=libc++'], + }, + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + '$SDKROOT/System/Library/Frameworks/ImageIO.framework', + '$SDKROOT/System/Library/Frameworks/MobileCoreServices.framework', + '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_JGMethodSwizzler b/buck-files/BUCK_JGMethodSwizzler new file mode 100755 index 0000000000..169cfa1e01 --- /dev/null +++ b/buck-files/BUCK_JGMethodSwizzler @@ -0,0 +1,9 @@ +apple_library( + name = 'JGMethodSwizzler', + exported_headers = ['JGMethodSwizzler' + '/JGMethodSwizzler.h'], + srcs = ['JGMethodSwizzler' + '/JGMethodSwizzler.m'], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_OCMock b/buck-files/BUCK_OCMock new file mode 100755 index 0000000000..666f844582 --- /dev/null +++ b/buck-files/BUCK_OCMock @@ -0,0 +1,9 @@ +apple_library( + name = 'OCMock', + exported_headers = glob(['Source/OCMock' + '/*.h']), + srcs = glob(['Source/OCMock' + '/*.m']), + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_PINCache b/buck-files/BUCK_PINCache new file mode 100755 index 0000000000..660b69f716 --- /dev/null +++ b/buck-files/BUCK_PINCache @@ -0,0 +1,23 @@ +apple_library( + name = 'PINCache', + exported_headers = glob(['PINCache/*.h']), + # PINDiskCache.m should be compiled with '-fobjc-arc-exceptions' (#105) + srcs = + glob(['PINCache/*.m'], excludes = ['PINCache/PINDiskCache.m']) + + [('PINCache/PINDiskCache.m', ['-fobjc-arc-exceptions'])], + preprocessor_flags = ['-fobjc-arc'], + lang_preprocessor_flags = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=gnu++11', '-stdlib=libc++'], + }, + linker_flags = [ + '-weak_framework', + 'UIKit', + '-weak_framework', + 'AppKit', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + ], + visibility = ['PUBLIC'], +) diff --git a/buck-files/BUCK_PINRemoteImage b/buck-files/BUCK_PINRemoteImage new file mode 100755 index 0000000000..95825e4d95 --- /dev/null +++ b/buck-files/BUCK_PINRemoteImage @@ -0,0 +1,93 @@ +##################################### +# Defines +##################################### +COMMON_PREPROCESSOR_FLAGS = ['-fobjc-arc'] + +COMMON_LANG_PREPROCESSOR_FLAGS = { + 'C': ['-std=gnu99'], + 'CXX': ['-std=gnu++11', '-stdlib=libc++'], +} + +FLANIMATEDIMAGE_HEADER_FILES = ['Pod/Classes/Image Categories/FLAnimatedImageView+PINRemoteImage.h'] +FLANIMATEDIMAGE_SOURCE_FILES = ['Pod/Classes/Image Categories/FLAnimatedImageView+PINRemoteImage.m'] + +PINCACHE_HEADER_FILES = glob(['Pod/Classes/PINCache/**/*.h']) +PINCACHE_SOURCE_FILES = glob(['Pod/Classes/PINCache/**/*.m']) + +##################################### +# PINRemoteImage core targets +##################################### +apple_library( + name = 'PINRemoteImage-Core', + header_path_prefix = 'PINRemoteImage', + exported_headers = glob([ + 'Pod/Classes/**/*.h', + ], + excludes = FLANIMATEDIMAGE_HEADER_FILES + PINCACHE_HEADER_FILES + ), + srcs = glob([ + 'Pod/Classes/**/*.m', + ], + excludes = FLANIMATEDIMAGE_SOURCE_FILES + PINCACHE_SOURCE_FILES + ), + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS + [ + '-DPIN_TARGET_IOS=(TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR || TARGET_OS_TV)', + '-DPIN_TARGET_MAC=(TARGET_OS_MAC)', + ], + lang_preprocessor_flags = COMMON_LANG_PREPROCESSOR_FLAGS, + linker_flags = [ + '-weak_framework', + 'UIKit', + '-weak_framework', + 'MobileCoreServices', + '-weak_framework', + 'Cocoa', + '-weak_framework', + 'CoreServices', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/ImageIO.framework', + '$SDKROOT/System/Library/Frameworks/Accelerate.framework', + ], + visibility = ['PUBLIC'], +) + +apple_library( + name = 'PINRemoteImage', + deps = [ + ':PINRemoteImage-FLAnimatedImage', + ':PINRemoteImage-PINCache' + ], + visibility = ['PUBLIC'], +) + +##################################### +# Other PINRemoteImage targets +##################################### +apple_library( + name = 'PINRemoteImage-FLAnimatedImage', + header_path_prefix = 'PINRemoteImage', + exported_headers = FLANIMATEDIMAGE_HEADER_FILES, + srcs = FLANIMATEDIMAGE_SOURCE_FILES, + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS, + deps = [ + ':PINRemoteImage-Core', + '//Pods/FLAnimatedImage:FLAnimatedImage' + ], + visibility = ['PUBLIC'], +) + +apple_library( + name = 'PINRemoteImage-PINCache', + header_path_prefix = 'PINRemoteImage', + exported_headers = PINCACHE_HEADER_FILES, + srcs = PINCACHE_SOURCE_FILES, + preprocessor_flags = COMMON_PREPROCESSOR_FLAGS, + deps = [ + ':PINRemoteImage-Core', + '//Pods/PINCache:PINCache' + ], + visibility = ['PUBLIC'], +) + +#TODO WebP variants diff --git a/build.sh b/build.sh index d87103ce7a..060d48d040 100755 --- a/build.sh +++ b/build.sh @@ -295,7 +295,7 @@ fi if [ "$MODE" = "cocoapods-lint" ]; then echo "Verifying that podspec lints." - set -o pipefail && pod lib lint + set -o pipefail && pod env && pod lib lint trap - EXIT exit 0 fi diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index 67e586b4a7..db215cb40b 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -88,10 +88,11 @@ static const CGFloat kInnerPadding = 10.0f; // kitten image, with a solid background colour serving as placeholder _imageNode = [[ASNetworkImageNode alloc] init]; - _imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); _imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"https://placekitten.com/%zd/%zd", (NSInteger)roundl(_kittenSize.width), (NSInteger)roundl(_kittenSize.height)]]; + _imageNode.placeholderFadeDuration = .5; + _imageNode.placeholderColor = ASDisplayNodeDefaultPlaceholderColor(); // _imageNode.contentMode = UIViewContentModeCenter; [_imageNode addTarget:self action:@selector(toggleNodesSwap) forControlEvents:ASControlNodeEventTouchUpInside]; [self addSubnode:_imageNode];