diff --git a/.travis.yml b/.travis.yml index 124c04c4bd..520f2f7600 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ env: - MODE=tests - MODE=examples - MODE=life-without-cocoapods + - MODE=framework script: ./build.sh $MODE after_success: - slather diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3f6adff9cd..1a58fabfe9 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -140,6 +140,8 @@ 205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; 242995D31B29743C00090100 /* ASBasicImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */; }; + 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; settings = {ASSET_TAGS = (); }; }; 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2911485B1A77147A005D0878 /* ASControlNodeTests.m */; }; 291B63FB1AA53A7A000A71B3 /* ASScrollDirection.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A311A951715005ACEAA /* ASScrollDirection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -154,6 +156,7 @@ 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */; }; 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED071B17843500DA7C62 /* ASDimension.h */; settings = {ATTRIBUTES = (Public, ); }; }; 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED081B17843500DA7C62 /* ASDimension.mm */; }; 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */; }; @@ -372,6 +375,10 @@ B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; + CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; settings = {ASSET_TAGS = (); }; }; + CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -617,6 +624,9 @@ ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASStackLayoutSpecSnapshotTests.mm; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B35061DD1B010EDF0018CF92 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "../AsyncDisplayKit-iOS/Info.plist"; sourceTree = ""; }; + CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; + CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = ""; }; + CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; @@ -799,6 +809,7 @@ ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */, + CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */, 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */, 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.m */, 2911485B1A77147A005D0878 /* ASControlNodeTests.m */, @@ -834,6 +845,8 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */, + CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, @@ -1104,6 +1117,7 @@ 9C8221951BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */, AC21EC101B3D0BF600C8B19A /* ASStackLayoutDefines.h in Headers */, + CC7FD9DE1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, ACF6ED2F1B17843500DA7C62 /* ASStackLayoutSpec.h in Headers */, ACF6ED4E1B17847A00DA7C62 /* ASStackLayoutSpecUtilities.h in Headers */, ACF6ED4F1B17847A00DA7C62 /* ASStackPositionedLayout.h in Headers */, @@ -1206,7 +1220,9 @@ 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutable.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, + CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, + 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */, 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, 34EFC7751B701D2400AD841F /* ASStackPositionedLayout.h in Headers */, 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */, @@ -1490,6 +1506,7 @@ 058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */, 058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */, + CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1511,6 +1528,7 @@ 056D21551ABCEF50001107EF /* ASImageNodeSnapshotTests.m in Sources */, ACF6ED5E1B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm in Sources */, ACF6ED601B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m in Sources */, + CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */, 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */, 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, @@ -1534,6 +1552,7 @@ B35062401B010EFD0018CF92 /* _ASAsyncTransactionContainer.m in Sources */, B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, + 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */, B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, B35062121B010EFD0018CF92 /* _ASDisplayView.mm in Sources */, B350624C1B010EFD0018CF92 /* _ASPendingState.m in Sources */, @@ -1594,6 +1613,7 @@ B35062351B010EFD0018CF92 /* ASTextNodeTextKitHelpers.mm in Sources */, B35062381B010EFD0018CF92 /* ASTextNodeWordKerner.m in Sources */, 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */, + 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, B35062441B010EFD0018CF92 /* UIView+ASConvenience.m in Sources */, diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme index 2a1226a3f6..df6da9c539 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit-iOS.xcscheme @@ -1,6 +1,6 @@ @@ -23,26 +23,29 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> + + @@ -52,15 +55,15 @@ diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 3eaf6c9402..c37db8e85f 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -62,15 +62,18 @@ ReferencedContainer = "container:AsyncDisplayKit.xcodeproj"> + + )dataSource { - ASDisplayNodeAssert(NO, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); + // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. + ASDisplayNodeAssert(dataSource == nil, @"ASCollectionView uses asyncDataSource, not UICollectionView's dataSource property."); } - (void)setDelegate:(id)delegate @@ -314,6 +318,16 @@ static BOOL _isInterceptedSelector(SEL sel) return [[_dataController nodeAtIndexPath:indexPath] calculatedSize]; } +- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return [_dataController nodeAtIndexPath:indexPath]; +} + +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode +{ + return [_dataController indexPathForNode:cellNode]; +} + - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleItems]; @@ -391,11 +405,6 @@ static BOOL _isInterceptedSelector(SEL sel) [_dataController moveRowAtIndexPath:indexPath toIndexPath:newIndexPath withAnimationOptions:kASCollectionViewAnimationNone]; } -- (ASCellNode *)nodeForItemAtIndexPath:(NSIndexPath *)indexPath -{ - return [_dataController nodeAtIndexPath:indexPath]; -} - #pragma mark - #pragma mark Intercepted selectors. @@ -489,6 +498,11 @@ static BOOL _isInterceptedSelector(SEL sel) if ([_asyncDelegate respondsToSelector:@selector(collectionView:willDisplayNodeForItemAtIndexPath:)]) { [_asyncDelegate collectionView:self willDisplayNodeForItemAtIndexPath:indexPath]; } + + ASCellNode *cellNode = [self nodeForItemAtIndexPath:indexPath]; + if (cellNode.neverShowPlaceholders) { + [cellNode recursivelyEnsureDisplay]; + } } - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index 7f94e3f28a..b1b2ac10c8 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -431,9 +431,41 @@ NS_ASSUME_NONNULL_BEGIN // This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, // but it's considered private API for now and its use should not be encouraged. - (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass; + +// The two methods below will eventually be exposed, but their names are subject to change. +/** + * @abstract Ensure that all rendering is complete for this node and its descendents. + * + * @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that + * placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController + * to implement their respective ".neverShowPlaceholders" option. + * + * If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately. + * + * This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an + * asynchronous display operation, and some already finished. + * + * In order to guarantee against deadlocks, this method should only be called on the main thread. + * It may block on the private queue, [_ASDisplayLayer displayQueue] + */ +- (void)recursivelyEnsureDisplay; + +/** + * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. + * + * @discussion Nodes that are expensive to draw and expected to have placeholder even with + * .neverShowPlaceholders enabled should set this to YES. + * + * ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay. + * + * ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server, + * and are expected to support a placeholder state given that display is often blocked on slow data fetching. + */ +@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; + @end -#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity") -#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity") +#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") +#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 323ade43e5..f8b5aef2ec 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -14,6 +14,7 @@ #import #import "_ASAsyncTransaction.h" +#import "_ASAsyncTransactionContainer+Private.h" #import "_ASPendingState.h" #import "_ASDisplayView.h" #import "_ASScopeTimer.h" @@ -671,25 +672,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // This is the root node. Trigger a full measurement pass on *current* thread. Old constrained size is re-used. [self measureWithSizeRange:oldConstrainedSize]; - CGSize oldSize = self.bounds.size; + CGRect oldBounds = self.bounds; + CGSize oldSize = oldBounds.size; CGSize newSize = _layout.size; if (! CGSizeEqualToSize(oldSize, newSize)) { - CGRect bounds = self.bounds; - bounds.size = newSize; - self.bounds = bounds; + self.bounds = (CGRect){ oldBounds.origin, newSize }; // Frame's origin must be preserved. Since it is computed from bounds size, anchorPoint // and position (see frame setter in ASDisplayNode+UIViewBridge), position needs to be adjusted. - BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); - CGPoint anchorPoint = (useLayer ? _layer.anchorPoint : self.anchorPoint); - CGPoint oldPosition = (useLayer ? _layer.position : self.position); - + CGPoint anchorPoint = self.anchorPoint; + CGPoint oldPosition = self.position; CGFloat xDelta = (newSize.width - oldSize.width) * anchorPoint.x; CGFloat yDelta = (newSize.height - oldSize.height) * anchorPoint.y; - CGPoint newPosition = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); - - useLayer ? _layer.position = newPosition : self.position = newPosition; + self.position = CGPointMake(oldPosition.x + xDelta, oldPosition.y + yDelta); } } } @@ -1415,6 +1411,68 @@ static NSInteger incrementIfFound(NSInteger i) { [_placeholderLayer removeFromSuperlayer]; } +void recursivelyEnsureDisplayForLayer(CALayer *layer) +{ + // This recursion must handle layers in various states: + // 1. Just added to hierarchy, CA hasn't yet called -display + // 2. Previously in a hierarchy (such as a working window owned by an Intelligent Preloading class, like ASTableView / ASCollectionView / ASViewController) + // 3. Has no content to display at all + // Specifically for case 1), we need to explicitly trigger a -display call now. + // Otherwise, there is no opportunity to block the main thread after CoreAnimation's transaction commit + // (even a runloop observer at a late call order will not stop the next frame from compositing, showing placeholders). + + ASDisplayNode *node = [layer asyncdisplaykit_node]; + if (!layer.contents && [node _implementsDisplay]) { + // For layers that do get displayed here, this immediately kicks off the work on the concurrent -[_ASDisplayLayer displayQueue]. + // At the same time, it creates an associated _ASAsyncTransaction, which we can use to block on display completion. See ASDisplayNode+AsyncDisplay.mm. + [layer displayIfNeeded]; + } + + // Kick off the recursion first, so that all necessary display calls are sent and the displayQueue is full of parallelizable work. + for (CALayer *sublayer in layer.sublayers) { + recursivelyEnsureDisplayForLayer(sublayer); + } + + // As the recursion unwinds, verify each transaction is complete and block if it is not. + // While blocking on one transaction, others may be completing concurrently, so it doesn't matter which blocks first. + BOOL waitUntilComplete = (!node.shouldBypassEnsureDisplay); + if (waitUntilComplete) { + for (_ASAsyncTransaction *transaction in [layer.asyncdisplaykit_asyncLayerTransactions copy]) { + // Even if none of the layers have had a chance to start display earlier, they will still be allowed to saturate a multicore CPU while blocking main. + // This significantly reduces time on the main thread relative to UIKit. + [transaction waitUntilComplete]; + } + } +} + +- (void)recursivelyEnsureDisplay +{ + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(self.isNodeLoaded, @"Node must have layer or view loaded to use -recursivelyEnsureDisplay"); + ASDisplayNodeAssert(self.inHierarchy && (self.isLayerBacked || self.view.window != nil), @"Node must be in a hierarchy to use -recursivelyEnsureDisplay"); + + CALayer *layer = self.layer; + // -layoutIfNeeded is recursive, and even walks up to superlayers to check if they need layout, + // so we should call it outside of starting the recursion below. If our own layer is not marked + // as dirty, we can assume layout has run on this subtree before. + if ([layer needsLayout]) { + [layer layoutIfNeeded]; + } + recursivelyEnsureDisplayForLayer(layer); +} + +- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay +{ + ASDN::MutexLocker l(_propertyLock); + _flags.shouldBypassEnsureDisplay = shouldBypassEnsureDisplay; +} + +- (BOOL)shouldBypassEnsureDisplay +{ + ASDN::MutexLocker l(_propertyLock); + return _flags.shouldBypassEnsureDisplay; +} + #pragma mark - For Subclasses - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize @@ -1484,6 +1542,7 @@ static NSInteger incrementIfFound(NSInteger i) { ASDN::MutexLocker l(_propertyLock); return _preferredFrameSize; } + - (UIImage *)placeholderImage { return nil; diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index 6c4b4582ba..4a810f15d6 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -12,7 +12,10 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASEditableTextNodeDelegate; -/// @abstract ASEditableTextNode implements a node that supports text editing. +/** + @abstract Implements a node that supports text editing. + @discussion Does not support layer backing. + */ @interface ASEditableTextNode : ASDisplayNode // @abstract The text node's delegate, which must conform to the protocol. diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index bd04062b29..e6b5399edf 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -143,6 +143,7 @@ _textKitComponents.textView.accessibilityHint = _placeholderTextKitComponents.textStorage.string; configureTextView(_textKitComponents.textView); [self.view addSubview:_textKitComponents.textView]; + [self _updateDisplayingPlaceholder]; } - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize @@ -188,6 +189,12 @@ _placeholderTextKitComponents.textView.opaque = opaque; } +- (void)setLayerBacked:(BOOL)layerBacked +{ + ASDisplayNodeAssert(!layerBacked, @"Cannot set layerBacked to YES on ASEditableTextNode – instances must be view-backed in order to ensure touch events can be passed to the internal UITextView during editing."); + [super setLayerBacked:layerBacked]; +} + #pragma mark - Configuration @synthesize delegate = _delegate; diff --git a/AsyncDisplayKit/ASMultiplexImageNode.h b/AsyncDisplayKit/ASMultiplexImageNode.h index 9c2807689c..c1ddedae02 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.h +++ b/AsyncDisplayKit/ASMultiplexImageNode.h @@ -9,6 +9,8 @@ #import #import +#import + NS_ASSUME_NONNULL_BEGIN @protocol ASMultiplexImageNodeDelegate; @@ -100,6 +102,13 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { */ @property (nullable, nonatomic, readonly) ASImageIdentifier displayedImageIdentifier; +/** + * @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used. + + * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. + */ +@property (nonatomic, strong) PHImageManager *imageManager; + @end @@ -197,13 +206,34 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier * @abstract An image URL for the specified identifier. * @param imageNode The sender. * @param imageIdentifier The identifier for the image that will be downloaded. - * @discussion Supported URLs include assets-library, Photo framework URLs (ph://), HTTP, HTTPS, and FTP URLs. If the - * image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource + * @discussion Supported URLs include HTTP, HTTPS, AssetsLibrary, and FTP URLs as well as Photos framework URLs (see note). + * + * If the image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource * multiplexImageNode:imageForImageIdentifier:]> instead. - * @returns An NSURL for the image identified by `imageIdentifier`, or nil if none is available. + * @return An NSURL for the image identified by `imageIdentifier`, or nil if none is available. + * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. */ - (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier; @end -NS_ASSUME_NONNULL_END +#pragma mark - + +@interface NSURL (ASPhotosFrameworkURLs) + +/** + * @abstract Create an NSURL that specifies an image from the Photos framework. + * + * @discussion When implementing `-multiplexImageNode:URLForImageIdentifier:`, you can return a URL + * created by this method and the image node will attempt to load the image from the Photos framework. + * @note The `synchronous` flag in `options` is ignored. + * @note The `Opportunistic` delivery mode is not supported and will be treated as `HighQualityFormat`. + */ ++ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier + targetSize:(CGSize)targetSize + contentMode:(PHImageContentMode)contentMode + options:(PHImageRequestOptions *)options; + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index d32d3be061..4ffb8a1275 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -18,6 +18,7 @@ #import "ASBaseDefines.h" #import "ASDisplayNode+Subclasses.h" #import "ASLog.h" +#import "ASPhotosFrameworkImageRequest.h" #if !AS_IOS8_SDK_OR_LATER #error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK. @@ -26,8 +27,6 @@ NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; static NSString *const kAssetsLibraryURLScheme = @"assets-library"; -static NSString *const kPHAssetURLScheme = @"ph"; -static NSString *const kPHAssetURLPrefix = @"ph://"; /** @abstract Signature for the block to be performed after an image has loaded. @@ -120,14 +119,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; /** - @abstract Loads the image corresponding to the given assetURL from the Photos framework. + @abstract Loads the image corresponding to the given image request from the Photos framework. @param imageIdentifier The identifier for the image to be loaded. May not be nil. - @param assetURL The photos framework URL (e.g., "ph://identifier") of the image to load, from PHAsset. May not be nil. + @param request The photos image request to load. May not be nil. @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. @param image The image that was loaded. May be nil if no image could be downloaded. @param error An error describing why the load failed, if it failed; nil otherwise. */ -- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; +- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock; /** @abstract Downloads the image corresponding to the given imageIdentifier from the given URL. @@ -158,6 +157,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = cache; _downloader = downloader; + self.shouldBypassEnsureDisplay = YES; return self; } @@ -456,8 +456,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }]; } // Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly. - else if (AS_AT_LEAST_IOS8 && [[nextImageURL scheme] isEqualToString:kPHAssetURLScheme]) { - [self _loadPHAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *image, NSError *error) { + else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { + [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier); finishedLoadingBlock(image, nextImageIdentifier, error); }]; @@ -511,38 +511,48 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }]; } -- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock +- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock { ASDisplayNodeAssert(AS_AT_LEAST_IOS8, @"PhotosKit is unavailable on iOS 7."); ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); - ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required"); + ASDisplayNodeAssertNotNil(request, @"request is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - - // Get the PHAsset itself. - ASDisplayNodeAssertTrue([[assetURL absoluteString] hasPrefix:kPHAssetURLPrefix]); - NSString *assetIdentifier = [[assetURL absoluteString] substringFromIndex:[kPHAssetURLPrefix length]]; - PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetIdentifier] options:nil]; - if ([assetFetchResult count] == 0) { - // Error. - completionBlock(nil, nil); - return; - } - - // Get the best image we can. - PHAsset *imageAsset = [assetFetchResult firstObject]; - - PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init]; - requestOptions.version = PHImageRequestOptionsVersionCurrent; - requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; - requestOptions.resizeMode = PHImageRequestOptionsResizeModeNone; - - [[PHImageManager defaultManager] requestImageForAsset:imageAsset - targetSize:CGSizeMake(2048.0, 2048.0) // Ideally we would use PHImageManagerMaximumSize and kill the options, but we get back nil when requesting images of video assets. rdar://18447788 - contentMode:PHImageContentModeDefault - options:requestOptions - resultHandler:^(UIImage *image, NSDictionary *info) { - completionBlock(image, info[PHImageErrorKey]); - }]; + + // This is sometimes called on main but there's no reason to stay there + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + // Get the PHAsset itself. + PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[request.assetIdentifier] options:nil]; + if ([assetFetchResult count] == 0) { + // Error. + completionBlock(nil, nil); + return; + } + + PHAsset *imageAsset = [assetFetchResult firstObject]; + PHImageRequestOptions *options = [request.options copy]; + + // We don't support opportunistic delivery – one request, one image. + if (options.deliveryMode == PHImageRequestOptionsDeliveryModeOpportunistic) { + options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; + } + + if (options.deliveryMode == PHImageRequestOptionsDeliveryModeHighQualityFormat) { + // Without this flag the result will be delivered on the main queue, which is pointless + // But synchronous -> HighQualityFormat so we only use it if high quality format is specified + options.synchronous = YES; + } + + PHImageManager *imageManager = self.imageManager ?: PHImageManager.defaultManager; + [imageManager requestImageForAsset:imageAsset targetSize:request.targetSize contentMode:request.contentMode options:options resultHandler:^(UIImage *image, NSDictionary *info) { + if (NSThread.isMainThread) { + dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ + completionBlock(image, info[PHImageErrorKey]); + }); + } else { + completionBlock(image, info[PHImageErrorKey]); + } + }]; + }); } - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock @@ -642,3 +652,16 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent } @end + +@implementation NSURL (ASPhotosFrameworkURLs) + ++ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options +{ + ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier]; + request.options = options; + request.contentMode = contentMode; + request.targetSize = targetSize; + return request.url; +} + +@end \ No newline at end of file diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 658176eeee..d41fd60976 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -44,6 +44,7 @@ _cache = cache; _downloader = downloader; _shouldCacheImage = YES; + self.shouldBypassEnsureDisplay = YES; return self; } diff --git a/AsyncDisplayKit/ASTableView.h b/AsyncDisplayKit/ASTableView.h index 3e42518b4d..e67a49634e 100644 --- a/AsyncDisplayKit/ASTableView.h +++ b/AsyncDisplayKit/ASTableView.h @@ -28,8 +28,28 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ASTableView : UITableView -@property (nonatomic, weak) id asyncDataSource; @property (nonatomic, weak) id asyncDelegate; // must not be nil +@property (nonatomic, weak) id asyncDataSource; + +/** + * Initializer. + * + * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. + * The frame of the table view changes as table cells are added and deleted. + * + * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. + * + * @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op. + * + * @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and + * `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically + * from calling thread. + * Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for + * large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically, + * we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching. + * The application should not update the data source while the data source is locked, to keep data consistence. + */ +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; /** * Tuning parameters for a range. @@ -51,26 +71,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType; -/** - * Initializer. - * - * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. - * The frame of the table view changes as table cells are added and deleted. - * - * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. - * - * @param asyncDataFetchingEnabled Enable the data fetching in async mode. - * - * @discussion If asyncDataFetching is enabled, the `ASTableView` will fetch data through `tableView:numberOfRowsInSection:` and - * `tableView:nodeForRowAtIndexPath:` in async mode from background thread. Otherwise, the methods will be invoked synchronically - * from calling thread. - * Enabling asyncDataFetching could avoid blocking main thread for `ASCellNode` allocation, which is frequently reported issue for - * large scale data. On another hand, the application code need take the responsibility to avoid data inconsistence. Specifically, - * we will lock the data source through `tableViewLockDataSource`, and unlock it by `tableViewUnlockDataSource` after the data fetching. - * The application should not update the data source while the data source is locked, to keep data consistence. - */ -- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled; - /** * The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called. * @@ -228,6 +228,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASCellNode *)nodeForRowAtIndexPath:(NSIndexPath *)indexPath; +/** + * Similar to -indexPathForCell:. + * + * @param cellNode a cellNode part of the table view + * + * @returns an indexPath for this cellNode + */ +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; + /** * Similar to -visibleCells. * diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index b7ebe9c2f1..7c36c775a6 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -18,6 +18,9 @@ #import "ASInternalHelpers.h" #import "ASLayout.h" +// FIXME: Temporary nonsense import until method names are finalized and exposed +#import "ASDisplayNode+Subclasses.h" + //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -259,7 +262,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - (void)setDataSource:(id)dataSource { - ASDisplayNodeAssert(NO, @"ASTableView uses asyncDataSource, not UITableView's dataSource property."); + // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. + ASDisplayNodeAssert(dataSource == nil, @"ASTableView uses asyncDataSource, not UITableView's dataSource property."); } - (void)setDelegate:(id)delegate @@ -345,6 +349,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { return [_dataController nodeAtIndexPath:indexPath]; } +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode +{ + return [_dataController indexPathForNode:cellNode]; +} + - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleRows]; @@ -563,6 +572,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { if ([_asyncDelegate respondsToSelector:@selector(tableView:willDisplayNodeForRowAtIndexPath:)]) { [_asyncDelegate tableView:self willDisplayNodeForRowAtIndexPath:indexPath]; } + + ASCellNode *cellNode = [self nodeForRowAtIndexPath:indexPath]; + if (cellNode.neverShowPlaceholders) { + [cellNode recursivelyEnsureDisplay]; + } } - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index aca6626fb7..98643b995d 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -15,6 +15,11 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong, readonly) ASDisplayNode *node; +// AsyncDisplayKit 2.0 BETA: This property is still being tested, but it allows +// blocking as a view controller becomes visible to ensure no placeholders flash onscreen. +// Refer to examples/SynchronousConcurrency, AsyncViewController.m +@property (nonatomic, assign) BOOL neverShowPlaceholders; + - (instancetype)initWithNode:(ASDisplayNode *)node; @end diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 2a9f555178..ef89b905a6 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -10,7 +10,13 @@ #import "ASAssert.h" #import "ASDimension.h" +// FIXME: Temporary nonsense import until method names are finalized and exposed +#import "ASDisplayNode+Subclasses.h" + @implementation ASViewController +{ + BOOL _ensureDisplayed; +} - (instancetype)initWithNode:(ASDisplayNode *)node { @@ -33,15 +39,25 @@ - (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; CGSize viewSize = self.view.bounds.size; ASSizeRange constrainedSize = ASSizeRangeMake(viewSize, viewSize); [_node measureWithSizeRange:constrainedSize]; - [super viewWillLayoutSubviews]; +} + +- (void)viewDidLayoutSubviews +{ + if (_ensureDisplayed && self.neverShowPlaceholders) { + _ensureDisplayed = NO; + [self.node recursivelyEnsureDisplay]; + } + [super viewDidLayoutSubviews]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + _ensureDisplayed = YES; [_node recursivelyFetchData]; } diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 6b2aa9ea9d..2f4d77d15b 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -18,6 +18,7 @@ #import #import #import +#import #import #import diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 6399f9a8c0..aac7d94cf5 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -175,6 +175,8 @@ typedef NSUInteger ASDataControllerAnimationOptions; - (nullable ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath; +- (nullable NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; + - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths; - (NSArray *> *)completedNodes; // This provides efficient access to the entire _completedNodes multidimensional array. diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index b0e7a94bb5..7b07ff76ef 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -24,7 +24,8 @@ const static NSUInteger kASDataControllerSizingCountPerProcessor = 5; static void *kASSizingQueueContext = &kASSizingQueueContext; @interface ASDataController () { - NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this. + NSMutableArray *_externalCompletedNodes; // Main thread only. External data access can immediately query this if available. + NSMutableArray *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableArray *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. @@ -104,31 +105,27 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } dispatch_group_t layoutGroup = dispatch_group_create(); - + ASSizeRange *nodeBoundSizes = (ASSizeRange *)malloc(sizeof(ASSizeRange) * nodes.count); for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { - NSArray *subIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(j, MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j))]; + NSInteger batchCount = MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j); - //TODO: There should be a fast-path that avoids all of this object creation. - NSMutableArray *nodeBoundSizes = [[NSMutableArray alloc] initWithCapacity:kASDataControllerSizingCountPerProcessor]; - [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - ASSizeRange constrainedSize = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]; - [nodeBoundSizes addObject:[NSValue valueWithBytes:&constrainedSize objCType:@encode(ASSizeRange)]]; - }]; + for (NSUInteger k = j; k < j + batchCount; k++) { + nodeBoundSizes[k] = [_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPaths[k]]; + } dispatch_group_async(layoutGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { - ASCellNode *node = nodes[j + idx]; - ASSizeRange constrainedSize; - [nodeBoundSizes[idx] getValue:&constrainedSize]; + for (NSUInteger k = j; k < j + batchCount; k++) { + ASCellNode *node = nodes[k]; + ASSizeRange constrainedSize = nodeBoundSizes[k]; [node measureWithSizeRange:constrainedSize]; - node.frame = CGRectMake(0.0f, 0.0f, node.calculatedSize.width, node.calculatedSize.height); - }]; + node.frame = CGRectMake(0, 0, node.calculatedSize.width, node.calculatedSize.height); + } }); } // Block the _editingTransactionQueue from executing a new edit transaction until layout is done & _editingNodes array is updated. dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); - + free(nodeBoundSizes); // Insert finished nodes into data storage [self _insertNodes:nodes atIndexPaths:indexPaths withAnimationOptions:animationOptions]; } @@ -347,6 +344,10 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ ASDisplayNodePerformBlockOnMainThread(^{ + // Deep copy _completedNodes to _externalCompletedNodes. + // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. + _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes); + LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); [_delegate dataControllerBeginUpdates:self]; }); @@ -363,6 +364,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_editingTransactionQueue addOperationWithBlock:^{ ASDisplayNodePerformBlockOnMainThread(^{ + // Now that the transaction is done, _completedNodes can be accessed externally again. + _externalCompletedNodes = nil; + LOG(@"endUpdatesWithCompletion - calling delegate end"); [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; }); @@ -617,31 +621,51 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSUInteger)numberOfSections { ASDisplayNodeAssertMainThread(); - return [_completedNodes count]; + return [[self completedNodes] count]; } - (NSUInteger)numberOfRowsInSection:(NSUInteger)section { ASDisplayNodeAssertMainThread(); - return [_completedNodes[section] count]; + return [[self completedNodes][section] count]; } - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); - return _completedNodes[indexPath.section][indexPath.row]; + return [self completedNodes][indexPath.section][indexPath.row]; +} + +- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; +{ + ASDisplayNodeAssertMainThread(); + + NSArray *nodes = [self completedNodes]; + NSUInteger numberOfNodes = nodes.count; + + // Loop through each section to look for the cellNode + for (NSUInteger i = 0; i < numberOfNodes; i++) { + NSArray *sectionNodes = nodes[i]; + NSUInteger cellIndex = [sectionNodes indexOfObjectIdenticalTo:cellNode]; + if (cellIndex != NSNotFound) { + return [NSIndexPath indexPathForRow:cellIndex inSection:i]; + } + } + + return nil; } - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); - return ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes, [indexPaths sortedArrayUsingSelector:@selector(compare:)]); + return ASFindElementsInMultidimensionalArrayAtIndexPaths((NSMutableArray *)[self completedNodes], [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } +/// Returns nodes that can be queried externally. _externalCompletedNodes is used if available, _completedNodes otherwise. - (NSArray *)completedNodes { ASDisplayNodeAssertMainThread(); - return _completedNodes; + return _externalCompletedNodes != nil ? _externalCompletedNodes : _completedNodes; } #pragma mark - Dealloc diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h new file mode 100644 index 0000000000..7630fe2e84 --- /dev/null +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h @@ -0,0 +1,66 @@ +// +// ASPhotosFrameworkImageRequest.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 9/25/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import +#import + +// NS_ASSUME_NONNULL_BEGIN + +extern NSString *const ASPhotosURLScheme; + +/** + @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from + the Photos framework and store it in a URL. + */ +@interface ASPhotosFrameworkImageRequest : NSObject + +- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; + +/** + @return A new image request deserialized from `url`, or nil if `url` is not a valid photos URL. + */ ++ (/*nullable*/ ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url; + +/** + @abstract The asset identifier for this image request provided during initialization. + */ +@property (nonatomic, readonly) NSString *assetIdentifier; + +/** + @abstract The target size for this image request. Defaults to `PHImageManagerMaximumSize`. + */ +@property (nonatomic) CGSize targetSize; + +/** + @abstract The content mode for this image request. Defaults to `PHImageContentModeDefault`. + + @see `PHImageManager` + */ +@property (nonatomic) PHImageContentMode contentMode; + +/** + @abstract The options specified for this request. Default value is the result of `[PHImageRequestOptions new]`. + + @discussion Some properties of this object are ignored when converting this request into a URL. + As of iOS SDK 9.0, these properties are `progressHandler` and `synchronous`. + */ +@property (nonatomic, strong) PHImageRequestOptions *options; + +/** + @return A new URL converted from this request. + */ +@property (nonatomic, readonly) NSURL *url; + +/** + @return `YES` if `object` is an equivalent image request, `NO` otherwise. + */ +- (BOOL)isEqual:(id)object; + +@end + +// NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m new file mode 100644 index 0000000000..d46b3791c1 --- /dev/null +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m @@ -0,0 +1,161 @@ +// +// ASPhotosFrameworkImageRequest.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 9/25/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ASPhotosFrameworkImageRequest.h" +#import "ASBaseDefines.h" +#import "ASAvailability.h" + +NSString *const ASPhotosURLScheme = @"ph"; + +static NSString *const _ASPhotosURLQueryKeyWidth = @"width"; +static NSString *const _ASPhotosURLQueryKeyHeight = @"height"; + +// value is PHImageContentMode value +static NSString *const _ASPhotosURLQueryKeyContentMode = @"contentmode"; + +// value is PHImageRequestOptionsResizeMode value +static NSString *const _ASPhotosURLQueryKeyResizeMode = @"resizemode"; + +// value is PHImageRequestOptionsDeliveryMode value +static NSString *const _ASPhotosURLQueryKeyDeliveryMode = @"deliverymode"; + +// value is PHImageRequestOptionsVersion value +static NSString *const _ASPhotosURLQueryKeyVersion = @"version"; + +// value is 0 or 1 +static NSString *const _ASPhotosURLQueryKeyAllowNetworkAccess = @"network"; + +static NSString *const _ASPhotosURLQueryKeyCropOriginX = @"crop_x"; +static NSString *const _ASPhotosURLQueryKeyCropOriginY = @"crop_y"; +static NSString *const _ASPhotosURLQueryKeyCropWidth = @"crop_w"; +static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; + +@implementation ASPhotosFrameworkImageRequest + +- (instancetype)init +{ + ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); + self = [self initWithAssetIdentifier:@""]; + return nil; +} + +- (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier +{ + self = [super init]; + if (self) { + _assetIdentifier = assetIdentifier; + _options = [PHImageRequestOptions new]; + _contentMode = PHImageContentModeDefault; + _targetSize = PHImageManagerMaximumSize; + } + return self; +} + +#pragma mark NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + ASPhotosFrameworkImageRequest *copy = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:self.assetIdentifier]; + copy.options = [self.options copy]; + copy.targetSize = self.targetSize; + copy.contentMode = self.contentMode; + return copy; +} + +#pragma mark Converting to URL + +- (NSURL *)url +{ + NSURLComponents *comp = [NSURLComponents new]; + comp.scheme = ASPhotosURLScheme; + comp.host = _assetIdentifier; + NSMutableArray *queryItems = [NSMutableArray arrayWithObjects: + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyWidth value:@(_targetSize.width).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyHeight value:@(_targetSize.height).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyVersion value:@(_options.version).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyContentMode value:@(_contentMode).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyAllowNetworkAccess value:@(_options.networkAccessAllowed).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyResizeMode value:@(_options.resizeMode).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyDeliveryMode value:@(_options.deliveryMode).stringValue] + , nil]; + + CGRect cropRect = _options.normalizedCropRect; + if (!CGRectIsEmpty(cropRect)) { + [queryItems addObjectsFromArray:@[ + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginX value:@(cropRect.origin.x).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropOriginY value:@(cropRect.origin.y).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropWidth value:@(cropRect.size.width).stringValue], + [NSURLQueryItem queryItemWithName:_ASPhotosURLQueryKeyCropHeight value:@(cropRect.size.height).stringValue] + ]]; + } + comp.queryItems = queryItems; + return comp.URL; +} + +#pragma mark Converting from URL + ++ (ASPhotosFrameworkImageRequest *)requestWithURL:(NSURL *)url +{ + // not a photos URL or iOS < 8 + if (![url.scheme isEqualToString:ASPhotosURLScheme] || !AS_AT_LEAST_IOS8) { + return nil; + } + + NSURLComponents *comp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; + + ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:url.host]; + + CGRect cropRect = CGRectZero; + CGSize targetSize = PHImageManagerMaximumSize; + for (NSURLQueryItem *item in comp.queryItems) { + if ([_ASPhotosURLQueryKeyAllowNetworkAccess isEqualToString:item.name]) { + request.options.networkAccessAllowed = item.value.boolValue; + } else if ([_ASPhotosURLQueryKeyWidth isEqualToString:item.name]) { + targetSize.width = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyHeight isEqualToString:item.name]) { + targetSize.height = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyContentMode isEqualToString:item.name]) { + request.contentMode = (PHImageContentMode)item.value.integerValue; + } else if ([_ASPhotosURLQueryKeyVersion isEqualToString:item.name]) { + request.options.version = (PHImageRequestOptionsVersion)item.value.integerValue; + } else if ([_ASPhotosURLQueryKeyCropOriginX isEqualToString:item.name]) { + cropRect.origin.x = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyCropOriginY isEqualToString:item.name]) { + cropRect.origin.y = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyCropWidth isEqualToString:item.name]) { + cropRect.size.width = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyCropHeight isEqualToString:item.name]) { + cropRect.size.height = item.value.doubleValue; + } else if ([_ASPhotosURLQueryKeyResizeMode isEqualToString:item.name]) { + request.options.resizeMode = (PHImageRequestOptionsResizeMode)item.value.integerValue; + } else if ([_ASPhotosURLQueryKeyDeliveryMode isEqualToString:item.name]) { + request.options.deliveryMode = (PHImageRequestOptionsDeliveryMode)item.value.integerValue; + } + } + request.targetSize = targetSize; + request.options.normalizedCropRect = cropRect; + return request; +} + +#pragma mark NSObject + +- (BOOL)isEqual:(id)object +{ + if (![object isKindOfClass:ASPhotosFrameworkImageRequest.class]) { + return NO; + } + ASPhotosFrameworkImageRequest *other = object; + return [other.assetIdentifier isEqualToString:self.assetIdentifier] && + other.contentMode == self.contentMode && + CGSizeEqualToSize(other.targetSize, self.targetSize) && + CGRectEqualToRect(other.options.normalizedCropRect, self.options.normalizedCropRect) && + other.options.resizeMode == self.options.resizeMode && + other.options.version == self.options.version; +} + +@end diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h index 1558766c40..ca792f9fec 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.h @@ -26,6 +26,7 @@ typedef NS_ENUM(NSUInteger, ASAsyncTransactionState) { ASAsyncTransactionStateOpen = 0, ASAsyncTransactionStateCommitted, ASAsyncTransactionStateCanceled, + ASAsyncTransactionStateComplete }; /** @@ -54,6 +55,13 @@ typedef NS_ENUM(NSUInteger, ASAsyncTransactionState) { - (id)initWithCallbackQueue:(dispatch_queue_t)callbackQueue completionBlock:(asyncdisplaykit_async_transaction_completion_block_t)completionBlock; +/** + @summary Block the main thread until the transaction is complete, including callbacks. + + @desc This must be called on the main thread. + */ +- (void)waitUntilComplete; + /** The dispatch queue that the completion blocks will be called on. */ diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m index 0457d7a9ed..37ba85dd53 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransaction.m @@ -7,7 +7,7 @@ */ #import "_ASAsyncTransaction.h" - +#import "_ASAsyncTransactionGroup.h" #import "ASAssert.h" @interface ASDisplayNodeAsyncTransactionOperation : NSObject @@ -40,6 +40,11 @@ } } +- (NSString *)description +{ + return [NSString stringWithFormat:@"", self, (unsigned long)_state, _group, _operations]; +} + @end diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h index 235e98a1cf..c7b3da3217 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.h @@ -15,6 +15,7 @@ @interface _ASAsyncTransactionGroup : NSObject /// The main transaction group is scheduled to commit on every tick of the main runloop. + (instancetype)mainTransactionGroup; ++ (void)commit; /// Add a transaction container to be committed. /// @param containerLayer A layer containing a transaction to be commited. May or may not be a container layer. diff --git a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m index c78a42d047..afebd4e5cc 100644 --- a/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m +++ b/AsyncDisplayKit/Details/Transactions/_ASAsyncTransactionGroup.m @@ -95,6 +95,11 @@ static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observ } } ++ (void)commit +{ + [[_ASAsyncTransactionGroup mainTransactionGroup] commit]; +} + @end static void _transactionGroupRunLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) diff --git a/AsyncDisplayKit/Layout/ASLayoutOptions.mm b/AsyncDisplayKit/Layout/ASLayoutOptions.mm index e96f955c21..a40eea2bd5 100644 --- a/AsyncDisplayKit/Layout/ASLayoutOptions.mm +++ b/AsyncDisplayKit/Layout/ASLayoutOptions.mm @@ -98,6 +98,7 @@ static Class gDefaultLayoutOptionsClass = nil; self.spacingBefore = layoutOptions.spacingBefore; self.flexGrow = layoutOptions.flexGrow; self.flexShrink = layoutOptions.flexShrink; + self.alignSelf = layoutOptions.alignSelf; self.ascender = layoutOptions.ascender; self.descender = layoutOptions.descender; diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm index 8e8435c9cc..7652247f9b 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStaticLayoutSpec.mm @@ -58,18 +58,14 @@ [sublayouts addObject:sublayout]; } - if (isnan(size.width) || size.width >= FLT_MAX - FLT_EPSILON) { - size.width = constrainedSize.min.width; - for (ASLayout *sublayout in sublayouts) { - size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); - } + size.width = constrainedSize.min.width; + for (ASLayout *sublayout in sublayouts) { + size.width = MAX(size.width, sublayout.position.x + sublayout.size.width); } - if (isnan(size.height) || size.height >= FLT_MAX - FLT_EPSILON) { - size.height = constrainedSize.min.height; - for (ASLayout *sublayout in sublayouts) { - size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); - } + size.height = constrainedSize.min.height; + for (ASLayout *sublayout in sublayouts) { + size.height = MAX(size.height, sublayout.position.y + sublayout.size.height); } return [ASLayout layoutWithLayoutableObject:self diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index e6551d7a26..98b1c2b6e6 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -94,6 +94,8 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, // Capture these outside the display block so they are retained. UIColor *backgroundColor = self.backgroundColor; CGRect bounds = self.bounds; + CGFloat cornerRadius = self.cornerRadius; + BOOL clipsToBounds = self.clipsToBounds; CGRect frame; @@ -129,7 +131,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, CGContextTranslateCTM(context, frame.origin.x, frame.origin.y); //support cornerRadius - if (rasterizingFromAscendent && self.cornerRadius && self.clipsToBounds) { + if (rasterizingFromAscendent && cornerRadius && clipsToBounds) { [[UIBezierPath bezierPathWithRoundedRect:bounds cornerRadius:self.cornerRadius] addClip]; } @@ -308,16 +310,14 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, return BOOL(displaySentinelValue != displaySentinel.value); }; - // If we're participating in an ancestor's asyncTransaction, find it here - ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil"); - CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ?: _layer; - _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction; - // Set up displayBlock to call either display or draw on the delegate and return a UIImage contents asyncdisplaykit_async_transaction_operation_block_t displayBlock = [self _displayBlockWithAsynchronous:asynchronously isCancelledBlock:isCancelledBlock rasterizing:NO]; + if (!displayBlock) { return; } + + ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil"); // This block is called back on the main thread after rendering at the completion of the current async transaction, or immediately if !asynchronously asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id value, BOOL canceled){ @@ -335,16 +335,27 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, } }; - if (displayBlock != NULL) { - // Call willDisplay immediately in either case - [self willDisplayAsyncLayer:self.asyncLayer]; + // Call willDisplay immediately in either case + [self willDisplayAsyncLayer:self.asyncLayer]; - if (asynchronously) { - [transaction addOperationWithBlock:displayBlock queue:[_ASDisplayLayer displayQueue] completion:completionBlock]; - } else { - UIImage *contents = (UIImage *)displayBlock(); - completionBlock(contents, NO); - } + if (asynchronously) { + // Async rendering operations are contained by a transaction, which allows them to proceed and concurrently + // while synchronizing the final application of the results to the layer's contents property (completionBlock). + + // First, look to see if we are expected to join a parent's transaction container. + CALayer *containerLayer = _layer.asyncdisplaykit_parentTransactionContainer ?: _layer; + + // In the case that a transaction does not yet exist (such as for an individual node outside of a container), + // this call will allocate the transaction and add it to _ASAsyncTransactionGroup. + // It will automatically commit the transaction at the end of the runloop. + _ASAsyncTransaction *transaction = containerLayer.asyncdisplaykit_asyncTransaction; + + // Adding this displayBlock operation to the transaction will start it IMMEDIATELY. + // The only function of the transaction commit is to gate the calling of the completionBlock. + [transaction addOperationWithBlock:displayBlock queue:[_ASDisplayLayer displayQueue] completion:completionBlock]; + } else { + UIImage *contents = (UIImage *)displayBlock(); + completionBlock(contents, NO); } } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index ef013a97c2..977c98ec40 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -60,7 +60,7 @@ #define _messageToLayer(layerSelector) __loaded ? [_layer layerSelector] : [self.pendingViewState layerSelector] /** - * This category implements certainly frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, + * This category implements certain frequently-used properties and methods of UIView and CALayer so that ASDisplayNode clients can just call the view/layer methods on the node, * with minimal loss in performance. Unlike UIView and CALayer methods, these can be called from a non-main thread until the view or layer is created. * This allows text sizing in -calculateSizeThatFits: (essentially a simplified layout) to happen off the main thread * without any CALayer or UIView actually existing while still being able to set and read properties from ASDisplayNode instances. @@ -122,7 +122,7 @@ // Frame is only defined when transform is identity. #if DEBUG // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform"); + ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode frame] - self.transform must be identity in order to use the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif CGPoint position = self.position; @@ -140,7 +140,7 @@ // Frame is only defined when transform is identity because we explicitly diverge from CALayer behavior and define frame without transform #if DEBUG // Checking if the transform is identity is expensive, so disable when unnecessary. We have assertions on in Release, so DEBUG is the only way I know of. - ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"Must be an identity transform"); + ASDisplayNodeAssert(CATransform3DIsIdentity(self.transform), @"-[ASDisplayNode setFrame:] - self.transform must be identity in order to set the frame property. (From Apple's UIView documentation: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.)"); #endif BOOL useLayer = (_layer && ASDisplayNodeThreadIsMain()); diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 7066159c68..8283438272 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -80,6 +80,7 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { unsigned layerBacked:1; unsigned displaysAsynchronously:1; unsigned shouldRasterizeDescendants:1; + unsigned shouldBypassEnsureDisplay:1; unsigned displaySuspended:1; // whether custom drawing is enabled diff --git a/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m new file mode 100644 index 0000000000..d6c641bd8a --- /dev/null +++ b/AsyncDisplayKitTests/ASPhotosFrameworkImageRequestTests.m @@ -0,0 +1,60 @@ +// +// ASPhotosFrameworkImageRequestTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 9/25/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import +#import "ASPhotosFrameworkImageRequest.h" + +static NSString *const kTestAssetID = @"testAssetID"; + +@interface ASPhotosFrameworkImageRequestTests : XCTestCase + +@end + +@implementation ASPhotosFrameworkImageRequestTests + +#pragma mark Example Data + ++ (ASPhotosFrameworkImageRequest *)exampleImageRequest +{ + ASPhotosFrameworkImageRequest *req = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:kTestAssetID]; + req.options.networkAccessAllowed = YES; + req.options.normalizedCropRect = CGRectMake(0.2, 0.1, 0.6, 0.8); + req.targetSize = CGSizeMake(1024, 1536); + req.contentMode = PHImageContentModeAspectFill; + req.options.version = PHImageRequestOptionsVersionOriginal; + req.options.resizeMode = PHImageRequestOptionsResizeModeFast; + return req; +} + ++ (NSURL *)urlForExampleImageRequest +{ + NSString *str = [NSString stringWithFormat:@"ph://%@?width=1024&height=1536&version=2&contentmode=1&network=1&resizemode=1&deliverymode=0&crop_x=0.2&crop_y=0.1&crop_w=0.6&crop_h=0.8", kTestAssetID]; + return [NSURL URLWithString:str]; +} + +#pragma mark Test cases + +- (void)testThatConvertingToURLWorks +{ + XCTAssertEqualObjects([self.class exampleImageRequest].url, [self.class urlForExampleImageRequest]); +} + +- (void)testThatParsingFromURLWorks +{ + NSURL *url = [self.class urlForExampleImageRequest]; + XCTAssertEqualObjects([ASPhotosFrameworkImageRequest requestWithURL:url], [self.class exampleImageRequest]); +} + +- (void)testThatCopyingWorks +{ + ASPhotosFrameworkImageRequest *example = [self.class exampleImageRequest]; + ASPhotosFrameworkImageRequest *copy = [[self.class exampleImageRequest] copy]; + XCTAssertEqualObjects(example, copy); +} + +@end diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index bdba88cf19..a0d2a413b7 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -428,4 +428,27 @@ }]; } +- (void)testIndexPathForNode +{ + CGSize tableViewSize = CGSizeMake(100, 500); + ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectMake(0, 0, tableViewSize.width, tableViewSize.height) + style:UITableViewStylePlain + asyncDataFetching:YES]; + ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; + + tableView.asyncDelegate = dataSource; + tableView.asyncDataSource = dataSource; + + [tableView reloadDataWithCompletion:^{ + for (NSUInteger i = 0; i < NumberOfSections; i++) { + for (NSUInteger j = 0; j < NumberOfRowsPerSection; j++) { + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:j inSection:i]; + ASCellNode *cellNode = [tableView nodeForRowAtIndexPath:indexPath]; + NSIndexPath *reportedIndexPath = [tableView indexPathForNode:cellNode]; + XCTAssertEqual(indexPath.row, reportedIndexPath.row); + } + } + }]; +} + @end diff --git a/ObjectiveC.gcno b/ObjectiveC.gcno new file mode 100644 index 0000000000..05bc771aa7 Binary files /dev/null and b/ObjectiveC.gcno differ diff --git a/Podfile.lock b/Podfile.lock index 423c2d2851..cf8e89d790 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -10,4 +10,4 @@ SPEC CHECKSUMS: FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 -COCOAPODS: 0.37.2 +COCOAPODS: 0.38.2 diff --git a/QuartzCore.gcno b/QuartzCore.gcno new file mode 100644 index 0000000000..05bc771aa7 Binary files /dev/null and b/QuartzCore.gcno differ diff --git a/build.sh b/build.sh index 2c0bc6dab5..935a0a9b2d 100755 --- a/build.sh +++ b/build.sh @@ -60,4 +60,17 @@ if [ "$MODE" = "life-without-cocoapods" ]; then exit 0 fi +if [ "$MODE" = "framework" ]; then + echo "Verifying that AsyncDisplayKit functions as a dynamic framework (for Swift/Carthage users)." + + xctool \ + -project "smoke-tests/Framework/Sample.xcodeproj" \ + -scheme Sample \ + -sdk "$SDK" \ + -destination "$PLATFORM" \ + build + trap - EXIT + exit 0 +fi + echo "Unrecognised mode '$MODE'." diff --git a/examples/ASTableViewStressTest/Sample/ViewController.m b/examples/ASTableViewStressTest/Sample/ViewController.m index a297d37c42..f082e565f0 100644 --- a/examples/ASTableViewStressTest/Sample/ViewController.m +++ b/examples/ASTableViewStressTest/Sample/ViewController.m @@ -100,9 +100,6 @@ - (void)thrashTableView { - _tableView.asyncDelegate = self; - _tableView.asyncDataSource = self; - [_tableView reloadData]; NSArray *indexPathsAddedAndRemoved = nil; diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index d1f49351a2..847a2629c7 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -38,7 +38,7 @@ static const CGFloat kInnerPadding = 10.0f; @implementation KittenNode -// lorem ipsum text courtesy http://kittyipsum.com/ <3 +// lorem ipsum text courtesy https://kittyipsum.com/ <3 + (NSArray *)placeholders { static NSArray *placeholders = nil; @@ -82,7 +82,7 @@ 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:@"http://placekitten.com/%zd/%zd", + _imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"https://placekitten.com/%zd/%zd", (NSInteger)roundl(_kittenSize.width), (NSInteger)roundl(_kittenSize.height)]]; // _imageNode.contentMode = UIViewContentModeCenter; diff --git a/examples/SynchronousConcurrency/Default-568h@2x.png b/examples/SynchronousConcurrency/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/SynchronousConcurrency/Default-568h@2x.png differ diff --git a/examples/SynchronousConcurrency/Default-667h@2x.png b/examples/SynchronousConcurrency/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/SynchronousConcurrency/Default-667h@2x.png differ diff --git a/examples/SynchronousConcurrency/Default-736h@3x.png b/examples/SynchronousConcurrency/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/SynchronousConcurrency/Default-736h@3x.png differ diff --git a/examples/SynchronousConcurrency/Podfile b/examples/SynchronousConcurrency/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/SynchronousConcurrency/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj b/examples/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..78472428dc --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,361 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* AsyncTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */; }; + 18748FDB1BB727B20053A9C1 /* AsyncViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */; settings = {ASSET_TAGS = (); }; }; + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */; }; + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* AsyncTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncTableViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AsyncTableViewController.m; sourceTree = ""; }; + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 18748FD91BB727B20053A9C1 /* AsyncViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncViewController.h; sourceTree = ""; }; + 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncViewController.m; sourceTree = ""; }; + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomCoreGraphicsNode.h; sourceTree = ""; }; + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomCoreGraphicsNode.m; sourceTree = ""; }; + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* AsyncTableViewController.h */, + 05E2128C19D4DB510098F589 /* AsyncTableViewController.m */, + 18748FD91BB727B20053A9C1 /* AsyncViewController.h */, + 18748FDA1BB727B20053A9C1 /* AsyncViewController.m */, + 18C2ED841B9B8CE700F627B3 /* RandomCoreGraphicsNode.h */, + 18C2ED851B9B8CE700F627B3 /* RandomCoreGraphicsNode.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */, + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 18C2ED861B9B8CE700F627B3 /* RandomCoreGraphicsNode.m in Sources */, + 18748FDB1BB727B20053A9C1 /* AsyncViewController.m in Sources */, + 05E2128D19D4DB510098F589 /* AsyncTableViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/SynchronousConcurrency/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/SynchronousConcurrency/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/SynchronousConcurrency/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/SynchronousConcurrency/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..1e14aa0329 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata b/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..d98549fd35 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/SynchronousConcurrency/Sample/AppDelegate.h b/examples/SynchronousConcurrency/Sample/AppDelegate.h new file mode 100644 index 0000000000..85855277e9 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AppDelegate.h @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#define UseAutomaticLayout 1 + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/SynchronousConcurrency/Sample/AppDelegate.m b/examples/SynchronousConcurrency/Sample/AppDelegate.m new file mode 100644 index 0000000000..a99a682823 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AppDelegate.m @@ -0,0 +1,33 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "AppDelegate.h" + +#import "AsyncTableViewController.h" +#import "AsyncViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + + UITabBarController *tabBarController = [[UITabBarController alloc] initWithNibName:nil bundle:nil]; + self.window.rootViewController = tabBarController; + + [tabBarController setViewControllers:@[[[AsyncTableViewController alloc] init], [[AsyncViewController alloc] init]]]; + + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncTableViewController.h b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.h new file mode 100644 index 0000000000..a1edcd7f6a --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.h @@ -0,0 +1,16 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface AsyncTableViewController : UIViewController + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m new file mode 100644 index 0000000000..090805234c --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncTableViewController.m @@ -0,0 +1,91 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import +#import + +#import "AsyncTableViewController.h" +#import "RandomCoreGraphicsNode.h" + +@interface AsyncTableViewController () +{ + ASTableView *_tableView; +} + +@end + +@implementation AsyncTableViewController + +#pragma mark - +#pragma mark UIViewController. + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableView.asyncDataSource = self; + _tableView.asyncDelegate = self; + + ASRangeTuningParameters tuningParameters; + tuningParameters.leadingBufferScreenfuls = 0.5; + tuningParameters.trailingBufferScreenfuls = 1.0; + [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypePreload]; + [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; + + self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFeatured tag:0]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo + target:self + action:@selector(reloadEverything)]; + + return self; +} + +- (void)reloadEverything +{ + [_tableView reloadData]; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubview:_tableView]; +} + +- (void)viewWillLayoutSubviews +{ + _tableView.frame = self.view.bounds; +} + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +#pragma mark - +#pragma mark ASTableView. + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init]; + elementNode.preferredFrameSize = CGSizeMake(320, 100); + return elementNode; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return 100; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncViewController.h b/examples/SynchronousConcurrency/Sample/AsyncViewController.h new file mode 100644 index 0000000000..b449abbb2f --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncViewController.h @@ -0,0 +1,13 @@ +// +// AsyncViewController.h +// Sample +// +// Created by Scott Goodson on 9/26/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ASViewController.h" + +@interface AsyncViewController : ASViewController + +@end diff --git a/examples/SynchronousConcurrency/Sample/AsyncViewController.m b/examples/SynchronousConcurrency/Sample/AsyncViewController.m new file mode 100644 index 0000000000..16e8fdb017 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/AsyncViewController.m @@ -0,0 +1,37 @@ +// +// AsyncViewController.m +// Sample +// +// Created by Scott Goodson on 9/26/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "AsyncViewController.h" +#import "RandomCoreGraphicsNode.h" + +@implementation AsyncViewController + +- (instancetype)init +{ + if (!(self = [super initWithNode:[[RandomCoreGraphicsNode alloc] init]])) { + return nil; + } + + self.neverShowPlaceholders = YES; + self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFavorites tag:0]; + return self; +} + +- (void)viewWillAppear:(BOOL)animated +{ + // FIXME: This is only being called on the first time the UITabBarController shows us. + [super viewWillAppear:animated]; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [self.node recursivelyClearContents]; + [super viewDidDisappear:animated]; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/Info.plist b/examples/SynchronousConcurrency/Sample/Info.plist new file mode 100644 index 0000000000..35d842827b --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.h b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.h new file mode 100644 index 0000000000..cecc3446b9 --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.h @@ -0,0 +1,16 @@ +// +// RandomCoreGraphicsNode.h +// Sample +// +// Created by Scott Goodson on 9/5/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import + +@interface RandomCoreGraphicsNode : ASCellNode +{ + ASTextNode *_textNode; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m new file mode 100644 index 0000000000..046a71e22e --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m @@ -0,0 +1,93 @@ +// +// RandomCoreGraphicsNode.m +// Sample +// +// Created by Scott Goodson on 9/5/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "RandomCoreGraphicsNode.h" +#import + +@implementation RandomCoreGraphicsNode + ++ (UIColor *)randomColor +{ + CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0 + CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white + CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black + return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1]; +} + ++ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing +{ + CGFloat locations[3]; + NSMutableArray *colors = [NSMutableArray arrayWithCapacity:3]; + [colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]]; + locations[0] = 0.0; + [colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]]; + locations[1] = 1.0; + [colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]]; + locations[2] = ( arc4random() % 256 / 256.0 ); + + + CGContextRef ctx = UIGraphicsGetCurrentContext(); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations); + + CGGradientDrawingOptions drawingOptions; + CGContextDrawLinearGradient(ctx, gradient, CGPointZero, CGPointMake(bounds.size.width, bounds.size.height), drawingOptions); + + CGColorSpaceRelease(colorSpace); +} + +- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer +{ + return [self description]; +} + +- (NSDictionary *)textStyle +{ + UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:36.0f]; + + NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + style.paragraphSpacing = 0.5 * font.lineHeight; + style.hyphenationFactor = 1.0; + + return @{ NSFontAttributeName: font, + NSParagraphStyleAttributeName: style }; +} + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _textNode = [[ASTextNode alloc] init]; + _textNode.placeholderEnabled = NO; + _textNode.attributedString = [[NSAttributedString alloc] initWithString:@"Hello, ASDK!" + attributes:[self textStyle]]; + [self addSubnode:_textNode]; + + return self; +} + +- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize +{ + [_textNode measure:constrainedSize]; + return CGSizeMake(constrainedSize.width, 100); +} + +- (void)layout +{ + CGSize boundsSize = self.bounds.size; + CGSize textSize = _textNode.calculatedSize; + CGRect textRect = CGRectMake(roundf((boundsSize.width - textSize.width) / 2.0), + roundf((boundsSize.height - textSize.height) / 2.0), + textSize.width, + textSize.height); + _textNode.frame = textRect; +} + +@end diff --git a/examples/SynchronousConcurrency/Sample/main.m b/examples/SynchronousConcurrency/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/SynchronousConcurrency/Sample/main.m @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/smoke-tests/Framework/Default-568h@2x.png b/smoke-tests/Framework/Default-568h@2x.png new file mode 100644 index 0000000000..1547a98454 Binary files /dev/null and b/smoke-tests/Framework/Default-568h@2x.png differ diff --git a/smoke-tests/Framework/Default-667h@2x.png b/smoke-tests/Framework/Default-667h@2x.png new file mode 100644 index 0000000000..988ea56bab Binary files /dev/null and b/smoke-tests/Framework/Default-667h@2x.png differ diff --git a/smoke-tests/Framework/Default-736h@3x.png b/smoke-tests/Framework/Default-736h@3x.png new file mode 100644 index 0000000000..d19eb325a2 Binary files /dev/null and b/smoke-tests/Framework/Default-736h@3x.png differ diff --git a/smoke-tests/Framework/README.md b/smoke-tests/Framework/README.md new file mode 100644 index 0000000000..cce2cb22fb --- /dev/null +++ b/smoke-tests/Framework/README.md @@ -0,0 +1,11 @@ +# "Framework" + +This is a very simple pseudo-"integration test" project that links against +AsyncDisplayKit as a dynamic framework, for Swift/Carthage users. + +If it fails to compile, Travis CI builds will fail. To escape from such dire straits: + +* If you've added a new class intended for public use, make sure you added its + header to the "Public" group of the "Headers" build phase in the + AsyncDisplayKit-iOS framework target. Note that this smoke test will only fail + if you remembered to add your new file to the umbrella helper. diff --git a/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..f07199694d --- /dev/null +++ b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,410 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 050E7C7419D22E19004363C2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050E7C7319D22E19004363C2 /* AppDelegate.swift */; }; + 050E7C7619D22E19004363C2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 050E7C7519D22E19004363C2 /* ViewController.swift */; }; + 05DDD8DB19D2336300013C30 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */; }; + 34566CAA1BC1204100715E6B /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */; }; + 34566CAB1BC1204100715E6B /* AsyncDisplayKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 6C5053DB19EE266A00E385DE /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053D919EE266A00E385DE /* Default-667h@2x.png */; }; + 6C5053DC19EE266A00E385DE /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 34566CA21BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09AC195D04C000B7D73C; + remoteInfo = AsyncDisplayKit; + }; + 34566CA41BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09BC195D04C000B7D73C; + remoteInfo = AsyncDisplayKitTests; + }; + 34566CA61BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; + remoteInfo = AsyncDisplayKitTestHost; + }; + 34566CA81BC1202A00715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B35061DA1B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; + 34566CAC1BC1204100715E6B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B35061D91B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 34566CAE1BC1204100715E6B /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 34566CAB1BC1204100715E6B /* AsyncDisplayKit.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 050E7C6E19D22E19004363C2 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 050E7C7219D22E19004363C2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 050E7C7319D22E19004363C2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 050E7C7519D22E19004363C2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AsyncDisplayKit.xcodeproj; path = ../../AsyncDisplayKit.xcodeproj; sourceTree = ""; }; + 34566CAF1BC1208200715E6B /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; + 34566CB11BC1208700715E6B /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; + 6C5053D919EE266A00E385DE /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 050E7C6B19D22E19004363C2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 34566CAA1BC1204100715E6B /* AsyncDisplayKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 050E7C6519D22E19004363C2 = { + isa = PBXGroup; + children = ( + 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */, + 050E7C7019D22E19004363C2 /* Sample */, + 050E7C6F19D22E19004363C2 /* Products */, + 092C2001FE124604891D6E90 /* Frameworks */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 050E7C6F19D22E19004363C2 /* Products */ = { + isa = PBXGroup; + children = ( + 050E7C6E19D22E19004363C2 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 050E7C7019D22E19004363C2 /* Sample */ = { + isa = PBXGroup; + children = ( + 050E7C7319D22E19004363C2 /* AppDelegate.swift */, + 050E7C7519D22E19004363C2 /* ViewController.swift */, + 050E7C7119D22E19004363C2 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 050E7C7119D22E19004363C2 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 050E7C7219D22E19004363C2 /* Info.plist */, + 05DDD8DA19D2336300013C30 /* Default-568h@2x.png */, + 6C5053D919EE266A00E385DE /* Default-667h@2x.png */, + 6C5053DA19EE266A00E385DE /* Default-736h@3x.png */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 092C2001FE124604891D6E90 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 34566CB11BC1208700715E6B /* AssetsLibrary.framework */, + 34566CAF1BC1208200715E6B /* Photos.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 34566C9C1BC1202A00715E6B /* Products */ = { + isa = PBXGroup; + children = ( + 34566CA31BC1202A00715E6B /* libAsyncDisplayKit.a */, + 34566CA51BC1202A00715E6B /* AsyncDisplayKitTests.xctest */, + 34566CA71BC1202A00715E6B /* AsyncDisplayKitTestHost.app */, + 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 050E7C6D19D22E19004363C2 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 050E7C8D19D22E1A004363C2 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 050E7C6A19D22E19004363C2 /* Sources */, + 050E7C6B19D22E19004363C2 /* Frameworks */, + 050E7C6C19D22E19004363C2 /* Resources */, + 34566CAE1BC1204100715E6B /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 34566CAD1BC1204100715E6B /* PBXTargetDependency */, + ); + name = Sample; + productName = Sample; + productReference = 050E7C6E19D22E19004363C2 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 050E7C6619D22E19004363C2 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 050E7C6D19D22E19004363C2 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 050E7C6919D22E19004363C2 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 050E7C6519D22E19004363C2; + productRefGroup = 050E7C6F19D22E19004363C2 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 34566C9C1BC1202A00715E6B /* Products */; + ProjectRef = 34566C9B1BC1202A00715E6B /* AsyncDisplayKit.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 050E7C6D19D22E19004363C2 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 34566CA31BC1202A00715E6B /* libAsyncDisplayKit.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libAsyncDisplayKit.a; + remoteRef = 34566CA21BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 34566CA51BC1202A00715E6B /* AsyncDisplayKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = AsyncDisplayKitTests.xctest; + remoteRef = 34566CA41BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 34566CA71BC1202A00715E6B /* AsyncDisplayKitTestHost.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = AsyncDisplayKitTestHost.app; + remoteRef = 34566CA61BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 34566CA91BC1202A00715E6B /* AsyncDisplayKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = AsyncDisplayKit.framework; + remoteRef = 34566CA81BC1202A00715E6B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 050E7C6C19D22E19004363C2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05DDD8DB19D2336300013C30 /* Default-568h@2x.png in Resources */, + 6C5053DB19EE266A00E385DE /* Default-667h@2x.png in Resources */, + 6C5053DC19EE266A00E385DE /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 050E7C6A19D22E19004363C2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 050E7C7619D22E19004363C2 /* ViewController.swift in Sources */, + 050E7C7419D22E19004363C2 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 34566CAD1BC1204100715E6B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "AsyncDisplayKit-iOS"; + targetProxy = 34566CAC1BC1204100715E6B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 050E7C8B19D22E1A004363C2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 050E7C8C19D22E1A004363C2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 050E7C8E19D22E1A004363C2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = ""; + }; + name = Debug; + }; + 050E7C8F19D22E1A004363C2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 050E7C6919D22E19004363C2 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 050E7C8B19D22E1A004363C2 /* Debug */, + 050E7C8C19D22E1A004363C2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 050E7C8D19D22E1A004363C2 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 050E7C8E19D22E1A004363C2 /* Debug */, + 050E7C8F19D22E1A004363C2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 050E7C6619D22E19004363C2 /* Project object */; +} diff --git a/smoke-tests/Framework/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/smoke-tests/Framework/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/smoke-tests/Framework/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/smoke-tests/Framework/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/smoke-tests/Framework/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..3cb0b0bd7b --- /dev/null +++ b/smoke-tests/Framework/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/smoke-tests/Framework/Sample/AppDelegate.swift b/smoke-tests/Framework/Sample/AppDelegate.swift new file mode 100644 index 0000000000..a2b00b1727 --- /dev/null +++ b/smoke-tests/Framework/Sample/AppDelegate.swift @@ -0,0 +1,28 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + let window = UIWindow(frame: UIScreen.mainScreen().bounds) + window.backgroundColor = UIColor.whiteColor() + window.rootViewController = ViewController(nibName: nil, bundle: nil) + window.makeKeyAndVisible() + self.window = window + return true + } + +} diff --git a/smoke-tests/Framework/Sample/Info.plist b/smoke-tests/Framework/Sample/Info.plist new file mode 100644 index 0000000000..fb4115c84c --- /dev/null +++ b/smoke-tests/Framework/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/smoke-tests/Framework/Sample/ViewController.swift b/smoke-tests/Framework/Sample/ViewController.swift new file mode 100644 index 0000000000..43db9fb420 --- /dev/null +++ b/smoke-tests/Framework/Sample/ViewController.swift @@ -0,0 +1,67 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit +import AsyncDisplayKit + +class ViewController: UIViewController, ASTableViewDataSource, ASTableViewDelegate { + + var tableView: ASTableView + + + // MARK: UIViewController. + + override required init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + self.tableView = ASTableView() + + super.init(nibName: nil, bundle: nil) + + self.tableView.asyncDataSource = self + self.tableView.asyncDelegate = self + } + + required init(coder aDecoder: NSCoder) { + fatalError("storyboards are incompatible with truth and beauty") + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.addSubview(self.tableView) + } + + override func viewWillLayoutSubviews() { + self.tableView.frame = self.view.bounds + } + + override func prefersStatusBarHidden() -> Bool { + return true + } + + + // MARK: ASTableView data source and delegate. + + func tableView(tableView: ASTableView!, nodeForRowAtIndexPath indexPath: NSIndexPath!) -> ASCellNode! { + let patter = NSString(format: "[%ld.%ld] says hello!", indexPath.section, indexPath.row) + let node = ASTextCellNode() + node.text = patter as String + + return node + } + + func numberOfSectionsInTableView(tableView: UITableView!) -> Int { + return 1 + } + + func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { + return 20 + } + +}