From 024b8afef58ecdfc5ac76715f498ca914ec4ba14 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 9 Aug 2016 13:06:01 -0700 Subject: [PATCH 01/50] Move NS_ASSUME_NONNULL_END to the end of the ASDisplayNode header (#2046) --- AsyncDisplayKit/ASDisplayNode.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 005d9e2203..3dd0f2749c 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -615,7 +615,7 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END + /** * ## UIView bridge * @@ -781,3 +781,5 @@ NS_ASSUME_NONNULL_END @property (nonatomic, assign) BOOL placeholderFadesOut ASDISPLAYNODE_DEPRECATED; @end + +NS_ASSUME_NONNULL_END From 03c74452b21ef40553adddf5d2ebe0c772a30ec4 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Tue, 9 Aug 2016 13:45:19 -0700 Subject: [PATCH 02/50] [ASImageNode] fix scaling math (#2045) * fix ASImageNode scaling * convert to type-generic math --- AsyncDisplayKit/Private/ASImageNode+CGExtras.m | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m index b1912e6b2b..446ec03da3 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.m +++ b/AsyncDisplayKit/Private/ASImageNode+CGExtras.m @@ -9,6 +9,7 @@ // #import "ASImageNode+CGExtras.h" +#import // TODO rewrite these to be closer to the intended use -- take UIViewContentMode as param, CGRect destinationBounds, CGSize sourceSize. static CGSize _ASSizeFillWithAspectRatio(CGFloat aspectRatio, CGSize constraints); @@ -20,7 +21,7 @@ static CGSize _ASSizeFillWithAspectRatio(CGFloat sizeToScaleAspectRatio, CGSize if (sizeToScaleAspectRatio > destinationAspectRatio) { return CGSizeMake(destinationSize.height * sizeToScaleAspectRatio, destinationSize.height); } else { - return CGSizeMake(destinationSize.width, floorf(destinationSize.width / sizeToScaleAspectRatio)); + return CGSizeMake(destinationSize.width, round(destinationSize.width / sizeToScaleAspectRatio)); } } @@ -49,7 +50,7 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, // Often, an image is too low resolution to completely fill the width and height provided. // Per the API contract as commented in the header, we will adjust input parameters (destinationWidth, destinationHeight) to ensure that the image is not upscaled on the CPU. - CGFloat boundsAspectRatio = (float)destinationWidth / (float)destinationHeight; + CGFloat boundsAspectRatio = (CGFloat)destinationWidth / (CGFloat)destinationHeight; CGSize scaledSizeForImage = sourceImageSize; BOOL cropToRectDimensions = !CGRectIsEmpty(cropRect); @@ -66,8 +67,8 @@ void ASCroppedImageBackingSizeAndDrawRectInBounds(CGSize sourceImageSize, // If fitting the desired aspect ratio to the image size actually results in a larger buffer, use the input values. // However, if there is a pixel savings (e.g. we would have to upscale the image), overwrite the function arguments. if (forceUpscaling == NO && (scaledSizeForImage.width * scaledSizeForImage.height) < (destinationWidth * destinationHeight)) { - destinationWidth = (size_t)roundf(scaledSizeForImage.width); - destinationHeight = (size_t)roundf(scaledSizeForImage.height); + destinationWidth = (size_t)round(scaledSizeForImage.width); + destinationHeight = (size_t)round(scaledSizeForImage.height); if (destinationWidth == 0 || destinationHeight == 0) { *outBackingSize = CGSizeZero; *outDrawRect = CGRectZero; From d9db780b0bc06765f784aa01adc90fc582a12c48 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 9 Aug 2016 14:56:59 -0700 Subject: [PATCH 03/50] [ASTextNode] Fix ASTextNode shadow is not rendering (#2042) * Passing through shadow in renderer attribute * Fix memory leak setting shadow color --- AsyncDisplayKit/ASTextNode.mm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index b564caa488..150644e52e 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -44,6 +44,7 @@ struct ASTextNodeDrawParameter { @implementation ASTextNode { CGSize _shadowOffset; CGColorRef _shadowColor; + UIColor *_cachedShadowUIColor; CGFloat _shadowOpacity; CGFloat _shadowRadius; @@ -232,6 +233,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; .pointSizeScaleFactors = self.pointSizeScaleFactors, .layoutManagerCreationBlock = self.layoutManagerCreationBlock, .textStorageCreationBlock = self.textStorageCreationBlock, + .shadowOffset = _shadowOffset, + .shadowColor = _cachedShadowUIColor, + .shadowOpacity = _shadowOpacity, + .shadowRadius = _shadowRadius }; } @@ -1043,7 +1048,11 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI if (shadowColor != NULL) { CGColorRetain(shadowColor); } + if (_shadowColor != NULL) { + CGColorRelease(_shadowColor); + } _shadowColor = shadowColor; + _cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor]; [self _invalidateRenderer]; [self setNeedsDisplay]; } From aba05a747c9222de91de2a65e601393a7a284943 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 9 Aug 2016 15:20:31 -0700 Subject: [PATCH 04/50] [ASThread] Add SharedLocker and SharedUnlocker that uses a shared pointer for the mutex (#2047) * Add SharedLocker and SharedUnlocker that uses a shared pointer for the mutex * Move ASTextKitContext to use Shared Locker --- AsyncDisplayKit/Details/ASThread.h | 63 ++++++++++++++++++- AsyncDisplayKit/Private/ASLayoutTransition.mm | 24 ++++--- AsyncDisplayKit/TextKit/ASTextKitContext.mm | 18 +++--- 3 files changed, 86 insertions(+), 19 deletions(-) diff --git a/AsyncDisplayKit/Details/ASThread.h b/AsyncDisplayKit/Details/ASThread.h index c88437d4ae..73c17163f5 100644 --- a/AsyncDisplayKit/Details/ASThread.h +++ b/AsyncDisplayKit/Details/ASThread.h @@ -34,6 +34,8 @@ static inline BOOL ASDisplayNodeThreadIsMain() #import #endif +#include + /** For use with ASDN::StaticMutex only. */ @@ -53,7 +55,7 @@ static inline BOOL ASDisplayNodeThreadIsMain() namespace ASDN { - + template class Locker { @@ -98,17 +100,72 @@ namespace ASDN { }; + template + class SharedLocker + { + std::shared_ptr _l; + +#if TIME_LOCKER + CFTimeInterval _ti; + const char *_name; +#endif + + public: +#if !TIME_LOCKER + + SharedLocker (std::shared_ptr const& l) ASDISPLAYNODE_NOTHROW : _l (l) { + assert(_l != nullptr); + _l->lock (); + } + + ~SharedLocker () { + _l->unlock (); + } + + // non-copyable. + SharedLocker(const SharedLocker&) = delete; + SharedLocker &operator=(const SharedLocker&) = delete; + +#else + + SharedLocker (std::shared_ptr const& l, const char *name = NULL) ASDISPLAYNODE_NOTHROW : _l (l), _name(name) { + _ti = CACurrentMediaTime(); + _l->lock (); + } + + ~SharedLocker () { + _l->unlock (); + if (_name) { + printf(_name, NULL); + printf(" dt:%f\n", CACurrentMediaTime() - _ti); + } + } + +#endif + + }; template class Unlocker { T &_l; public: - Unlocker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) {_l.unlock ();} + Unlocker (T &l) ASDISPLAYNODE_NOTHROW : _l (l) { _l.unlock (); } ~Unlocker () {_l.lock ();} Unlocker(Unlocker&) = delete; Unlocker &operator=(Unlocker&) = delete; }; + + template + class SharedUnlocker + { + std::shared_ptr _l; + public: + SharedUnlocker (std::shared_ptr const& l) ASDISPLAYNODE_NOTHROW : _l (l) { _l->unlock (); } + ~SharedUnlocker () { _l->lock (); } + SharedUnlocker(SharedUnlocker&) = delete; + SharedUnlocker &operator=(SharedUnlocker&) = delete; + }; struct Mutex { @@ -164,7 +221,9 @@ namespace ASDN { }; typedef Locker MutexLocker; + typedef SharedLocker MutexSharedLocker; typedef Unlocker MutexUnlocker; + typedef SharedUnlocker MutexSharedUnlocker; /** If you are creating a static mutex, use StaticMutex and specify its default value as one of ASDISPLAYNODE_MUTEX_INITIALIZER diff --git a/AsyncDisplayKit/Private/ASLayoutTransition.mm b/AsyncDisplayKit/Private/ASLayoutTransition.mm index e169b6e142..52af19fad9 100644 --- a/AsyncDisplayKit/Private/ASLayoutTransition.mm +++ b/AsyncDisplayKit/Private/ASLayoutTransition.mm @@ -16,6 +16,7 @@ #import "ASLayout.h" #import +#import #import "NSArray+Diffing.h" #import "ASEqualityHelpers.h" @@ -47,7 +48,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { } @implementation ASLayoutTransition { - ASDN::RecursiveMutex __instanceLock__; + std::shared_ptr __instanceLock__; + BOOL _calculatedSubnodeOperations; NSArray *_insertedSubnodes; NSArray *_removedSubnodes; @@ -61,6 +63,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { { self = [super init]; if (self) { + __instanceLock__ = std::make_shared(); + _node = node; _pendingLayout = pendingLayout; _previousLayout = previousLayout; @@ -70,7 +74,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (BOOL)isSynchronous { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); return !ASLayoutCanTransitionAsynchronous(_pendingLayout); } @@ -82,7 +86,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (void)applySubnodeInsertions { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; NSUInteger i = 0; @@ -95,7 +99,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (void)applySubnodeRemovals { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; for (ASDisplayNode *subnode in _removedSubnodes) { [subnode removeFromSupernode]; @@ -104,7 +108,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (void)calculateSubnodeOperationsIfNeeded { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if (_calculatedSubnodeOperations) { return; } @@ -134,27 +138,27 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (NSArray *)currentSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); return _node.subnodes; } - (NSArray *)insertedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _insertedSubnodes; } - (NSArray *)removedSubnodesWithTransitionContext:(_ASTransitionContext *)context { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); [self calculateSubnodeOperationsIfNeeded]; return _removedSubnodes; } - (ASLayout *)transitionContext:(_ASTransitionContext *)context layoutForKey:(NSString *)key { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { @@ -166,7 +170,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) { - (ASSizeRange)transitionContext:(_ASTransitionContext *)context constrainedSizeForKey:(NSString *)key { - ASDN::MutexLocker l(__instanceLock__); + ASDN::MutexSharedLocker l(__instanceLock__); if ([key isEqualToString:ASTransitionContextFromLayoutKey]) { return _previousLayout.constrainedSizeRange; } else if ([key isEqualToString:ASTransitionContextToLayoutKey]) { diff --git a/AsyncDisplayKit/TextKit/ASTextKitContext.mm b/AsyncDisplayKit/TextKit/ASTextKitContext.mm index e1f65a0122..93958ce241 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitContext.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitContext.mm @@ -10,13 +10,14 @@ #import "ASTextKitContext.h" #import "ASLayoutManager.h" +#import "ASThread.h" -#import +#include @implementation ASTextKitContext { // All TextKit operations (even non-mutative ones) must be executed serially. - std::mutex _textKitMutex; + std::shared_ptr __instanceLock__; NSLayoutManager *_layoutManager; NSTextStorage *_textStorage; @@ -35,8 +36,11 @@ { if (self = [super init]) { // Concurrently initialising TextKit components crashes (rdar://18448377) so we use a global lock. - static std::mutex __static_mutex; - std::lock_guard l(__static_mutex); + static ASDN::Mutex __staticMutex; + ASDN::MutexLocker l(__staticMutex); + + __instanceLock__ = std::make_shared(); + // Create the TextKit component stack with our default configuration. if (textStorageCreationBlock) { _textStorage = textStorageCreationBlock(attributedString); @@ -60,13 +64,13 @@ - (CGSize)constrainedSize { - std::lock_guard l(_textKitMutex); + ASDN::MutexSharedLocker l(__instanceLock__); return _textContainer.size; } - (void)setConstrainedSize:(CGSize)constrainedSize { - std::lock_guard l(_textKitMutex); + ASDN::MutexSharedLocker l(__instanceLock__); _textContainer.size = constrainedSize; } @@ -74,7 +78,7 @@ NSTextStorage *, NSTextContainer *))block { - std::lock_guard l(_textKitMutex); + ASDN::MutexSharedLocker l(__instanceLock__); if (block) { block(_layoutManager, _textStorage, _textContainer); } From 893e601e813203eaa86daaa087c95c505d77acd5 Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Tue, 9 Aug 2016 15:31:37 -0700 Subject: [PATCH 05/50] Convert to type-generic math (#2050) * [Optimization] Convert to type-generic math * add std:: prefix in obj-c++ files * more cleanup * revert test changes * convert min and max back to fmin/fmax --- AsyncDisplayKit/ASEditableTextNode.mm | 7 ++++--- AsyncDisplayKit/ASImageNode+tvOS.m | 9 ++++++--- AsyncDisplayKit/ASImageNode.mm | 4 +++- AsyncDisplayKit/ASMapNode.mm | 15 +++++++++------ AsyncDisplayKit/ASTextNode.mm | 3 ++- .../Details/ASHighlightOverlayLayer.mm | 3 ++- AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m | 12 +++++++----- AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm | 3 ++- .../Private/ASStackPositionedLayout.mm | 8 +++++--- .../Private/ASStackUnpositionedLayout.mm | 7 ++++--- AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm | 4 ++-- .../TextKit/ASTextKitFontSizeAdjuster.mm | 13 ++++++++----- .../TextKit/ASTextKitRenderer+Positioning.mm | 3 ++- AsyncDisplayKit/TextKit/ASTextKitShadower.mm | 10 ++++++---- 14 files changed, 62 insertions(+), 39 deletions(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 573323fda6..b47dc7ca14 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -11,6 +11,7 @@ #import "ASEditableTextNode.h" #import +#import #import "ASDisplayNode+Subclasses.h" #import "ASEqualityHelpers.h" @@ -238,9 +239,9 @@ { ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; - CGFloat width = ceilf(textSize.width + _textContainerInset.left + _textContainerInset.right); - CGFloat height = ceilf(textSize.height + _textContainerInset.top + _textContainerInset.bottom); - return CGSizeMake(fminf(width, constrainedSize.width), fminf(height, constrainedSize.height)); + CGFloat width = std::ceil(textSize.width + _textContainerInset.left + _textContainerInset.right); + CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom); + return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height)); } - (void)layout diff --git a/AsyncDisplayKit/ASImageNode+tvOS.m b/AsyncDisplayKit/ASImageNode+tvOS.m index 26b7dd49b4..cc0af5de01 100644 --- a/AsyncDisplayKit/ASImageNode+tvOS.m +++ b/AsyncDisplayKit/ASImageNode+tvOS.m @@ -12,7 +12,10 @@ #if TARGET_OS_TV #import "ASImageNode+tvOS.h" + #import +#import + #import "ASDisplayNodeExtras.h" @implementation ASImageNode (tvOS) @@ -75,8 +78,8 @@ // BUT we apply our transforms to *view since we want to apply // the transforms to the root view (L: 107) CGPoint point = [touch locationInView:self.view]; - float pitch = 0; - float yaw = 0; + CGFloat pitch = 0; + CGFloat yaw = 0; BOOL topHalf = NO; if (point.y > CGRectGetHeight(self.view.frame)) { pitch = 15; @@ -100,7 +103,7 @@ if (yaw > 0) { yaw = -yaw; } else { - yaw = fabsf(yaw); + yaw = fabs(yaw); } } diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index 8a6304f024..fa73b6a38f 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -10,6 +10,8 @@ #import "ASImageNode.h" +#import + #import "_ASDisplayLayer.h" #import "ASAssert.h" #import "ASDisplayNode+Subclasses.h" @@ -313,7 +315,7 @@ struct ASImageNodeDrawParameters { CGSize imageSize = image.size; CGSize imageSizeInPixels = CGSizeMake(imageSize.width * image.scale, imageSize.height * image.scale); - CGSize boundsSizeInPixels = CGSizeMake(floorf(bounds.size.width * contentsScale), floorf(bounds.size.height * contentsScale)); + CGSize boundsSizeInPixels = CGSizeMake(std::floor(bounds.size.width * contentsScale), std::floor(bounds.size.height * contentsScale)); if (_debugLabelNode) { CGFloat pixelCountRatio = (imageSizeInPixels.width * imageSizeInPixels.height) / (boundsSizeInPixels.width * boundsSizeInPixels.height); diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index c0d7fb9a25..e334ab633a 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -10,6 +10,9 @@ #if TARGET_OS_IOS #import "ASMapNode.h" + +#import + #import "ASDisplayNodeInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeExtras.h" @@ -320,16 +323,16 @@ CLLocationCoordinate2D bottomRightCoord = CLLocationCoordinate2DMake(90, -180); for (id annotation in annotations) { - topLeftCoord = CLLocationCoordinate2DMake(fmax(topLeftCoord.latitude, annotation.coordinate.latitude), - fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); - bottomRightCoord = CLLocationCoordinate2DMake(fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), - fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); + topLeftCoord = CLLocationCoordinate2DMake(std::fmax(topLeftCoord.latitude, annotation.coordinate.latitude), + std::fmin(topLeftCoord.longitude, annotation.coordinate.longitude)); + bottomRightCoord = CLLocationCoordinate2DMake(std::fmin(bottomRightCoord.latitude, annotation.coordinate.latitude), + std::fmax(bottomRightCoord.longitude, annotation.coordinate.longitude)); } MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5, topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5), - MKCoordinateSpanMake(fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, - fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); + MKCoordinateSpanMake(std::fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 2, + std::fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 2)); return region; } diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 150644e52e..e4edbbd99d 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -12,6 +12,7 @@ #import "ASTextNode+Beta.h" #include +#import #import "_ASDisplayLayer.h" #import "ASDisplayNode+Subclasses.h" @@ -524,7 +525,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; [renderer enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect)); - CGFloat currentDistance = sqrtf(powf(point.x - glyphLocation.x, 2.f) + powf(point.y - glyphLocation.y, 2.f)); + CGFloat currentDistance = std::sqrt(std::pow(point.x - glyphLocation.x, 2.f) + std::pow(point.y - glyphLocation.y, 2.f)); if (currentDistance >= minimumGlyphDistance) { // If the distance computed from the touch to the glyph location is // not the minimum among the located link attributes, we can just skip diff --git a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm index 8398207d2f..0d4032a318 100644 --- a/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm +++ b/AsyncDisplayKit/Details/ASHighlightOverlayLayer.mm @@ -11,6 +11,7 @@ #import "ASHighlightOverlayLayer.h" #import +#import #import "ASInternalHelpers.h" @@ -86,7 +87,7 @@ static const UIEdgeInsets padding = {2, 4, 1.5, 4}; if (targetLayer != nil) { rect = [self convertRect:rect fromLayer:targetLayer]; } - rect = CGRectMake(roundf(rect.origin.x), roundf(rect.origin.y), roundf(rect.size.width), roundf(rect.size.height)); + rect = CGRectMake(std::round(rect.origin.x), std::round(rect.origin.y), std::round(rect.size.width), std::round(rect.size.height)); CGFloat minX = rect.origin.x - padding.left; CGFloat maxX = CGRectGetMaxX(rect) + padding.right; diff --git a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m b/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m index e39f836aad..98f335ef1e 100644 --- a/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m +++ b/AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m @@ -10,6 +10,8 @@ #import "ASAsciiArtBoxCreator.h" +#import + static const NSUInteger kDebugBoxPadding = 2; typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) @@ -69,7 +71,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) for (NSString *child in children) { NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; - NSUInteger topPadding = ceilf((CGFloat)(lineCountPerChild - [lines count])/2.0); + NSUInteger topPadding = ceil((CGFloat)(lineCountPerChild - [lines count])/2.0); NSUInteger bottomPadding = (lineCountPerChild - [lines count])/2.0; NSUInteger lineLength = [lines[0] length]; @@ -98,7 +100,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) NSUInteger totalLineLength = [concatenatedLines[0] length]; if (totalLineLength < [parent length]) { NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - totalLineLength; - NSUInteger leftPadding = ceilf((CGFloat)difference/2.0); + NSUInteger leftPadding = ceil((CGFloat)difference/2.0); NSUInteger rightPadding = difference/2; NSString *leftString = [@"|" debugbox_stringByAddingPadding:@" " count:leftPadding location:PIDebugBoxPaddingLocationEnd]; @@ -137,7 +139,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) if (maxChildLength < [parent length]) { NSUInteger difference = [parent length] + (2 * kDebugBoxPadding) - maxChildLength; - leftPadding = ceilf((CGFloat)difference/2.0); + leftPadding = ceil((CGFloat)difference/2.0); rightPadding = difference/2; } @@ -147,7 +149,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) for (NSString *child in children) { NSMutableArray *lines = [[child componentsSeparatedByString:@"\n"] mutableCopy]; - NSUInteger leftLinePadding = ceilf((CGFloat)(maxChildLength - [lines[0] length])/2.0); + NSUInteger leftLinePadding = ceil((CGFloat)(maxChildLength - [lines[0] length])/2.0); NSUInteger rightLinePadding = (maxChildLength - [lines[0] length])/2.0; for (NSString *line in lines) { @@ -171,7 +173,7 @@ typedef NS_ENUM(NSUInteger, PIDebugBoxPaddingLocation) NSUInteger totalLineLength = [boxStrings[0] length]; [boxStrings addObject:[NSString debugbox_stringWithString:@"-" repeatedCount:totalLineLength]]; - NSUInteger leftPadding = ceilf(((CGFloat)(totalLineLength - [parent length]))/2.0); + NSUInteger leftPadding = ceil(((CGFloat)(totalLineLength - [parent length]))/2.0); NSUInteger rightPadding = (totalLineLength - [parent length])/2; NSString *topLine = [parent debugbox_stringByAddingPadding:@"-" count:leftPadding location:PIDebugBoxPaddingLocationFront]; diff --git a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm index 8b51c71148..32e1bdea84 100644 --- a/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASRatioLayoutSpec.mm @@ -11,6 +11,7 @@ #import "ASRatioLayoutSpec.h" #import +#import #import #import "ASAssert.h" @@ -64,7 +65,7 @@ // Choose the size closest to the desired ratio. const auto &bestSize = std::max_element(sizeOptions.begin(), sizeOptions.end(), [&](const CGSize &a, const CGSize &b){ - return fabs((a.height / a.width) - _ratio) > fabs((b.height / b.width) - _ratio); + return std::fabs((a.height / a.width) - _ratio) > std::fabs((b.height / b.width) - _ratio); }); // If there is no max size in *either* dimension, we can't apply the ratio, so just pass our size range through. diff --git a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm index 82cc1608be..629b4eee71 100644 --- a/AsyncDisplayKit/Private/ASStackPositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackPositionedLayout.mm @@ -10,6 +10,8 @@ #import "ASStackPositionedLayout.h" +#import + #import "ASInternalHelpers.h" #import "ASLayoutSpecUtilities.h" @@ -105,16 +107,16 @@ ASStackPositionedLayout ASStackPositionedLayout::compute(const ASStackUnposition case ASStackLayoutJustifyContentStart: return stackedLayout(style, 0, unpositionedLayout, constrainedSize); case ASStackLayoutJustifyContentCenter: - return stackedLayout(style, floorf(violation / 2), unpositionedLayout, constrainedSize); + return stackedLayout(style, std::floor(violation / 2), unpositionedLayout, constrainedSize); case ASStackLayoutJustifyContentEnd: return stackedLayout(style, violation, unpositionedLayout, constrainedSize); case ASStackLayoutJustifyContentSpaceBetween: { const auto numOfSpacings = numOfItems - 1; - return stackedLayout(style, 0, floorf(violation / numOfSpacings), fmodf(violation, numOfSpacings), unpositionedLayout, constrainedSize); + return stackedLayout(style, 0, std::floor(violation / numOfSpacings), std::fmod(violation, numOfSpacings), unpositionedLayout, constrainedSize); } case ASStackLayoutJustifyContentSpaceAround: { // Spacing between items are twice the spacing on the edges - CGFloat spacingUnit = floorf(violation / (numOfItems * 2)); + CGFloat spacingUnit = std::floor(violation / (numOfItems * 2)); return stackedLayout(style, spacingUnit, spacingUnit * 2, 0, unpositionedLayout, constrainedSize); } } diff --git a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm index e0026fcba9..0dfa11fb06 100644 --- a/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm +++ b/AsyncDisplayKit/Private/ASStackUnpositionedLayout.mm @@ -10,6 +10,7 @@ #import "ASStackUnpositionedLayout.h" +#import #import #import "ASLayoutSpecUtilities.h" @@ -87,7 +88,7 @@ static void stretchChildrenAlongCrossDimension(std::vector 0.01) { + if (alignItems == ASStackLayoutAlignItemsStretch && std::fabs(cross - childCrossMax) > 0.01) { l.layout = crossChildLayout(child, style, stack, stack, childCrossMax, childCrossMax); } } @@ -182,7 +183,7 @@ static const CGFloat kViolationEpsilon = 0.01; */ static std::function isFlexibleInViolationDirection(const CGFloat violation) { - if (fabs(violation) < kViolationEpsilon) { + if (std::fabs(violation) < kViolationEpsilon) { return [](const ASStackUnpositionedItem &l) { return NO; }; } else if (violation > 0) { return [](const ASStackUnpositionedItem &l) { return l.child.flexGrow; }; @@ -263,7 +264,7 @@ static void flexChildrenAlongStackDimension(std::vector } // Each flexible child along the direction of the violation is expanded or contracted equally - const CGFloat violationPerFlexChild = floorf(violation / flexibleChildren); + const CGFloat violationPerFlexChild = std::floor(violation / flexibleChildren); // If the floor operation above left a remainder we may have a remainder after deducting the adjustments from all the // contributions of the flexible children. const CGFloat violationRemainder = violation - (violationPerFlexChild * flexibleChildren); diff --git a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm index 3456308183..64eea95892 100644 --- a/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm +++ b/AsyncDisplayKit/Private/_ASCoreAnimationExtras.mm @@ -32,8 +32,8 @@ extern void ASDisplayNodeSetupLayerContentsWithResizableImage(CALayer *layer, UI UIEdgeInsets insets = [image capInsets]; // These are lifted from what UIImageView does by experimentation. Without these exact values, the stretching is slightly off. - const float halfPixelFudge = 0.49f; - const float otherPixelFudge = 0.02f; + const CGFloat halfPixelFudge = 0.49f; + const CGFloat otherPixelFudge = 0.02f; // Convert to unit coordinates for the contentsCenter property. CGRect contentsCenter = CGRectMake(0.0f, 0.0f, 1.0f, 1.0f); if (insets.left > 0 || insets.right > 0) { diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm index bf910a10de..441aa5c921 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -8,12 +8,15 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASTextKitContext.h" -#import "ASTextKitFontSizeAdjuster.h" -#import "ASLayoutManager.h" +#import "ASTextKitFontSizeAdjuster.h" + +#import #import +#import "ASTextKitContext.h" +#import "ASLayoutManager.h" + //#define LOG(...) NSLog(__VA_ARGS__) #define LOG(...) @@ -48,7 +51,7 @@ [attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { if (attrs[NSFontAttributeName] != nil) { UIFont *font = attrs[NSFontAttributeName]; - font = [font fontWithSize:roundf(font.pointSize * scaleFactor)]; + font = [font fontWithSize:std::round(font.pointSize * scaleFactor)]; [attrString removeAttribute:NSFontAttributeName range:range]; [attrString addAttribute:NSFontAttributeName value:font range:range]; } @@ -166,7 +169,7 @@ // adjust here so we start at the proper place in our scale array if we have too many lines scaleIndex++; - if (ceilf(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) { + if (std::ceil(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) { // we fit! we are done break; } diff --git a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm b/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm index 3a3abb5e47..1039f86494 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitRenderer+Positioning.mm @@ -11,6 +11,7 @@ #import "ASTextKitRenderer+Positioning.h" #import +#import #import "ASAssert.h" @@ -163,7 +164,7 @@ static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3; [self enumerateTextIndexesAtPosition:position usingBlock:^(NSUInteger characterIndex, CGRect glyphBoundingRect, BOOL *stop) { CGPoint glyphLocation = CGPointMake(CGRectGetMidX(glyphBoundingRect), CGRectGetMidY(glyphBoundingRect)); - CGFloat currentDistance = sqrtf(powf(position.x - glyphLocation.x, 2.f) + powf(position.y - glyphLocation.y, 2.f)); + CGFloat currentDistance = std::sqrt(std::pow(position.x - glyphLocation.x, 2.f) + std::pow(position.y - glyphLocation.y, 2.f)); if (currentDistance < minimumGlyphDistance) { minimumGlyphDistance = currentDistance; minimumGlyphCharacterIndex = characterIndex; diff --git a/AsyncDisplayKit/TextKit/ASTextKitShadower.mm b/AsyncDisplayKit/TextKit/ASTextKitShadower.mm index 5da2b55b75..a4960e6699 100755 --- a/AsyncDisplayKit/TextKit/ASTextKitShadower.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitShadower.mm @@ -10,6 +10,8 @@ #import "ASTextKitShadower.h" +#import + static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets) { return UIEdgeInsetsInsetRect({.size = size}, insets).size; @@ -87,11 +89,11 @@ static inline UIEdgeInsets _invertInsets(UIEdgeInsets insets) // min values are expected to be negative for most typical shadowOffset and // blurRadius settings: - shadowPadding.top = fminf(0.0f, _shadowOffset.height - _shadowRadius); - shadowPadding.left = fminf(0.0f, _shadowOffset.width - _shadowRadius); + shadowPadding.top = std::fmin(0.0f, _shadowOffset.height - _shadowRadius); + shadowPadding.left = std::fmin(0.0f, _shadowOffset.width - _shadowRadius); - shadowPadding.bottom = fminf(0.0f, -_shadowOffset.height - _shadowRadius); - shadowPadding.right = fminf(0.0f, -_shadowOffset.width - _shadowRadius); + shadowPadding.bottom = std::fmin(0.0f, -_shadowOffset.height - _shadowRadius); + shadowPadding.right = std::fmin(0.0f, -_shadowOffset.width - _shadowRadius); _calculatedShadowPadding = shadowPadding; } From 49b65fd783df1b86ea7e87b9f42293cc85f6fe14 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 9 Aug 2016 16:10:21 -0700 Subject: [PATCH 06/50] Revert "Fix if pending layout will be deallocated (#2038)" (#2051) This reverts commit 2804d50220dabebc62b088b17d5ffe2347da1e36. --- AsyncDisplayKit/ASDisplayNode.mm | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index c57589025d..edbf245461 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -789,12 +789,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // Setup pending layout transition for animation - // The pending layout transition needs to stay alive at least until applySubnodeInsertions did finish execute as - // it can happen that with Implicit Hierarchy Management new nodes gonna be added that internally call setNeedsLayout - // what will invalidate and deallocate the transition in the middle of inserting nodes - NS_VALID_UNTIL_END_OF_SCOPE ASLayoutTransition *pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self pendingLayout:newLayout previousLayout:previousLayout]; - _pendingLayoutTransition = pendingLayoutTransition; - + _pendingLayoutTransition = [[ASLayoutTransition alloc] initWithNode:self + pendingLayout:newLayout + previousLayout:previousLayout]; // Setup context for pending layout transition. we need to hold a strong reference to the context _pendingLayoutTransitionContext = [[_ASTransitionContext alloc] initWithAnimation:animated layoutDelegate:_pendingLayoutTransition From eb9f86cfdbce41204009bb7922b82a1bc77c5ecf Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 17 Jun 2016 10:34:45 -0700 Subject: [PATCH 07/50] Replace "percent" with "fraction" where appropriate --- AsyncDisplayKit/ASImageNode.h | 2 +- AsyncDisplayKit/Layout/ASDimension.h | 4 ++-- AsyncDisplayKit/Layout/ASDimension.mm | 10 +++++----- AsyncDisplayKit/Layout/ASInsetLayoutSpec.h | 2 +- AsyncDisplayKit/Layout/ASLayoutable.h | 2 +- AsyncDisplayKit/Layout/ASStaticLayoutable.h | 2 +- AsyncDisplayKit/Private/ASImageNode+CGExtras.h | 2 +- AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm | 6 +++--- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode.h b/AsyncDisplayKit/ASImageNode.h index c733a66459..35cbb068d6 100644 --- a/AsyncDisplayKit/ASImageNode.h +++ b/AsyncDisplayKit/ASImageNode.h @@ -83,7 +83,7 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); * * @discussion This value defines a rectangle that is to be featured by the * receiver. The rectangle is specified as a "unit rectangle," using - * percentages of the source image's width and height, e.g. CGRectMake(0.5, 0, + * fractions of the source image's width and height, e.g. CGRectMake(0.5, 0, * 0.5, 1.0) will feature the full right half a photo. If the cropRect is * empty, the content mode of the receiver will be used to determine its * dimensions, and only the cropRect's origin will be used for positioning. The diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h index 822a1c1963..79e7e2f64b 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -17,7 +17,7 @@ typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { /** Just a number. It will always resolve to exactly this amount. This is the default type. */ ASRelativeDimensionTypePoints, /** Multiplied to a provided parent amount to resolve a final amount. */ - ASRelativeDimensionTypePercent, + ASRelativeDimensionTypeFraction, }; typedef struct { @@ -44,7 +44,7 @@ extern ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, extern ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points); -extern ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent); +extern ASRelativeDimension ASRelativeDimensionMakeWithFraction(CGFloat fraction); extern ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension); diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm index d7309d7370..5859830c56 100644 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -32,10 +32,10 @@ ASRelativeDimension ASRelativeDimensionMakeWithPoints(CGFloat points) return ASRelativeDimensionMake(ASRelativeDimensionTypePoints, points); } -ASRelativeDimension ASRelativeDimensionMakeWithPercent(CGFloat percent) +ASRelativeDimension ASRelativeDimensionMakeWithFraction(CGFloat fraction) { - // ASDisplayNodeCAssert( 0 <= percent && percent <= 1.0, @"ASRelativeDimension percent value (%f) must be between 0 and 1.", percent); - return ASRelativeDimensionMake(ASRelativeDimensionTypePercent, percent); + // ASDisplayNodeCAssert( 0 <= fraction && fraction <= 1.0, @"ASRelativeDimension fraction value (%f) must be between 0 and 1.", fraction); + return ASRelativeDimensionMake(ASRelativeDimensionTypeFraction, fraction); } ASRelativeDimension ASRelativeDimensionCopy(ASRelativeDimension aDimension) @@ -53,7 +53,7 @@ NSString *NSStringFromASRelativeDimension(ASRelativeDimension dimension) switch (dimension.type) { case ASRelativeDimensionTypePoints: return [NSString stringWithFormat:@"%.0fpt", dimension.value]; - case ASRelativeDimensionTypePercent: + case ASRelativeDimensionTypeFraction: return [NSString stringWithFormat:@"%.0f%%", dimension.value * 100.0]; } } @@ -63,7 +63,7 @@ CGFloat ASRelativeDimensionResolve(ASRelativeDimension dimension, CGFloat parent switch (dimension.type) { case ASRelativeDimensionTypePoints: return dimension.value; - case ASRelativeDimensionTypePercent: + case ASRelativeDimensionTypeFraction: return dimension.value * parent; } } diff --git a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h index 9584094226..b41e66fb5f 100644 --- a/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASInsetLayoutSpec.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN /** A layout spec that wraps another layoutable child, applying insets around it. - If the child has a size specified as a percentage, the percentage is resolved against this spec's parent + If the child has a size specified as a fraction, the fraction is resolved against this spec's parent size **after** applying insets. @example ASOuterLayoutSpec contains an ASInsetLayoutSpec with an ASInnerLayoutSpec. Suppose that: diff --git a/AsyncDisplayKit/Layout/ASLayoutable.h b/AsyncDisplayKit/Layout/ASLayoutable.h index 0f68d5e2dd..ad1d1f8b92 100644 --- a/AsyncDisplayKit/Layout/ASLayoutable.h +++ b/AsyncDisplayKit/Layout/ASLayoutable.h @@ -117,7 +117,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - ASStaticLayoutable /** - If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec. + If specified, the child's size is restricted according to this size. Fractions are resolved relative to the static layout spec. */ @property (nonatomic, assign) ASRelativeSizeRange sizeRange; diff --git a/AsyncDisplayKit/Layout/ASStaticLayoutable.h b/AsyncDisplayKit/Layout/ASStaticLayoutable.h index 825b6ac40e..d1c3f74806 100644 --- a/AsyncDisplayKit/Layout/ASStaticLayoutable.h +++ b/AsyncDisplayKit/Layout/ASStaticLayoutable.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASStaticLayoutable /** - If specified, the child's size is restricted according to this size. Percentages are resolved relative to the static layout spec. + If specified, the child's size is restricted according to this size. Fractions are resolved relative to the static layout spec. */ @property (nonatomic, assign) ASRelativeSizeRange sizeRange; diff --git a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h b/AsyncDisplayKit/Private/ASImageNode+CGExtras.h index 74471ad62d..52b2109c50 100644 --- a/AsyncDisplayKit/Private/ASImageNode+CGExtras.h +++ b/AsyncDisplayKit/Private/ASImageNode+CGExtras.h @@ -20,7 +20,7 @@ ASDISPLAYNODE_EXTERN_C_BEGIN @param sourceImageSize The size of the encoded image. @param boundsSize The bounds in which the image will be displayed. @param contentMode The mode that defines how image will be scaled and cropped to fit. Supported values are UIViewContentModeScaleToAspectFill and UIViewContentModeScaleToAspectFit. - @param cropRect A rectangle that is to be featured by the cropped image. The rectangle is specified as a "unit rectangle," using percentages of the source image's width and height, e.g. CGRectMake(0.5, 0, 0.5, 1.0) will feature the full right half a photo. If the cropRect is empty, the contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning. + @param cropRect A rectangle that is to be featured by the cropped image. The rectangle is specified as a "unit rectangle," using fractions of the source image's width and height, e.g. CGRectMake(0.5, 0, 0.5, 1.0) will feature the full right half a photo. If the cropRect is empty, the contentMode will be used to determine the drawRect's size, and only the cropRect's origin will be used for positioning. @param forceUpscaling A boolean that indicates you would *not* like the backing size to be downscaled if the image is smaller than the destination size. Setting this to YES will result in higher memory usage when images are smaller than their destination. @discussion If the image is smaller than the size and UIViewContentModeScaleToAspectFill is specified, we suggest the input size so it will be efficiently upscaled on the GPU by the displaying layer at composite time. */ diff --git a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm index bf40f61133..3b30272d45 100644 --- a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm +++ b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm @@ -330,7 +330,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) subnode2.staticSize = {50, 50}; ASRatioLayoutSpec *child1 = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1.5 child:subnode1]; - child1.flexBasis = ASRelativeDimensionMakeWithPercent(1); + child1.flexBasis = ASRelativeDimensionMakeWithFraction(1); child1.flexGrow = YES; child1.flexShrink = YES; @@ -508,7 +508,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) [self testStackLayoutSpecWithStyle:style sizeRange:kOverflowSize subnodes:subnodes identifier:@"overflow"]; } -- (void)testPercentageFlexBasisResolvesAgainstParentSize +- (void)testFractionalFlexBasisResolvesAgainstParentSize { ASStackLayoutSpecStyle style = {.direction = ASStackLayoutDirectionHorizontal}; @@ -520,7 +520,7 @@ static NSArray *defaultSubnodesWithSameSize(CGSize subnodeSize, BOOL flex) // This should override the intrinsic size of 50pts and instead compute to 50% = 100pts. // The result should be that the red box is twice as wide as the blue and gree boxes after flexing. - ((ASStaticSizeDisplayNode *)subnodes[0]).flexBasis = ASRelativeDimensionMakeWithPercent(0.5); + ((ASStaticSizeDisplayNode *)subnodes[0]).flexBasis = ASRelativeDimensionMakeWithFraction(0.5); static ASSizeRange kSize = {{200, 0}, {200, INFINITY}}; [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; From 7abceba4a01254d56aee226fe6988a3eec7e0b1c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 17 Jun 2016 11:41:26 -0700 Subject: [PATCH 08/50] Rename snapshot test reference images --- ...tionalFlexBasisResolvesAgainstParentSize@2x.png} | Bin ...tionalFlexBasisResolvesAgainstParentSize@3x.png} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/{testPercentageFlexBasisResolvesAgainstParentSize@2x.png => testFractionalFlexBasisResolvesAgainstParentSize@2x.png} (100%) rename AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/{testPercentageFlexBasisResolvesAgainstParentSize@3x.png => testFractionalFlexBasisResolvesAgainstParentSize@3x.png} (100%) diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@2x.png rename to AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@2x.png diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@3x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@3x.png similarity index 100% rename from AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testPercentageFlexBasisResolvesAgainstParentSize@3x.png rename to AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testFractionalFlexBasisResolvesAgainstParentSize@3x.png From f07d5c4e9922db40835997275577cf129ecdde5a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 17 Jun 2016 13:52:09 -0700 Subject: [PATCH 09/50] [Examples] Percent -> Fraction --- .../Sample/OverviewComponentsViewController.m | 4 ++-- examples/CatDealsCollectionView/Sample/ItemNode.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m b/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m index 54bd530b1a..2734bb5265 100644 --- a/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m +++ b/examples/AsyncDisplayKitOverview/Sample/OverviewComponentsViewController.m @@ -425,8 +425,8 @@ typedef ASLayoutSpec *(^OverviewDisplayNodeSizeThatFitsBlock)(ASSizeRange constr verticalStackLayoutSpec.spacing = 5.0; // Spacing between children // Layout the stack layout with 100% width and 100% height of the parent node - ASRelativeSizeRange sizeRange = ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimensionMakeWithPercent(1), - ASRelativeDimensionMakeWithPercent(1)); + ASRelativeSizeRange sizeRange = ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimensionMakeWithFraction(1), + ASRelativeDimensionMakeWithFraction(1)); verticalStackLayoutSpec.sizeRange = sizeRange; // Wrap the static stack layout in a static spec so it will grow to the whole parent node size diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m index e383abf16f..34da74cfbc 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.m +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -107,7 +107,7 @@ const CGFloat kSoldOutGBHeight = 50.0; self.soldOutLabelFlat.layerBacked = YES; self.soldOutLabelBackground = [[ASDisplayNode alloc] init]; - self.soldOutLabelBackground.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight))); + self.soldOutLabelBackground.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight))); self.soldOutLabelBackground.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9]; self.soldOutLabelBackground.flexGrow = YES; self.soldOutLabelBackground.layerBacked = YES; @@ -289,7 +289,7 @@ const CGFloat kSoldOutGBHeight = 50.0; ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:self.dealImageView]; self.badge.layoutPosition = CGPointMake(0, constrainedSize.max.height - kFixedLabelsAreaHeight - kBadgeHeight); - self.badge.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(0), ASRelativeDimensionMakeWithPoints(kBadgeHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kBadgeHeight))); + self.badge.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(0), ASRelativeDimensionMakeWithPoints(kBadgeHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(1), ASRelativeDimensionMakeWithPoints(kBadgeHeight))); ASStaticLayoutSpec *badgePosition = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.badge]]; ASOverlayLayoutSpec *badgeOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imagePlace overlay:badgePosition]; From 32058815f24c136172d8b39974482c24d97ef8f0 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 1 Aug 2016 17:12:52 -0700 Subject: [PATCH 10/50] Move Transition Layout API out of beta header and remove shouldMeasureAsync from API shouldMeasureAsync is enabled by default now. --- AsyncDisplayKit/ASContextTransitioning.h | 5 +- AsyncDisplayKit/ASDisplayNode+Beta.h | 53 +--------- AsyncDisplayKit/ASDisplayNode.h | 97 +++++++++++++++++++ AsyncDisplayKit/ASDisplayNode.mm | 42 +++++--- AsyncDisplayKit/_ASTransitionContext.m | 2 +- .../ASDisplayNodeImplicitHierarchyTests.m | 2 +- 6 files changed, 132 insertions(+), 69 deletions(-) diff --git a/AsyncDisplayKit/ASContextTransitioning.h b/AsyncDisplayKit/ASContextTransitioning.h index 1b87cad643..f013ff2134 100644 --- a/AsyncDisplayKit/ASContextTransitioning.h +++ b/AsyncDisplayKit/ASContextTransitioning.h @@ -10,7 +10,10 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import +#import + +@class ASDisplayNode; +@class ASLayout; NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index b44715c97e..aa3b3d96a1 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -8,7 +8,7 @@ // of patent rights can be found in the PATENTS file in the same directory. // -#import "ASContextTransitioning.h" +#import "ASDisplayNode.h" #import "ASLayoutRangeType.h" NS_ASSUME_NONNULL_BEGIN @@ -65,63 +65,12 @@ ASDISPLAYNODE_EXTERN_C_END @property (nonatomic) BOOL usesImplicitHierarchyManagement; -/** - * @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. - */ -- (void)animateLayoutTransition:(id)context; - -/** - * @discussion A place to clean up your nodes after the transition - */ -- (void)didCompleteLayoutTransition:(id)context; - -/** - * @abstract Transitions the current layout with a new constrained size. Must be called on main thread. - * - * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. - * - * @param shouldMeasureAsync Measure the layout asynchronously. - * - * @param measurementCompletion Optional completion block called only if a new layout is calculated. - * It is called on main, right after the measurement and before -animateLayoutTransition:. - * - * @discussion If the passed constrainedSize is the the same as the node's current constrained size, this method is noop. - * - * @see animateLayoutTransition: - */ -- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize - animated:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(nullable void(^)())completion; - -/** - * @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread. - * - * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. - * - * @param shouldMeasureAsync Measure the layout asynchronously. - * - * @param measurementCompletion Optional completion block called only if a new layout is calculated. - * It is called right after the measurement and before -animateLayoutTransition:. - * - * @see animateLayoutTransition: - */ -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(nullable void(^)())completion; - - /** * @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network. * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. */ - (BOOL)placeholderShouldPersist; -/** - * @abstract Cancels all performing layout transitions. Can be called on any thread. - */ -- (void)cancelLayoutTransitionsInProgress; - /** * @abstract Indicates that the receiver and all subnodes have finished displaying. May be called more than once, for example if the receiver has * a network image node. This is called after the first display pass even if network image nodes have not downloaded anything (text would be done, diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 3dd0f2749c..08b2bfe469 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -16,6 +16,7 @@ #import #import #import +#import #define ASDisplayNodeLoggingEnabled 0 @@ -744,6 +745,54 @@ NS_ASSUME_NONNULL_BEGIN // Accessibility identification support @property (nonatomic, copy, nullable) NSString *accessibilityIdentifier; +@end + +@interface ASDisplayNode (LayoutTransitioning) + +/** + * @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. + */ +- (void)animateLayoutTransition:(nonnull id)context; + +/** + * @discussion A place to clean up your nodes after the transition + */ +- (void)didCompleteLayoutTransition:(nonnull id)context; + +/** + * @abstract Transitions the current layout with a new constrained size. Must be called on main thread. + * + * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * @param shouldMeasureAsync Measure the layout asynchronously. + * @param measurementCompletion Optional completion block called only if a new layout is calculated. + * + * @discussion It is called on main, right after the measurement and before -animateLayoutTransition:. If the passed constrainedSize is the the same as the node's current constrained size, this method is noop. + * + * @see animateLayoutTransition: + */ +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + measurementCompletion:(nullable void(^)())completion; + +/** + * @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread. + * + * @discussion It is called right after the measurement and before -animateLayoutTransition:. + * + * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * @param measurementCompletion Optional completion block called only if a new layout is calculated. + * + * @see animateLayoutTransition: + * + */ +- (void)transitionLayoutAnimated:(BOOL)animated measurementCompletion:(nullable void(^)())completion; + +/** + * @abstract Cancels all performing layout transitions. Can be called on any thread. + */ +- (void)cancelLayoutTransition; + + @end /* @@ -776,6 +825,54 @@ NS_ASSUME_NONNULL_BEGIN @interface ASDisplayNode (Deprecated) +/** + * @abstract Transitions the current layout with a new constrained size. Must be called on main thread. + * + * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * @param shouldMeasureAsync Measure the layout asynchronously. + * @param measurementCompletion Optional completion block called only if a new layout is calculated. + * It is called on main, right after the measurement and before -animateLayoutTransition:. + * + * @discussion If the passed constrainedSize is the the same as the node's current constrained size, this method is noop. + * + * @see animateLayoutTransition: + * + * @deprecated Deprecated in version 2.0: Use transitionLayoutWithSizeRange:animated:measurementCompletion:. + * shouldMeasureAsync is enabled by default now. + * + */ +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(nullable void(^)())completion ASDISPLAYNODE_DEPRECATED; + + +/** + * @abstract Invalidates the current layout and begins a relayout of the node with the current `constrainedSize`. Must be called on main thread. + * + * @discussion It is called right after the measurement and before -animateLayoutTransition:. + * + * @param animated Animation is optional, but will still proceed through your `animateLayoutTransition` implementation with `isAnimated == NO`. + * @param shouldMeasureAsync Measure the layout asynchronously. + * @param measurementCompletion Optional completion block called only if a new layout is calculated. + * + * @see animateLayoutTransition: + * + * @deprecated Deprecated in version 2.0: Use transitionLayoutAnimated:measurementCompletion: + * shouldMeasureAsync is enabled by default now. + * + */ +- (void)transitionLayoutWithAnimation:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(nullable void(^)())completion ASDISPLAYNODE_DEPRECATED; + +/** + * @abstract Cancels all performing layout transitions. Can be called on any thread. + * + * @deprecated Deprecated in version 2.0: Use cancelLayoutTransition + */ +- (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED; + - (void)reclaimMemory ASDISPLAYNODE_DEPRECATED; - (void)recursivelyReclaimMemory ASDISPLAYNODE_DEPRECATED; @property (nonatomic, assign) BOOL placeholderFadesOut ASDISPLAYNODE_DEPRECATED; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index edbf245461..f4b804d6d7 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -646,7 +646,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return _calculatedLayout ? : [ASLayout layoutWithLayoutableObject:self constrainedSizeRange:constrainedSize size:CGSizeZero]; } - [self cancelLayoutTransitionsInProgress]; + [self cancelLayoutTransition]; ASLayout *previousLayout = _calculatedLayout; ASLayout *newLayout = [self calculateLayoutThatFits:constrainedSize]; @@ -701,9 +701,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark - Layout Transition -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion +- (void)transitionLayoutAnimated:(BOOL)animated measurementCompletion:(void (^)())completion { if (_calculatedLayout == nil) { // constrainedSizeRange returns a struct and is invalid to call on nil. @@ -714,14 +712,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self invalidateCalculatedLayout]; [self transitionLayoutWithSizeRange:_calculatedLayout.constrainedSizeRange animated:animated - shouldMeasureAsync:shouldMeasureAsync measurementCompletion:completion]; } - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion + measurementCompletion:(void (^)())completion { ASDisplayNodeAssertMainThread(); if (! [self shouldMeasureWithSizeRange:constrainedSize]) { @@ -741,7 +737,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) node.pendingTransitionID = transitionID; }); - void (^transitionBlock)() = ^{ + ASPerformBlockOnBackgroundThread(^{ if ([self _shouldAbortTransitionWithID:transitionID]) { return; } @@ -803,12 +799,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // Kick off animating the layout transition [self animateLayoutTransition:_pendingLayoutTransitionContext]; }); - }; - - ASPerformBlockOnBackgroundThread(transitionBlock); + }); } -- (void)cancelLayoutTransitionsInProgress +- (void)cancelLayoutTransition { ASDN::MutexLocker l(__instanceLock__); if ([self _isTransitionInProgress]) { @@ -861,7 +855,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return (!_transitionInProgress || _transitionID != transitionID); } -#pragma mark - Layout Transition API / ASDisplayNode (Beta) +#pragma mark Layout Transition API /* * Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default this just layouts @@ -882,7 +876,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [_pendingLayoutTransition applySubnodeRemovals]; } -#pragma mark - _ASTransitionContextCompletionDelegate +#pragma mark _ASTransitionContextCompletionDelegate /* * After completeTransition: is called on the ASContextTransitioning object in animateLayoutTransition: this @@ -3172,6 +3166,26 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; @implementation ASDisplayNode (Deprecated) +- (void)transitionLayoutWithAnimation:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion +{ + [self transitionLayoutAnimated:animated measurementCompletion:completion]; +} + +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion +{ + [self transitionLayoutWithSizeRange:constrainedSize animated:animated measurementCompletion:completion]; +} + +- (void)cancelLayoutTransitionsInProgress +{ + [self cancelLayoutTransition]; +} + - (void)setPlaceholderFadesOut:(BOOL)placeholderFadesOut { self.placeholderFadeDuration = placeholderFadesOut ? 0.1 : 0.0; diff --git a/AsyncDisplayKit/_ASTransitionContext.m b/AsyncDisplayKit/_ASTransitionContext.m index e8ad4e8c14..031810400c 100644 --- a/AsyncDisplayKit/_ASTransitionContext.m +++ b/AsyncDisplayKit/_ASTransitionContext.m @@ -11,7 +11,7 @@ // #import "_ASTransitionContext.h" - +#import "ASDisplayNode.h" #import "ASLayout.h" diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 627960d12d..95adca7a35 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -199,7 +199,7 @@ node.layoutState = @2; [node invalidateCalculatedLayout]; - [node transitionLayoutWithAnimation:YES shouldMeasureAsync:YES measurementCompletion:^{ + [node transitionLayoutAnimated:YES measurementCompletion:^{ // Push this to the next runloop to let async insertion / removing of nodes finished before checking dispatch_async(dispatch_get_main_queue(), ^{ XCTAssertEqual(node.subnodes[0], node2); From b84be07776ce6d65cb064b278b7a26d3874de0e5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 5 Aug 2016 06:33:59 -0700 Subject: [PATCH 11/50] Make child and children on ASLayoutSpec a property --- AsyncDisplayKit/Layout/ASLayoutSpec.h | 30 ++++++--------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/AsyncDisplayKit/Layout/ASLayoutSpec.h b/AsyncDisplayKit/Layout/ASLayoutSpec.h index 97f9fe1ca8..72d5b2445c 100644 --- a/AsyncDisplayKit/Layout/ASLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASLayoutSpec.h @@ -40,11 +40,12 @@ NS_ASSUME_NONNULL_BEGIN * only require a single child. * * For layout specs that require a known number of children (ASBackgroundLayoutSpec, for example) - * a subclass should use this method to set the "primary" child. This is actually the same as calling - * setChild:forIdentifier:0. All other children should be set by defining convenience methods - * that call setChild:forIdentifier behind the scenes. + * a subclass should use this method to set the "primary" child. It can then use setChild:forIdentifier: + * to set any other required children. Ideally a subclass would hide this from the user, and use the + * setChild:forIdentifier: internally. For example, ASBackgroundLayoutSpec exposes a backgroundChild + * property that behind the scenes is calling setChild:forIdentifier:. */ -- (void)setChild:(id)child; +@property (nullable, strong, nonatomic) id child; /** * Adds a child with the given identifier to this layout spec. @@ -76,21 +77,7 @@ NS_ASSUME_NONNULL_BEGIN * For good measure, in these layout specs it probably makes sense to define * setChild: and setChild:forIdentifier: methods to do something appropriate or to assert. */ -- (void)setChildren:(NSArray> *)children; - -/** - * Get child methods - * - * There is a corresponding "getChild" method for the above "setChild" methods. If a subclass - * has extra layoutable children, it is recommended to make a corresponding get method for that - * child. For example, the ASBackgroundLayoutSpec responds to backgroundChild. - * - * If a get method is called on a spec that doesn't make sense, then the standard is to assert. - * For example, calling children on an ASInsetLayoutSpec will assert. - */ - -/** Returns the child added to this layout spec using the default identifier. */ -- (nullable id)child; +@property (nullable, strong, nonatomic) NSArray> *children; /** * Returns the child added to this layout spec using the given index. @@ -99,11 +86,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable id)childForIndex:(NSUInteger)index; -/** - * Returns all children added to this layout spec. - */ -- (nullable NSArray> *)children; - @end @interface ASLayoutSpec (Debugging) From e80a82f8327d24ef1150699125ba53918f8b4bed Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 10 Aug 2016 14:51:10 -0700 Subject: [PATCH 12/50] [ASDataController] Bail on waitUntilUpdatesAreCommitted if batch updating (#2053) --- AsyncDisplayKit/Details/ASChangeSetDataController.mm | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/AsyncDisplayKit/Details/ASChangeSetDataController.mm index b85aacf817..208d84aebb 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.mm +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.mm @@ -84,6 +84,18 @@ return batchUpdating; } +- (void)waitUntilAllUpdatesAreCommitted +{ + ASDisplayNodeAssertMainThread(); + if (self.batchUpdating) { + // This assertion will be enabled soon. +// ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); + return; + } + + [super waitUntilAllUpdatesAreCommitted]; +} + #pragma mark - Section Editing (External API) - (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions From 3f77a3d46c01e24d90c1d1a9dd3d119ffe2d0065 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 11 Aug 2016 10:37:48 -0700 Subject: [PATCH 13/50] More Percent -> Fraction --- AsyncDisplayKit/Layout/ASDimension.mm | 4 ++-- AsyncDisplayKit/Layout/ASRelativeSize.h | 8 ++++---- AsyncDisplayKit/Layout/ASRelativeSize.mm | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/AsyncDisplayKit/Layout/ASDimension.mm b/AsyncDisplayKit/Layout/ASDimension.mm index 5859830c56..a60b6b4ba9 100644 --- a/AsyncDisplayKit/Layout/ASDimension.mm +++ b/AsyncDisplayKit/Layout/ASDimension.mm @@ -19,9 +19,9 @@ ASRelativeDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloa { if (type == ASRelativeDimensionTypePoints) { ASDisplayNodeCAssertPositiveReal(@"Points", value); - } else if (type == ASRelativeDimensionTypePercent) { + } else if (type == ASRelativeDimensionTypeFraction) { // TODO: Enable this assertion for 2.0. Check that there is no use case for using a larger value, e.g. to layout for a clipsToBounds = NO element. - // ASDisplayNodeCAssert( 0 <= value && value <= 1.0, @"ASRelativeDimension percent value (%f) must be between 0 and 1.", value); + // ASDisplayNodeCAssert( 0 <= value && value <= 1.0, @"ASRelativeDimension fraction value (%f) must be between 0 and 1.", value); } ASRelativeDimension dimension; dimension.type = type; dimension.value = value; return dimension; } diff --git a/AsyncDisplayKit/Layout/ASRelativeSize.h b/AsyncDisplayKit/Layout/ASRelativeSize.h index 12f4845531..4f355044e9 100644 --- a/AsyncDisplayKit/Layout/ASRelativeSize.h +++ b/AsyncDisplayKit/Layout/ASRelativeSize.h @@ -39,11 +39,11 @@ NS_ASSUME_NONNULL_BEGIN extern ASRelativeSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height); -/** Convenience constructor to provide size in Points. */ +/** Convenience constructor to provide size in points. */ extern ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size); -/** Convenience constructor to provide size in Percentage. */ -extern ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent); +/** Convenience constructor to provide size as a fraction. */ +extern ASRelativeSize ASRelativeSizeMakeWithFraction(CGFloat fraction); /** Resolve this relative size relative to a parent size. */ extern CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize); @@ -61,7 +61,7 @@ extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelati extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact); -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactPercent(CGFloat percent); +extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction); extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight); diff --git a/AsyncDisplayKit/Layout/ASRelativeSize.mm b/AsyncDisplayKit/Layout/ASRelativeSize.mm index 575c1f197e..3fc7abd92d 100644 --- a/AsyncDisplayKit/Layout/ASRelativeSize.mm +++ b/AsyncDisplayKit/Layout/ASRelativeSize.mm @@ -25,10 +25,10 @@ ASRelativeSize ASRelativeSizeMakeWithCGSize(CGSize size) ASRelativeDimensionMakeWithPoints(size.height)); } -ASRelativeSize ASRelativeSizeMakeWithPercent(CGFloat percent) +ASRelativeSize ASRelativeSizeMakeWithFraction(CGFloat fraction) { - return ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(percent), - ASRelativeDimensionMakeWithPercent(percent)); + return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction), + ASRelativeDimensionMakeWithFraction(fraction)); } CGSize ASRelativeSizeResolveSize(ASRelativeSize relativeSize, CGSize parentSize) @@ -67,9 +67,9 @@ ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); } -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactPercent(CGFloat percent) +ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) { - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithPercent(percent)); + return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithFraction(fraction)); } ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, From 2afd063b10213a631f8313088f046a220c29507f Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Thu, 11 Aug 2016 11:23:07 -0700 Subject: [PATCH 14/50] [ASRangeController] Debug overlay to show the size and direction of display + fetchData ranges during scrolling. (#2008) * Rebase ASRangeController diff with master. * fix Table / CollectionNode debug label names - now returns class names rather than generic ASTableNode, ASPagerNode - ASPagerNode will be labeled as ASPagerNodeProxy (not sure how to get around this) * refactor layout code: use ASDk's own resizeableRoundedCorner... methods * Fixes and cleanup for manual layout version of range controller debug overlay. I am working on a layout spec-based version, but it has some issues, so landing this to get the near-term value is probably the best next step. * Remove .orig and .rej files. * One last .orig file to remove. * Final project file cleanup and tweaks to implementation for ASTableNode. * fix build issues * fix arrow directions --- AsyncDisplayKit/ASCollectionNode.mm | 7 + AsyncDisplayKit/ASCollectionView.mm | 5 + AsyncDisplayKit/ASTableNode.mm | 7 + AsyncDisplayKit/ASTableView.mm | 5 + AsyncDisplayKit/AsyncDisplayKit+Debug.h | 39 +- AsyncDisplayKit/AsyncDisplayKit+Debug.m | 560 ++++++++++++++++++ AsyncDisplayKit/Details/ASRangeController.h | 2 + AsyncDisplayKit/Details/ASRangeController.mm | 30 +- .../Sample/ViewController.m | 1 + 9 files changed, 647 insertions(+), 9 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index c8d7aa2573..510ad4e50b 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -17,6 +17,7 @@ #import "ASEnvironmentInternal.h" #import "ASInternalHelpers.h" #import "ASCellNode+Internal.h" +#import "AsyncDisplayKit+Debug.h" #pragma mark - _ASCollectionPendingState @@ -171,6 +172,12 @@ [self.view clearFetchedData]; } +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState +{ + [super interfaceStateDidChange:newState fromState:oldState]; + [ASRangeController layoutDebugOverlayIfNeeded]; +} + #if ASRangeControllerLoggingEnabled - (void)visibleStateDidChange:(BOOL)isVisible { diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 5e71ffd40c..0c581d92b4 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -1080,6 +1080,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return [_dataController nodeAtIndexPath:indexPath]; } +- (NSString *)nameForRangeControllerDataSource +{ + return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); +} + #pragma mark - ASRangeControllerDelegate - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController diff --git a/AsyncDisplayKit/ASTableNode.mm b/AsyncDisplayKit/ASTableNode.mm index 2f4de2d0cf..f8af1ae0cb 100644 --- a/AsyncDisplayKit/ASTableNode.mm +++ b/AsyncDisplayKit/ASTableNode.mm @@ -16,6 +16,7 @@ #import "ASDisplayNode+Subclasses.h" #import "ASInternalHelpers.h" #import "ASCellNode+Internal.h" +#import "AsyncDisplayKit+Debug.h" #pragma mark - _ASTablePendingState @@ -125,6 +126,12 @@ [self.view clearFetchedData]; } +- (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState +{ + [super interfaceStateDidChange:newState fromState:oldState]; + [ASRangeController layoutDebugOverlayIfNeeded]; +} + #if ASRangeControllerLoggingEnabled - (void)visibleStateDidChange:(BOOL)isVisible { diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 958530cca9..85d24d199d 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -937,6 +937,11 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; return ASInterfaceStateForDisplayNode(self.tableNode, self.window); } +- (NSString *)nameForRangeControllerDataSource +{ + return self.asyncDataSource ? NSStringFromClass([self.asyncDataSource class]) : NSStringFromClass([self class]); +} + #pragma mark - ASRangeControllerDelegate - (void)didBeginUpdatesInRangeController:(ASRangeController *)rangeController diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.h b/AsyncDisplayKit/AsyncDisplayKit+Debug.h index 09c20f2b66..a85dda03fc 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.h +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.h @@ -12,6 +12,7 @@ #import "ASControlNode.h" #import "ASImageNode.h" +#import "ASRangeController.h" @interface ASImageNode (Debugging) @@ -29,16 +30,40 @@ @interface ASControlNode (Debugging) /** - Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. - NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! - Overlay = translucent GREEN color, - edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, - edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond - overlay rect, but can't be visualized). - @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. + * Class method to enable a visualization overlay of the tappable area on the ASControlNode. For app debugging purposes only. + * NOTE: GESTURE RECOGNIZERS, (including tap gesture recognizers on a control node) WILL NOT BE VISUALIZED!!! + * Overlay = translucent GREEN color, + * edges that are clipped by the tappable area of any parent (their bounds + hitTestSlop) in the hierarchy = DARK GREEN BORDERED EDGE, + * edges that are clipped by clipToBounds = YES of any parent in the hierarchy = ORANGE BORDERED EDGE (may still receive touches beyond + * overlay rect, but can't be visualized). + * @param enable Specify YES to make this debug feature enabled when messaging the ASControlNode class. */ + (void)setEnableHitTestDebug:(BOOL)enable; + (BOOL)enableHitTestDebug; @end +@interface ASRangeController (Debugging) + +/** + * Class method to enable a visualization overlay of the all ASRangeController's tuning parameters. For dev purposes only. + * To use, message ASRangeController in the AppDelegate --> [ASRangeController setShouldShowRangeDebugOverlay:YES]; + * @param enable Specify YES to make this debug feature enabled when messaging the ASRangeController class. + */ ++ (void)setShouldShowRangeDebugOverlay:(BOOL)show; ++ (BOOL)shouldShowRangeDebugOverlay; + ++ (void)layoutDebugOverlayIfNeeded; + +- (void)addRangeControllerToRangeDebugOverlay; + +- (void)updateRangeController:(ASRangeController *)controller + withScrollableDirections:(ASScrollDirection)scrollableDirections + scrollDirection:(ASScrollDirection)direction + rangeMode:(ASLayoutRangeMode)mode + displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters + fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters + interfaceState:(ASInterfaceState)interfaceState; + +@end + diff --git a/AsyncDisplayKit/AsyncDisplayKit+Debug.m b/AsyncDisplayKit/AsyncDisplayKit+Debug.m index 86d77e0f3b..e456ce72f6 100644 --- a/AsyncDisplayKit/AsyncDisplayKit+Debug.m +++ b/AsyncDisplayKit/AsyncDisplayKit+Debug.m @@ -13,6 +13,16 @@ #import "AsyncDisplayKit+Debug.h" #import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeExtras.h" +#import "ASWeakSet.h" +#import "UIImage+ASConvenience.h" +#import +#import +#import +#import +#import + + +#pragma mark - ASImageNode (Debugging) static BOOL __shouldShowImageScalingOverlay = NO; @@ -30,6 +40,8 @@ static BOOL __shouldShowImageScalingOverlay = NO; @end +#pragma mark - ASControlNode (DebuggingInternal) + static BOOL __enableHitTestDebug = NO; @interface ASControlNode (DebuggingInternal) @@ -191,3 +203,551 @@ static BOOL __enableHitTestDebug = NO; } @end + +#pragma mark - ASRangeController (Debugging) + +@interface _ASRangeDebugOverlayView : UIView + ++ (instancetype)sharedInstance; + +- (void)addRangeController:(ASRangeController *)rangeController; + +- (void)updateRangeController:(ASRangeController *)controller + withScrollableDirections:(ASScrollDirection)scrollableDirections + scrollDirection:(ASScrollDirection)direction + rangeMode:(ASLayoutRangeMode)mode + displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters + fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters + interfaceState:(ASInterfaceState)interfaceState; + +@end + +@interface _ASRangeDebugBarView : UIView + +@property (nonatomic, weak) ASRangeController *rangeController; +@property (nonatomic, assign) BOOL destroyOnLayout; +@property (nonatomic, strong) NSString *debugString; + +- (instancetype)initWithRangeController:(ASRangeController *)rangeController; + +- (void)updateWithVisibleRatio:(CGFloat)visibleRatio + displayRatio:(CGFloat)displayRatio + leadingDisplayRatio:(CGFloat)leadingDisplayRatio + fetchDataRatio:(CGFloat)fetchDataRatio + leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio + direction:(ASScrollDirection)direction; + +@end + +static BOOL __shouldShowRangeDebugOverlay = NO; + +@implementation ASRangeController (Debugging) + ++ (void)setShouldShowRangeDebugOverlay:(BOOL)show +{ + __shouldShowRangeDebugOverlay = show; +} + ++ (BOOL)shouldShowRangeDebugOverlay +{ + return __shouldShowRangeDebugOverlay; +} + ++ (void)layoutDebugOverlayIfNeeded +{ + [[_ASRangeDebugOverlayView sharedInstance] setNeedsLayout]; +} + +- (void)addRangeControllerToRangeDebugOverlay +{ + [[_ASRangeDebugOverlayView sharedInstance] addRangeController:self]; +} + +- (void)updateRangeController:(ASRangeController *)controller + withScrollableDirections:(ASScrollDirection)scrollableDirections + scrollDirection:(ASScrollDirection)direction + rangeMode:(ASLayoutRangeMode)mode + displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters + fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters + interfaceState:(ASInterfaceState)interfaceState +{ + [[_ASRangeDebugOverlayView sharedInstance] updateRangeController:controller + withScrollableDirections:scrollableDirections + scrollDirection:direction + rangeMode:mode + displayTuningParameters:displayTuningParameters + fetchDataTuningParameters:fetchDataTuningParameters + interfaceState:interfaceState]; +} + +@end + + +#pragma mark _ASRangeDebugOverlayView + +@interface _ASRangeDebugOverlayView () +@end + +@implementation _ASRangeDebugOverlayView +{ + NSMutableArray *_rangeControllerViews; + NSInteger _newControllerCount; + NSInteger _removeControllerCount; + BOOL _animating; +} + ++ (UIWindow *)keyWindow +{ + // hack to work around app extensions not having UIApplication...not sure of a better way to do this? + return [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow]; +} + ++ (instancetype)sharedInstance +{ + static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil; + + if (!__rangeDebugOverlay && [ASRangeController shouldShowRangeDebugOverlay]) { + __rangeDebugOverlay = [[self alloc] initWithFrame:CGRectZero]; + [[self keyWindow] addSubview:__rangeDebugOverlay]; + } + + return __rangeDebugOverlay; +} + +#define OVERLAY_INSET 10 +#define OVERLAY_SCALE 3 +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + _rangeControllerViews = [[NSMutableArray alloc] init]; + self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; + self.layer.zPosition = 1000; + self.clipsToBounds = YES; + + CGSize windowSize = [[[self class] keyWindow] bounds].size; + self.frame = CGRectMake(windowSize.width - (windowSize.width / OVERLAY_SCALE) - OVERLAY_INSET, windowSize.height - OVERLAY_INSET, + windowSize.width / OVERLAY_SCALE, 0.0); + + UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(rangeDebugOverlayWasPanned:)]; + [self addGestureRecognizer:panGR]; + } + + return self; +} + +#define BAR_THICKNESS 24 + +- (void)layoutSubviews +{ + [super layoutSubviews]; + [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + [self layoutToFitAllBarsExcept:0]; + } completion:^(BOOL finished) { + + }]; +} + +- (void)layoutToFitAllBarsExcept:(NSInteger)barsToClip +{ + CGSize boundsSize = self.bounds.size; + CGFloat totalHeight = 0.0; + + CGRect barRect = CGRectMake(0, boundsSize.height - BAR_THICKNESS, self.bounds.size.width, BAR_THICKNESS); + NSMutableArray *displayedBars = [NSMutableArray array]; + + for (_ASRangeDebugBarView *barView in [_rangeControllerViews copy]) { + barView.frame = barRect; + + ASInterfaceState interfaceState = [barView.rangeController.dataSource interfaceStateForRangeController:barView.rangeController]; + + if (!(interfaceState & (ASInterfaceStateVisible))) { + if (barView.destroyOnLayout && barView.alpha == 0.0) { + [_rangeControllerViews removeObjectIdenticalTo:barView]; + [barView removeFromSuperview]; + } else { + barView.alpha = 0.0; + } + } else { + assert(!barView.destroyOnLayout); // In this case we should not have a visible interfaceState + barView.alpha = 1.0; + totalHeight += BAR_THICKNESS; + barRect.origin.y -= BAR_THICKNESS; + [displayedBars addObject:barView]; + } + } + + if (totalHeight > 0) { + totalHeight -= (BAR_THICKNESS * barsToClip); + } + + if (barsToClip == 0) { + CGRect overlayFrame = self.frame; + CGFloat heightChange = (overlayFrame.size.height - totalHeight); + + overlayFrame.origin.y += heightChange; + overlayFrame.size.height = totalHeight; + self.frame = overlayFrame; + + for (_ASRangeDebugBarView *barView in displayedBars) { + [self offsetYOrigin:-heightChange forView:barView]; + } + } +} + +- (void)setOrigin:(CGPoint)origin forView:(UIView *)view +{ + CGRect newFrame = view.frame; + newFrame.origin = origin; + view.frame = newFrame; +} + +- (void)offsetYOrigin:(CGFloat)offset forView:(UIView *)view +{ + CGRect newFrame = view.frame; + newFrame.origin = CGPointMake(newFrame.origin.x, newFrame.origin.y + offset); + view.frame = newFrame; +} + +- (void)addRangeController:(ASRangeController *)rangeController +{ + for (_ASRangeDebugBarView *rangeView in _rangeControllerViews) { + if (rangeView.rangeController == rangeController) { + return; + } + } + _ASRangeDebugBarView *rangeView = [[_ASRangeDebugBarView alloc] initWithRangeController:rangeController]; + [_rangeControllerViews addObject:rangeView]; + [self addSubview:rangeView]; + + if (!_animating) { + [self layoutToFitAllBarsExcept:1]; + } + + [UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + _animating = YES; + [self layoutToFitAllBarsExcept:0]; + } completion:^(BOOL finished) { + _animating = NO; + }]; +} + +- (void)updateRangeController:(ASRangeController *)controller + withScrollableDirections:(ASScrollDirection)scrollableDirections + scrollDirection:(ASScrollDirection)scrollDirection + rangeMode:(ASLayoutRangeMode)rangeMode + displayTuningParameters:(ASRangeTuningParameters)displayTuningParameters + fetchDataTuningParameters:(ASRangeTuningParameters)fetchDataTuningParameters + interfaceState:(ASInterfaceState)interfaceState; +{ + _ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller]; + + CGRect boundsRect = self.bounds; + CGRect visibleRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, ASRangeTuningParametersZero, scrollableDirections, scrollDirection); + CGRect displayRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, displayTuningParameters, scrollableDirections, scrollDirection); + CGRect fetchDataRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, fetchDataTuningParameters, scrollableDirections, scrollDirection); + + // figure out which is biggest and assume that is full bounds + BOOL displayRangeLargerThanFetch = NO; + CGFloat visibleRatio = 0; + CGFloat displayRatio = 0; + CGFloat fetchDataRatio = 0; + CGFloat leadingDisplayTuningRatio = 0; + CGFloat leadingFetchDataTuningRatio = 0; + + if (!((displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls) == 0)) { + leadingDisplayTuningRatio = displayTuningParameters.leadingBufferScreenfuls / (displayTuningParameters.leadingBufferScreenfuls + displayTuningParameters.trailingBufferScreenfuls); + } + if (!((fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls) == 0)) { + leadingFetchDataTuningRatio = fetchDataTuningParameters.leadingBufferScreenfuls / (fetchDataTuningParameters.leadingBufferScreenfuls + fetchDataTuningParameters.trailingBufferScreenfuls); + } + + if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) { + + if (displayRect.size.height >= fetchDataRect.size.height) { + displayRangeLargerThanFetch = YES; + } else { + displayRangeLargerThanFetch = NO; + } + + if (displayRangeLargerThanFetch) { + visibleRatio = visibleRect.size.height / displayRect.size.height; + displayRatio = 1.0; + fetchDataRatio = fetchDataRect.size.height / displayRect.size.height; + } else { + visibleRatio = visibleRect.size.height / fetchDataRect.size.height; + displayRatio = displayRect.size.height / fetchDataRect.size.height; + fetchDataRatio = 1.0; + } + + } else { + + if (displayRect.size.width >= fetchDataRect.size.width) { + displayRangeLargerThanFetch = YES; + } else { + displayRangeLargerThanFetch = NO; + } + + if (displayRangeLargerThanFetch) { + visibleRatio = visibleRect.size.width / displayRect.size.width; + displayRatio = 1.0; + fetchDataRatio = fetchDataRect.size.width / displayRect.size.width; + } else { + visibleRatio = visibleRect.size.width / fetchDataRect.size.width; + displayRatio = displayRect.size.width / fetchDataRect.size.width; + fetchDataRatio = 1.0; + } + } + + [viewToUpdate updateWithVisibleRatio:visibleRatio + displayRatio:displayRatio + leadingDisplayRatio:leadingDisplayTuningRatio + fetchDataRatio:fetchDataRatio + leadingFetchDataRatio:leadingFetchDataTuningRatio + direction:scrollDirection]; + + [self setNeedsLayout]; +} + +- (_ASRangeDebugBarView *)barViewForRangeController:(ASRangeController *)controller +{ + _ASRangeDebugBarView *rangeControllerBarView = nil; + + for (_ASRangeDebugBarView *rangeView in [[_rangeControllerViews reverseObjectEnumerator] allObjects]) { + // remove barView if its rangeController has been deleted + if (!rangeView.rangeController) { + rangeView.destroyOnLayout = YES; + [self setNeedsLayout]; + } + ASInterfaceState interfaceState = [rangeView.rangeController.dataSource interfaceStateForRangeController:rangeView.rangeController]; + if (!(interfaceState & (ASInterfaceStateVisible | ASInterfaceStateDisplay))) { + [self setNeedsLayout]; + } + + if ([rangeView.rangeController isEqual:controller]) { + rangeControllerBarView = rangeView; + } + } + + return rangeControllerBarView; +} + +#define MIN_VISIBLE_INSET 40 +- (void)rangeDebugOverlayWasPanned:(UIPanGestureRecognizer *)recognizer +{ + CGPoint translation = [recognizer translationInView:recognizer.view]; + CGFloat newCenterX = recognizer.view.center.x + translation.x; + CGFloat newCenterY = recognizer.view.center.y + translation.y; + CGSize boundsSize = recognizer.view.bounds.size; + CGSize superBoundsSize = recognizer.view.superview.bounds.size; + CGFloat minAllowableX = -boundsSize.width / 2.0 + MIN_VISIBLE_INSET; + CGFloat maxAllowableX = superBoundsSize.width + boundsSize.width / 2.0 - MIN_VISIBLE_INSET; + + if (newCenterX > maxAllowableX) { + newCenterX = maxAllowableX; + } else if (newCenterX < minAllowableX) { + newCenterX = minAllowableX; + } + + CGFloat minAllowableY = -boundsSize.height / 2.0 + MIN_VISIBLE_INSET; + CGFloat maxAllowableY = superBoundsSize.height + boundsSize.height / 2.0 - MIN_VISIBLE_INSET; + + if (newCenterY > maxAllowableY) { + newCenterY = maxAllowableY; + } else if (newCenterY < minAllowableY) { + newCenterY = minAllowableY; + } + + recognizer.view.center = CGPointMake(newCenterX, newCenterY); + [recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view]; +} + +@end + +#pragma mark _ASRangeDebugBarView + +@implementation _ASRangeDebugBarView +{ + ASTextNode *_debugText; + ASTextNode *_leftDebugText; + ASTextNode *_rightDebugText; + ASImageNode *_visibleRect; + ASImageNode *_displayRect; + ASImageNode *_fetchDataRect; + CGFloat _visibleRatio; + CGFloat _displayRatio; + CGFloat _fetchDataRatio; + CGFloat _leadingDisplayRatio; + CGFloat _leadingFetchDataRatio; + ASScrollDirection _scrollDirection; + BOOL _firstLayoutOfRects; +} + +- (instancetype)initWithRangeController:(ASRangeController *)rangeController +{ + self = [super initWithFrame:CGRectZero]; + if (self) { + _firstLayoutOfRects = YES; + _rangeController = rangeController; + _debugText = [self createDebugTextNode]; + _leftDebugText = [self createDebugTextNode]; + _rightDebugText = [self createDebugTextNode]; + _fetchDataRect = [self createRangeNodeWithColor:[UIColor orangeColor]]; + _displayRect = [self createRangeNodeWithColor:[UIColor yellowColor]]; + _visibleRect = [self createRangeNodeWithColor:[UIColor greenColor]]; + } + + return self; +} + +#define HORIZONTAL_INSET 10 +- (void)layoutSubviews +{ + [super layoutSubviews]; + + CGSize boundsSize = self.bounds.size; + CGFloat subCellHeight = 9.0; + [self setBarDebugLabelsWithSize:subCellHeight]; + [self setBarSubviewOrder]; + + CGRect rect = CGRectIntegral(CGRectMake(0, 0, boundsSize.width, floorf(boundsSize.height / 2.0))); + rect.size = [_debugText measure:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)]; + rect.origin.x = (boundsSize.width - rect.size.width) / 2.0; + _debugText.frame = rect; + rect.origin.y += rect.size.height; + + rect.origin.x = 0; + rect.size = CGSizeMake(HORIZONTAL_INSET, boundsSize.height / 2.0); + _leftDebugText.frame = rect; + + rect.origin.x = boundsSize.width - HORIZONTAL_INSET; + _rightDebugText.frame = rect; + + CGFloat visibleDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _visibleRatio; + CGFloat displayDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _displayRatio; + CGFloat fetchDataDimension = (boundsSize.width - 2 * HORIZONTAL_INSET) * _fetchDataRatio; + CGFloat visiblePoint = 0; + CGFloat displayPoint = 0; + CGFloat fetchDataPoint = 0; + + BOOL displayLargerThanFetchData = (_displayRatio == 1.0) ? YES : NO; + + if (ASScrollDirectionContainsLeft(_scrollDirection) || ASScrollDirectionContainsUp(_scrollDirection)) { + + if (displayLargerThanFetchData) { + visiblePoint = (displayDimension - visibleDimension) * _leadingDisplayRatio; + fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio; + } else { + visiblePoint = (fetchDataDimension - visibleDimension) * _leadingFetchDataRatio; + displayPoint = visiblePoint - (displayDimension - visibleDimension) * _leadingDisplayRatio; + } + } else if (ASScrollDirectionContainsRight(_scrollDirection) || ASScrollDirectionContainsDown(_scrollDirection)) { + + if (displayLargerThanFetchData) { + visiblePoint = (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio); + fetchDataPoint = visiblePoint - (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio); + } else { + visiblePoint = (fetchDataDimension - visibleDimension) * (1 - _leadingFetchDataRatio); + displayPoint = visiblePoint - (displayDimension - visibleDimension) * (1 - _leadingDisplayRatio); + } + } + + BOOL animate = !_firstLayoutOfRects; + [UIView animateWithDuration:animate ? 0.3 : 0.0 delay:0.0 options:UIViewAnimationOptionLayoutSubviews animations:^{ + _visibleRect.frame = CGRectMake(HORIZONTAL_INSET + visiblePoint, rect.origin.y, visibleDimension, subCellHeight); + _displayRect.frame = CGRectMake(HORIZONTAL_INSET + displayPoint, rect.origin.y, displayDimension, subCellHeight); + _fetchDataRect.frame = CGRectMake(HORIZONTAL_INSET + fetchDataPoint, rect.origin.y, fetchDataDimension, subCellHeight); + } completion:^(BOOL finished) {}]; + + if (!animate) { + _visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 0; + [UIView animateWithDuration:0.3 animations:^{ + _visibleRect.alpha = _displayRect.alpha = _fetchDataRect.alpha = 1; + }]; + } + + _firstLayoutOfRects = NO; +} + +- (void)updateWithVisibleRatio:(CGFloat)visibleRatio + displayRatio:(CGFloat)displayRatio + leadingDisplayRatio:(CGFloat)leadingDisplayRatio + fetchDataRatio:(CGFloat)fetchDataRatio + leadingFetchDataRatio:(CGFloat)leadingFetchDataRatio + direction:(ASScrollDirection)scrollDirection +{ + _visibleRatio = visibleRatio; + _displayRatio = displayRatio; + _leadingDisplayRatio = leadingDisplayRatio; + _fetchDataRatio = fetchDataRatio; + _leadingFetchDataRatio = leadingFetchDataRatio; + _scrollDirection = scrollDirection; + + [self setNeedsLayout]; +} + +- (void)setBarSubviewOrder +{ + if (_fetchDataRatio == 1.0) { + [self sendSubviewToBack:_fetchDataRect.view]; + } else { + [self sendSubviewToBack:_displayRect.view]; + } + + [self bringSubviewToFront:_visibleRect.view]; +} + +- (void)setBarDebugLabelsWithSize:(CGFloat)size +{ + if (!_debugString) { + _debugString = [[_rangeController dataSource] nameForRangeControllerDataSource]; + } + if (_debugString) { + _debugText.attributedString = [_ASRangeDebugBarView whiteAttributedStringFromString:_debugString withSize:size]; + } + + if (ASScrollDirectionContainsVerticalDirection(_scrollDirection)) { + _leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▲" withSize:size]; + _rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▼" withSize:size]; + } else if (ASScrollDirectionContainsHorizontalDirection(_scrollDirection)) { + _leftDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"◀︎" withSize:size]; + _rightDebugText.attributedText = [_ASRangeDebugBarView whiteAttributedStringFromString:@"▶︎" withSize:size]; + } + + _leftDebugText.hidden = (_scrollDirection != ASScrollDirectionLeft && _scrollDirection != ASScrollDirectionUp); + _rightDebugText.hidden = (_scrollDirection != ASScrollDirectionRight && _scrollDirection != ASScrollDirectionDown); +} + +- (ASTextNode *)createDebugTextNode +{ + ASTextNode *label = [[ASTextNode alloc] init]; + [self addSubnode:label]; + return label; +} + +#define RANGE_BAR_CORNER_RADIUS 3 +#define RANGE_BAR_BORDER_WIDTH 1 +- (ASImageNode *)createRangeNodeWithColor:(UIColor *)color +{ + ASImageNode *rangeBarImageNode = [[ASImageNode alloc] init]; + rangeBarImageNode.image = [UIImage as_resizableRoundedImageWithCornerRadius:RANGE_BAR_CORNER_RADIUS + cornerColor:[UIColor clearColor] + fillColor:[color colorWithAlphaComponent:0.5] + borderColor:[[UIColor blackColor] colorWithAlphaComponent:0.9] + borderWidth:RANGE_BAR_BORDER_WIDTH + roundedCorners:UIRectCornerAllCorners + scale:[[UIScreen mainScreen] scale]]; + [self addSubnode:rangeBarImageNode]; + + return rangeBarImageNode; +} + ++ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size +{ + NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], + NSFontAttributeName : [UIFont systemFontOfSize:size]}; + return [[NSAttributedString alloc] initWithString:string attributes:attributes]; +} + +@end diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index d682e07b9d..c46b1d99b5 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -134,6 +134,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *> *)completedNodes; +- (NSString *)nameForRangeControllerDataSource; + @end /** diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index d82073779d..e71e990123 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -11,13 +11,16 @@ #import "ASRangeController.h" #import "ASAssert.h" -#import "ASWeakSet.h" +#import "ASCellNode.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" #import "ASMultidimensionalArrayUtils.h" #import "ASInternalHelpers.h" +#import "ASMultiDimensionalArrayUtils.h" +#import "ASWeakSet.h" + #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASCellNode.h" +#import "AsyncDisplayKit+Debug.h" #define AS_RANGECONTROLLER_LOG_UPDATE_FREQ 0 @@ -64,6 +67,10 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; #endif + if ([ASRangeController shouldShowRangeDebugOverlay]) { + [self addRangeControllerToRangeDebugOverlay]; + } + return self; } @@ -335,6 +342,25 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive; } } + // TODO: This code is for debugging only, but would be great to clean up with a delegate method implementation. + if ([ASRangeController shouldShowRangeDebugOverlay]) { + ASScrollDirection scrollableDirections = ASScrollDirectionUp | ASScrollDirectionDown; + if ([_dataSource isKindOfClass:NSClassFromString(@"ASCollectionView")]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + scrollableDirections = (ASScrollDirection)[_dataSource performSelector:@selector(scrollableDirections)]; +#pragma clang diagnostic pop + } + + [self updateRangeController:self + withScrollableDirections:scrollableDirections + scrollDirection:scrollDirection + rangeMode:rangeMode + displayTuningParameters:parametersDisplay + fetchDataTuningParameters:parametersFetchData + interfaceState:selfInterfaceState]; + } + _rangeIsValid = YES; #if ASRangeControllerLoggingEnabled diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m index 74a5f53cc8..c506a3faa9 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m @@ -38,6 +38,7 @@ _pagerNode = [[ASPagerNode alloc] init]; _pagerNode.dataSource = self; + [ASRangeController setShouldShowRangeDebugOverlay:YES]; // Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView. //_pagerNode.delegate = self; From aedb83cfe3dd15b9442efca5c968d717ba548660 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 11 Aug 2016 11:48:17 -0700 Subject: [PATCH 15/50] [ASDataController] Remove Unused Batch Update Processing Code (#2054) * [ASDataController] Remove dead batch updating code * Address PR comments --- AsyncDisplayKit/Details/ASDataController.mm | 455 +++++++++----------- 1 file changed, 209 insertions(+), 246 deletions(-) diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index f738b55e01..e079f74b3c 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -49,8 +49,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; std::vector _itemCountsFromDataSource; // Main thread only. ASMainSerialQueue *_mainSerialQueue; - - NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. + dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. @@ -62,8 +61,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; BOOL _delegateDidDeleteSections; } -@property (nonatomic, assign) NSUInteger batchUpdateCounter; - @end @implementation ASDataController @@ -87,15 +84,11 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; _mainSerialQueue = [[ASMainSerialQueue alloc] init]; - _pendingEditCommandBlocks = [NSMutableArray array]; - const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(_editingTransactionQueue, &kASDataControllerEditingQueueKey, &kASDataControllerEditingQueueContext, NULL); _editingTransactionGroup = dispatch_group_create(); - _batchUpdateCounter = 0; - return self; } @@ -395,60 +388,55 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)_reloadDataWithAnimationOptions:(ASDataControllerAnimationOptions)animationOptions synchronously:(BOOL)synchronously completion:(void (^)())completion { + ASDisplayNodeAssertMainThread(); + _initialReloadDataHasBeenCalled = YES; - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - [self invalidateDataSourceItemCounts]; - NSUInteger sectionCount = [self itemCountsFromDataSource].size(); - NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; - NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; + [self invalidateDataSourceItemCounts]; + NSUInteger sectionCount = [self itemCountsFromDataSource].size(); + NSIndexSet *sectionIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)]; + NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; + + // Allow subclasses to perform setup before going into the edit transaction + [self prepareForReloadData]; + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + LOG(@"Edit Transaction - reloadData"); - // Allow subclasses to perform setup before going into the edit transaction - [self prepareForReloadData]; + // Remove everything that existed before the reload, now that we're ready to insert replacements + NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; + NSUInteger editingNodesSectionCount = editingNodes.count; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - reloadData"); - - // Remove everything that existed before the reload, now that we're ready to insert replacements - NSMutableArray *editingNodes = _editingNodes[ASDataControllerRowNodeKind]; - NSUInteger editingNodesSectionCount = editingNodes.count; - - if (editingNodesSectionCount) { - NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)]; - [self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions]; - [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; - } - - [self willReloadData]; - - // Insert empty sections - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; - for (int i = 0; i < sectionCount; i++) { - [sections addObject:[[NSMutableArray alloc] init]]; - } - [self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions]; - - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - - if (completion) { - [_mainSerialQueue performBlockOnMainThread:completion]; - } - }); - if (synchronously) { - [self waitUntilAllUpdatesAreCommitted]; + if (editingNodesSectionCount) { + NSIndexSet *indexSet = [[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodesSectionCount)]; + [self _deleteNodesAtIndexPaths:ASIndexPathsForTwoDimensionalArray(editingNodes) withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; } - }]; + + [self willReloadData]; + + // Insert empty sections + NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; + for (int i = 0; i < sectionCount; i++) { + [sections addObject:[[NSMutableArray alloc] init]]; + } + [self _insertSections:sections atIndexSet:sectionIndexSet withAnimationOptions:animationOptions]; + + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; + + if (completion) { + [_mainSerialQueue performBlockOnMainThread:completion]; + } + }); + if (synchronously) { + [self waitUntilAllUpdatesAreCommitted]; + } } - (void)waitUntilAllUpdatesAreCommitted { ASDisplayNodeAssertMainThread(); - ASDisplayNodeAssert(_batchUpdateCounter == 0, @"Should not be called between beginUpdate or endUpdate"); - - // This should never be called in a batch update, return immediately therefore - if (_batchUpdateCounter > 0) { return; } dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); @@ -516,10 +504,20 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)beginUpdates { + ASDisplayNodeAssertMainThread(); + // TODO: make this -waitUntilAllUpdatesAreCommitted? dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - // Begin queuing up edit calls that happen on the main thread. - // This will prevent further operations from being scheduled on _editingTransactionQueue. - _batchUpdateCounter++; + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [_mainSerialQueue performBlockOnMainThread:^{ + // Deep copy _completedNodes to _externalCompletedNodes. + // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. + _externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); + + LOG(@"beginUpdates - begin updates call to delegate"); + [_delegate dataControllerBeginUpdates:self]; + }]; + }); } - (void)endUpdates @@ -529,118 +527,72 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - _batchUpdateCounter--; + LOG(@"endUpdatesWithCompletion - beginning"); + ASDisplayNodeAssertMainThread(); - if (_batchUpdateCounter == 0) { - LOG(@"endUpdatesWithCompletion - beginning"); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // Deep copy _completedNodes to _externalCompletedNodes. - // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. - _externalCompletedNodes = ASTwoDimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); - - LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); - [_delegate dataControllerBeginUpdates:self]; - }]; - }); - - // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. - // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction. - LOG(@"endUpdatesWithCompletion - %zd blocks to run", _pendingEditCommandBlocks.count); - NSUInteger i = 0; - for (dispatch_block_t block in _pendingEditCommandBlocks) { - LOG(@"endUpdatesWithCompletion - running block #%zd", i); - block(); - i += 1; - } - [_pendingEditCommandBlocks removeAllObjects]; - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - // 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]; - }]; - }); - } -} - -/** - * Queues the given operation until an `endUpdates` synchronize update is completed. - * - * If this method is called outside of a begin/endUpdates batch update, the block is - * executed immediately. - */ -- (void)performEditCommandWithBlock:(void (^)(void))block -{ - // This method needs to block the thread and synchronously perform the operation if we are not - // queuing commands for begin/endUpdates. If we are queuing, it needs to return immediately. - if (!_initialReloadDataHasBeenCalled) { - return; - } - - if (block == nil) { - return; - } - - // If we have never performed a reload, there is no value in executing edit operations as the initial - // reload will directly re-query the latest state of the datasource - so completely skip the block in this case. - if (_batchUpdateCounter == 0) { - block(); - } else { - [_pendingEditCommandBlocks addObject:block]; - } + // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. + // Each subsequent command in the queue will also wait on the full asynchronous completion of the prior command's edit transaction. + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [_mainSerialQueue performBlockOnMainThread:^{ + // 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]; + }]; + }); } #pragma mark - Section Editing (External API) - (void)insertSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssertMainThread(); + LOG(@"Edit Command - insertSections: %@", sections); + if (!_initialReloadDataHasBeenCalled) { + return; + } - LOG(@"Edit Command - insertSections: %@", sections); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + + NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; + + [self prepareForInsertSections:sections]; + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willInsertSections:sections]; + + LOG(@"Edit Transaction - insertSections: %@", sections); + NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; + for (NSUInteger i = 0; i < sections.count; i++) { + [sectionArray addObject:[NSMutableArray array]]; + } + + [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sections]; - - [self prepareForInsertSections:sections]; - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertSections:sections]; - - LOG(@"Edit Transaction - insertSections: %@", sections); - NSMutableArray *sectionArray = [NSMutableArray arrayWithCapacity:sections.count]; - for (NSUInteger i = 0; i < sections.count; i++) { - [sectionArray addObject:[NSMutableArray array]]; - } - - [self _insertSections:sectionArray atIndexSet:sections withAnimationOptions:animationOptions]; - - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); - }]; + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; + }); } - (void)deleteSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - deleteSections: %@", sections); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteSections:sections]; + ASDisplayNodeAssertMainThread(); + LOG(@"Edit Command - deleteSections: %@", sections); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // remove elements - LOG(@"Edit Transaction - deleteSections: %@", sections); - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); - - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; - }); - }]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willDeleteSections:sections]; + + // remove elements + LOG(@"Edit Transaction - deleteSections: %@", sections); + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(_editingNodes[ASDataControllerRowNodeKind], sections); + + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + [self _deleteSectionsAtIndexSet:sections withAnimationOptions:animationOptions]; + }); } - (void)reloadSections:(NSIndexSet *)sections withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -650,33 +602,35 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - moveSection"); + ASDisplayNodeAssertMainThread(); + LOG(@"Edit Command - moveSection"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willMoveSection:section toSection:newSection]; + if (!_initialReloadDataHasBeenCalled) { + return; + } - // remove elements - - LOG(@"Edit Transaction - moveSection"); - NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; - NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willMoveSection:section toSection:newSection]; - // update the section of indexpaths - NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - for (NSIndexPath *indexPath in indexPaths) { - NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection]; - [updatedIndexPaths addObject:updatedIndexPath]; - } + // remove elements + + LOG(@"Edit Transaction - moveSection"); + NSMutableArray *editingRows = _editingNodes[ASDataControllerRowNodeKind]; + NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet(editingRows, [NSIndexSet indexSetWithIndex:section]); + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(editingRows, indexPaths); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; - // Don't re-calculate size for moving - [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; - }); - }]; + // update the section of indexpaths + NSMutableArray *updatedIndexPaths = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + for (NSIndexPath *indexPath in indexPaths) { + NSIndexPath *updatedIndexPath = [NSIndexPath indexPathForItem:indexPath.item inSection:newSection]; + [updatedIndexPaths addObject:updatedIndexPath]; + } + + // Don't re-calculate size for moving + [self _insertNodes:nodes atIndexPaths:updatedIndexPaths withAnimationOptions:animationOptions]; + }); } @@ -736,59 +690,64 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)insertRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - insertRows: %@", indexPaths); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // Sort indexPath to avoid messing up the index when inserting in several batches - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; + LOG(@"Edit Command - insertRows: %@", indexPaths); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - - for (NSIndexPath *indexPath in sortedIndexPaths) { - ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; - ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; - [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock - indexPath:indexPath - constrainedSize:constrainedSize - environmentTraitCollection:environmentTraitCollection]]; - } + // Sort indexPath to avoid messing up the index when inserting in several batches + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + NSMutableArray *contexts = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; - [self prepareForInsertRowsAtIndexPaths:indexPaths]; + id environment = [self.environmentDelegate dataControllerEnvironment]; + ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; + + for (NSIndexPath *indexPath in sortedIndexPaths) { + ASCellNodeBlock nodeBlock = [_dataSource dataController:self nodeBlockAtIndexPath:indexPath]; + ASSizeRange constrainedSize = [self constrainedSizeForNodeOfKind:ASDataControllerRowNodeKind atIndexPath:indexPath]; + [contexts addObject:[[ASIndexedNodeContext alloc] initWithNodeBlock:nodeBlock + indexPath:indexPath + constrainedSize:constrainedSize + environmentTraitCollection:environmentTraitCollection]]; + } - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willInsertRowsAtIndexPaths:indexPaths]; + [self prepareForInsertRowsAtIndexPaths:indexPaths]; - LOG(@"Edit Transaction - insertRows: %@", indexPaths); - [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; - }); - }]; + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willInsertRowsAtIndexPaths:indexPaths]; + + LOG(@"Edit Transaction - insertRows: %@", indexPaths); + [self _batchLayoutAndInsertNodesFromContexts:contexts withAnimationOptions:animationOptions]; + }); } - (void)deleteRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - deleteRows: %@", indexPaths); + ASDisplayNodeAssertMainThread(); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - // Sort indexPath in order to avoid messing up the index when deleting in several batches. - // FIXME: Shouldn't deletes be sorted in descending order? - NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; + if (!_initialReloadDataHasBeenCalled) { + return; + } - [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; + LOG(@"Edit Command - deleteRows: %@", indexPaths); - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [self willDeleteRowsAtIndexPaths:sortedIndexPaths]; + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + + // Sort indexPath in order to avoid messing up the index when deleting in several batches. + // FIXME: Shouldn't deletes be sorted in descending order? + NSArray *sortedIndexPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)]; - LOG(@"Edit Transaction - deleteRows: %@", indexPaths); - [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; - }); - }]; + [self prepareForDeleteRowsAtIndexPaths:sortedIndexPaths]; + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [self willDeleteRowsAtIndexPaths:sortedIndexPaths]; + + LOG(@"Edit Transaction - deleteRows: %@", indexPaths); + [self _deleteNodesAtIndexPaths:sortedIndexPaths withAnimationOptions:animationOptions]; + }); } - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions @@ -798,22 +757,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)relayoutAllNodes { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - relayoutRows"); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // Can't relayout right away because _completedNodes may not be up-to-date, - // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes - // (see _layoutNodes:atIndexPaths:withAnimationOptions:). - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - [_mainSerialQueue performBlockOnMainThread:^{ - for (NSString *kind in _completedNodes) { - [self _relayoutNodesOfKind:kind]; - } - }]; - }); - }]; + LOG(@"Edit Command - relayoutRows"); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + + // Can't relayout right away because _completedNodes may not be up-to-date, + // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes + // (see _layoutNodes:atIndexPaths:withAnimationOptions:). + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + [_mainSerialQueue performBlockOnMainThread:^{ + for (NSString *kind in _completedNodes) { + [self _relayoutNodesOfKind:kind]; + } + }]; + }); } - (void)_relayoutNodesOfKind:(NSString *)kind @@ -840,22 +801,24 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath withAnimationOptions:(ASDataControllerAnimationOptions)animationOptions { - [self performEditCommandWithBlock:^{ - ASDisplayNodeAssertMainThread(); - LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); - dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); - - dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ - LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); - NSArray *indexPaths = @[indexPath]; - NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); - [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + ASDisplayNodeAssertMainThread(); + if (!_initialReloadDataHasBeenCalled) { + return; + } - // Don't re-calculate size for moving - NSArray *newIndexPaths = @[newIndexPath]; - [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; - }); - }]; + LOG(@"Edit Command - moveRow: %@ > %@", indexPath, newIndexPath); + dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER); + + dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ + LOG(@"Edit Transaction - moveRow: %@ > %@", indexPath, newIndexPath); + NSArray *indexPaths = @[indexPath]; + NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_editingNodes[ASDataControllerRowNodeKind], indexPaths); + [self _deleteNodesAtIndexPaths:indexPaths withAnimationOptions:animationOptions]; + + // Don't re-calculate size for moving + NSArray *newIndexPaths = @[newIndexPath]; + [self _insertNodes:nodes atIndexPaths:newIndexPaths withAnimationOptions:animationOptions]; + }); } #pragma mark - Data Querying (Subclass API) From 075dd732809c6410ef2db722237f2b5e5a9b9402 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 11 Aug 2016 12:46:26 -0700 Subject: [PATCH 16/50] Parse RFC 3339 date time strings around 7-8 times faster (#2059) --- examples/ASDKgram/Sample/Utilities.m | 104 ++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/examples/ASDKgram/Sample/Utilities.m b/examples/ASDKgram/Sample/Utilities.m index b5a144a0be..556a3a2d2c 100644 --- a/examples/ASDKgram/Sample/Utilities.m +++ b/examples/ASDKgram/Sample/Utilities.m @@ -21,6 +21,85 @@ #define StrokeRoundedImages 0 +#define IsDigit(v) (v >= '0' && v <= '9') + +static time_t parseRfc3339ToTimeT(const char *string) +{ + int dy, dm, dd; + int th, tm, ts; + int oh, om, osign; + char current; + + if (!string) + return (time_t)0; + + // date + if (sscanf(string, "%04d-%02d-%02d", &dy, &dm, &dd) == 3) { + string += 10; + + if (*string++ != 'T') + return (time_t)0; + + // time + if (sscanf(string, "%02d:%02d:%02d", &th, &tm, &ts) == 3) { + string += 8; + + current = *string; + + // optional: second fraction + if (current == '.') { + ++string; + while(IsDigit(*string)) + ++string; + + current = *string; + } + + if (current == 'Z') { + oh = om = 0; + osign = 1; + } else if (current == '-') { + ++string; + if (sscanf(string, "%02d:%02d", &oh, &om) != 2) + return (time_t)0; + osign = -1; + } else if (current == '+') { + ++string; + if (sscanf(string, "%02d:%02d", &oh, &om) != 2) + return (time_t)0; + osign = 1; + } else { + return (time_t)0; + } + + struct tm timeinfo; + timeinfo.tm_wday = timeinfo.tm_yday = 0; + timeinfo.tm_zone = NULL; + timeinfo.tm_isdst = -1; + + timeinfo.tm_year = dy - 1900; + timeinfo.tm_mon = dm - 1; + timeinfo.tm_mday = dd; + + timeinfo.tm_hour = th; + timeinfo.tm_min = tm; + timeinfo.tm_sec = ts; + + // convert to utc + return timegm(&timeinfo) - (((oh * 60 * 60) + (om * 60)) * osign); + } + } + + return (time_t)0; +} + +static NSDate *parseRfc3339ToNSDate(NSString *rfc3339DateTimeString) +{ + time_t t = parseRfc3339ToTimeT([rfc3339DateTimeString cStringUsingEncoding:NSUTF8StringEncoding]); + return [NSDate dateWithTimeIntervalSince1970:t]; +} + + @implementation UIColor (Additions) + (UIColor *)darkBlueColor @@ -142,26 +221,15 @@ @implementation NSString (Additions) -// Returns a user-visible date time string that corresponds to the -// specified RFC 3339 date time string. Note that this does not handle -// all possible RFC 3339 date time strings, just one of the most common -// styles. +/* + * Returns a user-visible date time string that corresponds to the + * specified RFC 3339 date time string. Note that this does not handle + * all possible RFC 3339 date time strings, just one of the most common + * styles. + */ + (NSDate *)userVisibleDateTimeStringForRFC3339DateTimeString:(NSString *)rfc3339DateTimeString { - NSDateFormatter * rfc3339DateFormatter; - NSLocale * enUSPOSIXLocale; - - // Convert the RFC 3339 date time string to an NSDate. - - rfc3339DateFormatter = [[NSDateFormatter alloc] init]; - - enUSPOSIXLocale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; - - [rfc3339DateFormatter setLocale:enUSPOSIXLocale]; - [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ'"]; - [rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; - - return [rfc3339DateFormatter dateFromString:rfc3339DateTimeString]; + return parseRfc3339ToNSDate(rfc3339DateTimeString); } + (NSString *)elapsedTimeStringSinceDate:(NSString *)uploadDateString From 39cb188b9e7db73750bf7d2f703ac0cecd96fc9c Mon Sep 17 00:00:00 2001 From: Flo Date: Fri, 12 Aug 2016 21:06:02 +0200 Subject: [PATCH 17/50] [ASVideoNode] Improve playerStatus behaviour and image generation (#2024) * [ASVideoNode] Use the videoComposition when generating images. * [ASVideoNode] Improve playerState behaviour. * [ASVideoNode] Use KVO on _player.rate to determine the playerState. --- AsyncDisplayKit/ASVideoNode.mm | 102 ++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 32 deletions(-) diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index e6f44993ea..4fd05f06ac 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -37,6 +37,7 @@ static void *ASVideoNodeContext = &ASVideoNodeContext; static NSString * const kPlaybackLikelyToKeepUpKey = @"playbackLikelyToKeepUp"; static NSString * const kplaybackBufferEmpty = @"playbackBufferEmpty"; static NSString * const kStatus = @"status"; +static NSString * const kRate = @"rate"; @interface ASVideoNode () { @@ -208,6 +209,25 @@ static NSString * const kStatus = @"status"; [notificationCenter removeObserver:self name:AVPlayerItemNewErrorLogEntryNotification object:playerItem]; } +- (void)addPlayerObservers:(AVPlayer *)player +{ + if (player == nil) { + return; + } + + [player addObserver:self forKeyPath:kRate options:NSKeyValueObservingOptionNew context:ASVideoNodeContext]; +} + +- (void) removePlayerObservers:(AVPlayer *)player +{ + @try { + [player removeObserver:self forKeyPath:kRate context:ASVideoNodeContext]; + } + @catch (NSException * __unused exception) { + NSLog(@"Unnecessary KVO removal"); + } +} + - (void)layout { [super layout]; @@ -267,6 +287,7 @@ static NSString * const kStatus = @"status"; AVAssetImageGenerator *previewImageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset]; previewImageGenerator.appliesPreferredTrackTransform = YES; + previewImageGenerator.videoComposition = _videoComposition; [previewImageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:imageTime]] completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) { @@ -291,29 +312,47 @@ static NSString * const kStatus = @"status"; { ASDN::MutexLocker l(__instanceLock__); - if (object != _currentPlayerItem) { - return; - } - - if ([keyPath isEqualToString:kStatus]) { - if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { - self.playerState = ASVideoNodePlayerStateReadyToPlay; - // If we don't yet have a placeholder image update it now that we should have data available for it - if (self.image == nil && self.URL == nil) { - [self generatePlaceholderImage]; + if (object == _currentPlayerItem) { + if ([keyPath isEqualToString:kStatus]) { + if ([change[NSKeyValueChangeNewKey] integerValue] == AVPlayerItemStatusReadyToPlay) { + if (self.playerState != ASVideoNodePlayerStatePlaying) { + self.playerState = ASVideoNodePlayerStateReadyToPlay; + } + // If we don't yet have a placeholder image update it now that we should have data available for it + if (self.image == nil && self.URL == nil) { + [self generatePlaceholderImage]; + } + } + } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { + BOOL likelyToKeepUp = [change[NSKeyValueChangeNewKey] boolValue]; + if (likelyToKeepUp && self.playerState == ASVideoNodePlayerStatePlaying) { + return; + } + if (!likelyToKeepUp) { + self.playerState = ASVideoNodePlayerStateLoading; + } else if (self.playerState != ASVideoNodePlayerStateFinished) { + self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; + } + if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || likelyToKeepUp) && ASInterfaceStateIncludesVisible(self.interfaceState)) { + if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { + [_delegate videoNodeDidRecoverFromStall:self]; + } + [self play]; // autoresume after buffer catches up + } + } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { + if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { + self.playerState = ASVideoNodePlayerStateLoading; } } - } else if ([keyPath isEqualToString:kPlaybackLikelyToKeepUpKey]) { - self.playerState = ASVideoNodePlayerStatePlaybackLikelyToKeepUpButNotPlaying; - if (_shouldBePlaying && (_shouldAggressivelyRecoverFromStall || [change[NSKeyValueChangeNewKey] boolValue]) && ASInterfaceStateIncludesVisible(self.interfaceState)) { - if (self.playerState == ASVideoNodePlayerStateLoading && _delegateFlags.delegateVideoNodeDidRecoverFromStall) { - [_delegate videoNodeDidRecoverFromStall:self]; + } else if (object == _player) { + if ([keyPath isEqualToString:kRate]) { + if ([change[NSKeyValueChangeNewKey] floatValue] == 0.0) { + if (self.playerState == ASVideoNodePlayerStatePlaying) { + self.playerState = ASVideoNodePlayerStatePaused; + } + } else { + self.playerState = ASVideoNodePlayerStatePlaying; } - [self play]; // autoresume after buffer catches up - } - } else if ([keyPath isEqualToString:kplaybackBufferEmpty]) { - if (_shouldBePlaying && [change[NSKeyValueChangeNewKey] boolValue] == true && ASInterfaceStateIncludesVisible(self.interfaceState)) { - self.playerState = ASVideoNodePlayerStateLoading; } } } @@ -345,14 +384,11 @@ static NSString * const kStatus = @"status"; ASDN::MutexLocker l(__instanceLock__); AVAsset *asset = self.asset; // Return immediately if the asset is nil; - if (asset == nil || self.playerState == ASVideoNodePlayerStateInitialLoading) { + if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) { return; } - // FIXME: Nothing appears to prevent this method from sending the delegate notification / calling load on the asset - // multiple times, even after the asset is fully loaded and ready to play. There should probably be a playerState - // for NotLoaded or such, besides Unknown, so this can be easily checked before proceeding. - self.playerState = ASVideoNodePlayerStateInitialLoading; + self.playerState = ASVideoNodePlayerStateLoading; if (_delegateFlags.delegateVideoNodeDidStartInitialLoading) { [_delegate videoNodeDidStartInitialLoading:self]; } @@ -396,6 +432,7 @@ static NSString * const kStatus = @"status"; self.player = nil; self.currentItem = nil; + self.playerState = ASVideoNodePlayerStateUnknown; } } @@ -603,12 +640,6 @@ static NSString * const kStatus = @"status"; [_player play]; _shouldBePlaying = YES; - - if (![self ready]) { - self.playerState = ASVideoNodePlayerStateLoading; - } else { - self.playerState = ASVideoNodePlayerStatePlaying; - } } - (BOOL)ready @@ -622,7 +653,6 @@ static NSString * const kStatus = @"status"; if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { return; } - self.playerState = ASVideoNodePlayerStatePaused; [_player pause]; _shouldBePlaying = NO; } @@ -731,9 +761,16 @@ static NSString * const kStatus = @"status"; - (void)setPlayer:(AVPlayer *)player { ASDN::MutexLocker l(__instanceLock__); + + [self removePlayerObservers:_player]; + _player = player; player.muted = _muted; ((AVPlayerLayer *)_playerNode.layer).player = player; + + if (player != nil) { + [self addPlayerObservers:player]; + } } - (BOOL)shouldBePlaying @@ -755,6 +792,7 @@ static NSString * const kStatus = @"status"; [_player removeTimeObserver:_timeObserver]; _timeObserver = nil; [self removePlayerItemObservers:_currentPlayerItem]; + [self removePlayerObservers:_player]; } @end From 2c9e51e8f7d2b7c963be0200fce1e02de4f34f7f Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 12 Aug 2016 12:07:00 -0700 Subject: [PATCH 18/50] Add support for textContainerInset to ASTextNode (ala UITextView) (#2062) * Add support for textContainerInset to ASTextNode (ala UITextView) * Better comment, parens to increase readability. Thanks @schneider! * Add textContainerInset snapshot test. --- AsyncDisplayKit/ASTextNode+Beta.h | 8 +++ AsyncDisplayKit/ASTextNode.mm | 56 ++++++++++++++++-- .../ASTextNodeSnapshotTests.m | 35 +++++++++++ .../testTextContainerInset@2x.png | Bin 0 -> 2651 bytes 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 AsyncDisplayKitTests/ASTextNodeSnapshotTests.m create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/AsyncDisplayKit/ASTextNode+Beta.h index 899b7d2175..1e6dc38765 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/AsyncDisplayKit/ASTextNode+Beta.h @@ -31,6 +31,14 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *_Nullable attributedString); +/** + @abstract Text margins for text laid out in the text node. + @discussion defaults to UIEdgeInsetsZero. + This property can be useful for handling text which does not fit within the view by default. An example: like UILabel, + ASTextNode will clip the left and right of the string "judar" if it's rendered in an italicised font. + */ +@property (nonatomic, assign) UIEdgeInsets textContainerInset; + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index e4edbbd99d..b658be8a4e 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -48,6 +48,8 @@ struct ASTextNodeDrawParameter { UIColor *_cachedShadowUIColor; CGFloat _shadowOpacity; CGFloat _shadowRadius; + + UIEdgeInsets _textContainerInset; NSArray *_exclusionPaths; @@ -213,7 +215,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASDN::MutexLocker l(__instanceLock__); if (_renderer == nil) { - CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size; + CGSize constrainedSize; + if (_constrainedSize.width != -INFINITY) { + constrainedSize = _constrainedSize; + } else { + constrainedSize = bounds.size; + constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); + constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); + } + _renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] constrainedSize:constrainedSize]; } @@ -279,6 +289,24 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; #pragma mark - Layout and Sizing +- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset +{ + ASDN::MutexLocker l(__instanceLock__); + + BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset); + if (needsUpdate) { + _textContainerInset = textContainerInset; + [self invalidateCalculatedLayout]; + [self setNeedsLayout]; + } +} + +- (UIEdgeInsets)textContainerInset +{ + ASDN::MutexLocker l(__instanceLock__); + return _textContainerInset; +} + - (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize { ASDN::MutexLocker l(__instanceLock__); @@ -291,6 +319,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; // a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated. CGSize rendererConstrainedSize = _renderer.constrainedSize; + //inset bounds + boundsSize.width -= _textContainerInset.left + _textContainerInset.right; + boundsSize.height -= _textContainerInset.top + _textContainerInset.bottom; + if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) { return NO; } else { @@ -321,9 +353,14 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; if (layout != nil) { ASDN::MutexLocker l(__instanceLock__); - if (CGSizeEqualToSize(_constrainedSize, layout.size) == NO) { - _constrainedSize = layout.size; - _renderer.constrainedSize = layout.size; + CGSize layoutSize = layout.size; + //Apply textContainerInset + layoutSize.width -= (_textContainerInset.left + _textContainerInset.right); + layoutSize.height -= (_textContainerInset.top + _textContainerInset.bottom); + + if (CGSizeEqualToSize(_constrainedSize, layoutSize) == NO) { + _constrainedSize = layoutSize; + _renderer.constrainedSize = layoutSize; } } } @@ -335,6 +372,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; ASDN::MutexLocker l(__instanceLock__); + //remove textContainerInset + constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right); + constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom); + _constrainedSize = constrainedSize; // Instead of invalidating the renderer, in case this is a new call with a different constrained size, @@ -353,6 +394,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; self.descender *= _renderer.currentScaleFactor; } } + + //add textContainerInset + size.width += (_textContainerInset.left + _textContainerInset.right); + size.height += (_textContainerInset.top + _textContainerInset.bottom); + return size; } @@ -466,6 +512,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; CGContextSaveGState(context); + CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top); + ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer]; CGPoint boundsOrigin = drawParameterBounds.origin; diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m new file mode 100644 index 0000000000..78b420bfc5 --- /dev/null +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -0,0 +1,35 @@ +// +// ASTextNodeSnapshotTests.m +// AsyncDisplayKit +// +// Created by Garrett Moon on 8/12/16. +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "ASSnapshotTestCase.h" + +#import + +@interface ASTextNodeSnapshotTests : ASSnapshotTestCase + +@end + +@implementation ASTextNodeSnapshotTests + +- (void)testTextContainerInset +{ + // trivial test case to ensure ASSnapshotTestCase works + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" + attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}]; + [textNode measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; + textNode.frame = CGRectMake(0, 0, textNode.calculatedSize.width, textNode.calculatedSize.height); + textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); + + ASSnapshotVerifyNode(textNode, nil); +} + +@end diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInset@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7e6cac14b804f09ecdece6aa2d7e8e59d44ecd48 GIT binary patch literal 2651 zcmai$cT^IL*T)$`xwFhs4&2I9IdbMU4awXqEl^9%5v78ODJ~LI6U|wy_k2mS1v_y70a^Sz&Y@1OUad(TaFutNy)o!|oi0D?$sOQ!?k4s`A? z^dS0l6ATXk#5y4?0I&L`<_`+q5Ni)C03fFHS3snb;>tl&D$vs2<$$0A{Wa+Wss0}i zavlRJPWSo&AxKMe7d&v$19jilQB=m?LShxR>Rm2^?EOq!b#9Rpw)jkiv^C$2W<0L-zjYxEM%%%$nn0ZjR%(5R?CS-b_bB-(CwHZ^XK%JBLI=@-Gmd zILsh=X89Umcbd;PUw_I9fLuJHSNu54;$xV0o`f!(+UQ~M_%?qjd_RK~?fswEYum!y zFjc9fG31jBMojK6O{4gO-MP|WcH`6L1`XAF}**HB171Xucu0S z%f1%!mfDZkb^_MDknKbpNTzgoG7g8xGyw_#Zon!(&?(;`bpheDltvoEgL4$eX4)(J zjSHI5&M*vM%;BYAb`8(7mDCJYxY%#A6+J+DPT|_@1nV^esqzN7-Nz{6uMk@yfEoH% zEvph`g|B9`g(YsWFrN&14_20^bzf8r|s(V zmm?j*`95YhehKi*F~uhu3{-Hh?9qBpO({}|iMYCHrq5xVDI~p^7lFsijVSwkUd8*8 zd=1WZg0*zVf3RvboM;=uakBKt2lJ0!D#U0t-3BbBZuYH%VT8aL*EMl)?rXPSUpU96 zWC0Db1k+Dp5<_%X^4#6-;hz7tZeQ{oL~QiOf$fx4E|7aRY`v8<_!yxd(q+%ac`_6j zTVsQEC$MTwKNC+A8o)_Fv227%;xS#Zub?)bsmseJdXrR$%=v}jH61|o%nd+SyFew- zB9&+Q4GJxEq}nyN+KGu%7|TSB$=GUMpg+s<8~`0qB3M;{-`Y`9*aL@MPrCf$h=ywx~- z$q5=hv@%+b&w+k+x{+Vd+s|rK_tbZN%Np|G8U`HP%Bk`?%Qi z?Farzy_I_uu zq!6_vXq$YsPXAcG)q@i(0i0$`|5q;t-s|jg?(D7H7QNAv<`Vctspz& zk-UCd36*h&<{=uAQk`q)6(Oec;@v`TwEckaC9qmVkQd-#-}%7E2*fjgwq1C9u782a z0v;oYwlk)V-}Y`pnZ?jn%XtdQs&o3swm>9fh5%GNVce{umv2RF3(AgfRF9ihT=zTH zqm`W-1WZ9TdznSz9rNlo z`N8zMwkrxwf`!%1lzti)7+b)MmN*#WD_n;^g!FV^TLB$0{TQg{gdz?7@H4lx*osIdL0c#oqhCSjQVkNoJME$`l1xNghB%@=gZ zpNskwlA+c%__c{Am{`PKR4eJgJ@5VpPvIX@xughJJ*8gFk~f^c1K~Ec%bj=2k?BFHo&ar5F0dQ=wO`Z~wW^haPih1>q&Udzpj3l?blvk&L55i?|<^vmVi!B-+6Mk)eGu2kpq{v8cd>N zO-%NGuoc>i*fF2mc}+;to#bgCmE$&g_>BtI%bkoL3s?t5Pr5L~=-R|BUl2t>LKB7esY(B*7J zZzjEs60}5ge4~J+JCV1_vc6RnW-A9Z7;5VAzW|TwuDh2M^LfVk8d+lxc7yVDwM*-a zRXKCy44Z6cIZyl)Qex=BZwwTzI$9cj5x2=MdU8110dq}#;L<#)o=BA_q3iHT3B193 zbn`8d$dljPub$S(2f9YTf$yc(_Hr-284~F8p59ox+K|H~V`9eJd#DtKy|><48OG%7 z6-Urcy_;)7aQyW9VXu^q24PgR`w&Z@Ahok0vIF($YNs;o->IAeAVGFJ%|!&KF5wWi z*h}D*9EkllJz?8hIRJbp;bOIbi)5vigI`InSx$U3lnW-@N=ys%;tc$K{*YF7mai Date: Fri, 12 Aug 2016 15:37:23 -0700 Subject: [PATCH 19/50] [ASCollectionView / ASPagerNode] Move constrainedSizeForNodeAtIndexPath from the DataSource to the Delegate (#2065) * Move constrainedSizeForNodeAtIndexPath from the DataSource to the Delegate in ASCollectionView and ASPagerNode * Fix ASPagerNode declaration of dataSource and delegate As ASPagerDataSource does not inherit from ASCollectionDataSource it's not possible to declare it as property. * Update comment --- AsyncDisplayKit/ASCollectionView.h | 22 +++++----- AsyncDisplayKit/ASCollectionView.mm | 4 -- AsyncDisplayKit/ASPagerNode.h | 23 ++++++---- AsyncDisplayKit/ASPagerNode.m | 43 +++++++++++++------ .../ASCollectionViewFlowLayoutInspector.h | 2 +- .../ASCollectionViewFlowLayoutInspector.m | 24 +++++------ .../Node Containers/OverviewASPagerNode.m | 3 +- .../Sample/ViewController.m | 3 +- 8 files changed, 74 insertions(+), 50 deletions(-) diff --git a/AsyncDisplayKit/ASCollectionView.h b/AsyncDisplayKit/ASCollectionView.h index 62f508f173..afe74f5ea8 100644 --- a/AsyncDisplayKit/ASCollectionView.h +++ b/AsyncDisplayKit/ASCollectionView.h @@ -400,17 +400,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; -/** - * Provides the constrained size range for measuring the node at the index path. - * - * @param collectionView The sender. - * - * @param indexPath The index path of the node. - * - * @returns A constrained size range for layout the node at this index path. - */ -- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; - /** * Indicator to lock the data source for data fetching in async mode. * We should not update the data source until the data source has been unlocked. Otherwise, it will incur data inconsistency or exception @@ -442,6 +431,17 @@ NS_ASSUME_NONNULL_BEGIN @optional +/** + * Provides the constrained size range for measuring the node at the index path. + * + * @param collectionView The sender. + * + * @param indexPath The index path of the node. + * + * @returns A constrained size range for layout the node at this index path. + */ +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; + /** * Informs the delegate that the collection view will add the node * at the given index path to the view hierarchy. diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 0c581d92b4..d7cd5b404c 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -151,11 +151,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; } _asyncDelegateFlags; struct { - unsigned int asyncDataSourceConstrainedSizeForNode:1; unsigned int asyncDataSourceNodeForItemAtIndexPath:1; unsigned int asyncDataSourceNodeBlockForItemAtIndexPath:1; unsigned int asyncDataSourceNumberOfSectionsInCollectionView:1; - unsigned int asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath:1; } _asyncDataSourceFlags; struct { @@ -354,11 +352,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _asyncDataSource = asyncDataSource; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; - _asyncDataSourceFlags.asyncDataSourceConstrainedSizeForNode = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:nodeBlockForItemAtIndexPath:)]; _asyncDataSourceFlags.asyncDataSourceNumberOfSectionsInCollectionView = [_asyncDataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; - _asyncDataSourceFlags.asyncDataSourceCollectionViewConstrainedSizeForNodeAtIndexPath = [_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; // Data-source must implement collectionView:nodeForItemAtIndexPath: or collectionView:nodeBlockForItemAtIndexPath: ASDisplayNodeAssertTrue(_asyncDataSourceFlags.asyncDataSourceNodeBlockForItemAtIndexPath || _asyncDataSourceFlags.asyncDataSourceNodeForItemAtIndexPath); diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 21b5d698e2..1b028b020b 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -16,6 +16,8 @@ @class ASPagerNode; @class ASPagerFlowLayout; +NS_ASSUME_NONNULL_BEGIN + #define ASPagerNodeDataSource ASPagerDataSource @protocol ASPagerDataSource @@ -52,6 +54,12 @@ */ - (ASCellNodeBlock)pagerNode:(ASPagerNode *)pagerNode nodeBlockAtIndex:(NSInteger)index; +@end + +@protocol ASPagerDelegate + +@optional + /** * Provides the constrained size range for measuring the node at the index path. * @@ -63,10 +71,6 @@ @end -@protocol ASPagerDelegate - -@end - @interface ASPagerNode : ASCollectionNode /** @@ -82,14 +86,15 @@ /** * Data Source is required, and uses a different protocol from ASCollectionNode. */ -- (void)setDataSource:(id )dataSource; -- (id )dataSource; +- (void)setDataSource:(nullable id )dataSource; +- (nullable id )dataSource; /** - * Delegate is optional, and uses the same protocol as ASCollectionNode. + * Delegate is optional. * This includes UIScrollViewDelegate as well as most methods from UICollectionViewDelegate, like willDisplay... */ -@property (nonatomic, weak) id delegate; +- (void)setDelegate:(nullable id )delegate; +- (nullable id )delegate; /** * The underlying ASCollectionView object. @@ -112,3 +117,5 @@ - (ASCellNode *)nodeForPageAtIndex:(NSInteger)index; @end + +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index b5050f4d5c..47704098ce 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -15,18 +15,23 @@ #import "ASDisplayNode+Subclasses.h" #import "ASPagerFlowLayout.h" -@interface ASPagerNode () +@interface ASPagerNode () { ASPagerFlowLayout *_flowLayout; - ASPagerNodeProxy *_proxy; - __weak id _pagerDataSource; + + __weak id _pagerDataSource; + ASPagerNodeProxy *_proxyDataSource; BOOL _pagerDataSourceImplementsNodeBlockAtIndex; - BOOL _pagerDataSourceImplementsConstrainedSizeForNode; + + __weak id _pagerDelegate; + ASPagerNodeProxy *_proxyDelegate; + BOOL _pagerDelegateImplementsConstrainedSizeForNode; } @end @implementation ASPagerNode + @dynamic view, delegate, dataSource; #pragma mark - Lifecycle @@ -58,6 +63,7 @@ [super didLoad]; ASCollectionView *cv = self.view; + cv.asyncDelegate = self; #if TARGET_OS_IOS cv.pagingEnabled = YES; cv.scrollsToTop = NO; @@ -122,9 +128,10 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - if (_pagerDataSourceImplementsConstrainedSizeForNode) { - return [_pagerDataSource pagerNode:self constrainedSizeForNodeAtIndexPath:indexPath]; + if (_pagerDelegateImplementsConstrainedSizeForNode) { + return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndexPath:indexPath]; } + return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); } @@ -135,26 +142,38 @@ return _pagerDataSource; } -- (void)setDataSource:(id )pagerDataSource +- (void)setDataSource:(id )dataSource { - if (pagerDataSource != _pagerDataSource) { - _pagerDataSource = pagerDataSource; + if (dataSource != _pagerDataSource) { + _pagerDataSource = dataSource; _pagerDataSourceImplementsNodeBlockAtIndex = [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeBlockAtIndex:)]; // Data source must implement pagerNode:nodeBlockAtIndex: or pagerNode:nodeAtIndex: ASDisplayNodeAssertTrue(_pagerDataSourceImplementsNodeBlockAtIndex || [_pagerDataSource respondsToSelector:@selector(pagerNode:nodeAtIndex:)]); - _pagerDataSourceImplementsConstrainedSizeForNode = [_pagerDataSource respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)]; + _proxyDataSource = dataSource ? [[ASPagerNodeProxy alloc] initWithTarget:dataSource interceptor:self] : nil; - _proxy = pagerDataSource ? [[ASPagerNodeProxy alloc] initWithTarget:pagerDataSource interceptor:self] : nil; + super.dataSource = (id )_proxyDataSource; + } +} + +- (void)setDelegate:(id)delegate +{ + if (delegate != _pagerDelegate) { + _pagerDelegate = delegate; - super.dataSource = (id )_proxy; + _pagerDelegateImplementsConstrainedSizeForNode = [_pagerDelegate respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)]; + + _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; + + super.delegate = (id )_proxyDelegate; } } - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy { [self setDataSource:nil]; + [self setDelegate:nil]; } @end diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 423c8302a7..608a93cea1 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASCollectionViewLayoutInspecting /** - * Asks the inspector to provide a constarained size range for the given collection view node. + * Asks the inspector to provide a constrained size range for the given collection view node. */ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index 25ba06b71d..9ae4d1e158 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -34,7 +34,7 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView @implementation ASCollectionViewLayoutInspector { struct { unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; - } _dataSourceFlags; + } _delegateFlags; } #pragma mark Lifecycle @@ -43,26 +43,26 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView { self = [super init]; if (self != nil) { - [self didChangeCollectionViewDataSource:collectionView.asyncDataSource]; + [self didChangeCollectionViewDelegate:collectionView.asyncDelegate]; } return self; } #pragma mark ASCollectionViewLayoutInspecting -- (void)didChangeCollectionViewDataSource:(id)dataSource +- (void)didChangeCollectionViewDelegate:(id)delegate { - if (dataSource == nil) { - memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); + if (delegate == nil) { + memset(&_delegateFlags, 0, sizeof(_delegateFlags)); } else { - _dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; + _delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; } } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { - return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) { + return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; } return NodeConstrainedSizeForScrollDirection(collectionView); @@ -99,10 +99,10 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView struct { unsigned int implementsReferenceSizeForHeader:1; unsigned int implementsReferenceSizeForFooter:1; + unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; } _delegateFlags; struct { - unsigned int implementsConstrainedSizeForNodeAtIndexPath:1; unsigned int implementsNumberOfSectionsInCollectionView:1; } _dataSourceFlags; } @@ -132,6 +132,7 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView } else { _delegateFlags.implementsReferenceSizeForHeader = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; _delegateFlags.implementsReferenceSizeForFooter = [delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; + _delegateFlags.implementsConstrainedSizeForNodeAtIndexPath = [delegate respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; } } @@ -140,15 +141,14 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView if (dataSource == nil) { memset(&_dataSourceFlags, 0, sizeof(_dataSourceFlags)); } else { - _dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath = [dataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)]; _dataSourceFlags.implementsNumberOfSectionsInCollectionView = [dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]; } } - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { - if (_dataSourceFlags.implementsConstrainedSizeForNodeAtIndexPath) { - return [collectionView.asyncDataSource collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; + if (_delegateFlags.implementsConstrainedSizeForNodeAtIndexPath) { + return [collectionView.asyncDelegate collectionView:collectionView constrainedSizeForNodeAtIndexPath:indexPath]; } CGSize itemSize = _layout.itemSize; diff --git a/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m b/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m index 8b7f39d840..b2a4499b89 100644 --- a/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m +++ b/examples/AsyncDisplayKitOverview/Sample/Node Containers/OverviewASPagerNode.m @@ -47,7 +47,7 @@ static UIColor *OverViewASPagerNodeRandomColor() { #pragma mark - OverviewASPagerNode -@interface OverviewASPagerNode () +@interface OverviewASPagerNode () @property (nonatomic, strong) ASPagerNode *node; @property (nonatomic, copy) NSArray *data; @end @@ -61,6 +61,7 @@ static UIColor *OverViewASPagerNodeRandomColor() { _node = [ASPagerNode new]; _node.dataSource = self; + _node.delegate = self; [self addSubnode:_node]; return self; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m index c506a3faa9..c474bbbbae 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m +++ b/examples/VerticalWithinHorizontalScrolling/Sample/ViewController.m @@ -19,7 +19,7 @@ #import "ViewController.h" #import "GradientTableNode.h" -@interface ViewController () +@interface ViewController () { ASPagerNode *_pagerNode; } @@ -38,6 +38,7 @@ _pagerNode = [[ASPagerNode alloc] init]; _pagerNode.dataSource = self; + _pagerNode.delegate = self; [ASRangeController setShouldShowRangeDebugOverlay:YES]; // Could implement ASCollectionDelegate if we wanted extra callbacks, like from UIScrollView. From 43370fe6ffd1291c8302656f8f56d0b7677f0f51 Mon Sep 17 00:00:00 2001 From: Maximilian Szengel Date: Sun, 14 Aug 2016 02:28:19 +0200 Subject: [PATCH 20/50] Expose AVPlayerLayer on ASVideoNode (#2028) The playerLayer is needed for picture in picture support on the iPad (AVPictureInPictureController). --- AsyncDisplayKit/ASVideoNode.h | 3 ++- AsyncDisplayKit/ASVideoNode.mm | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASVideoNode.h b/AsyncDisplayKit/ASVideoNode.h index 039c21269a..9223da3fd4 100644 --- a/AsyncDisplayKit/ASVideoNode.h +++ b/AsyncDisplayKit/ASVideoNode.h @@ -12,7 +12,7 @@ #import #import -@class AVAsset, AVPlayer, AVPlayerItem, AVVideoComposition, AVAudioMix; +@class AVAsset, AVPlayer, AVPlayerLayer, AVPlayerItem, AVVideoComposition, AVAudioMix; @protocol ASVideoNodeDelegate; typedef enum { @@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, strong, readwrite) AVAudioMix *audioMix; @property (nullable, nonatomic, strong, readonly) AVPlayer *player; +@property (nullable, nonatomic, strong, readonly) AVPlayerLayer *playerLayer; @property (nullable, nonatomic, strong, readonly) AVPlayerItem *currentItem; diff --git a/AsyncDisplayKit/ASVideoNode.mm b/AsyncDisplayKit/ASVideoNode.mm index 4fd05f06ac..008c4b94f0 100644 --- a/AsyncDisplayKit/ASVideoNode.mm +++ b/AsyncDisplayKit/ASVideoNode.mm @@ -551,6 +551,12 @@ static NSString * const kRate = @"rate"; return _player; } +- (AVPlayerLayer *)playerLayer +{ + ASDN::MutexLocker l(__instanceLock__); + return (AVPlayerLayer *)_playerNode.layer; +} + - (id)delegate{ return _delegate; } From adcc9afb5a243d7fea8f20e20377f34ea57a1a22 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Sat, 13 Aug 2016 17:35:20 -0700 Subject: [PATCH 21/50] [Layout Transition] Add default fade in / out layout transition (#2052) * Add default fade in / out layout transition * Add example for layout transition * Update for recent layout transition API changes * To be able to do a layoutTransition the node needs to be loaded * Rename layoutTransitionDuration to defaultLayoutTransitionDuration * Expose default layout transition duration delay and options * Use `UIViewAnimationOptionBeginFromCurrentState` for initial defaultLayoutTransitionOptions --- AsyncDisplayKit/ASDisplayNode.h | 17 + AsyncDisplayKit/ASDisplayNode.mm | 116 +++++- .../Private/ASDisplayNodeInternal.h | 3 + .../ASDisplayNodeImplicitHierarchyTests.m | 2 + .../ASDKLayoutTransition/Default-568h@2x.png | Bin 0 -> 17520 bytes .../ASDKLayoutTransition/Default-667h@2x.png | Bin 0 -> 18314 bytes .../ASDKLayoutTransition/Default-736h@3x.png | Bin 0 -> 23380 bytes examples/ASDKLayoutTransition/Podfile | 5 + .../Sample.xcodeproj/project.pbxproj | 368 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/Sample.xcscheme | 91 +++++ .../ASDKLayoutTransition/Sample/AppDelegate.h | 24 ++ .../ASDKLayoutTransition/Sample/AppDelegate.m | 33 ++ .../ASDKLayoutTransition/Sample/Info.plist | 36 ++ .../Sample/ViewController.h | 22 ++ .../Sample/ViewController.m | 199 ++++++++++ examples/ASDKLayoutTransition/Sample/main.m | 26 ++ 17 files changed, 945 insertions(+), 4 deletions(-) create mode 100644 examples/ASDKLayoutTransition/Default-568h@2x.png create mode 100644 examples/ASDKLayoutTransition/Default-667h@2x.png create mode 100644 examples/ASDKLayoutTransition/Default-736h@3x.png create mode 100644 examples/ASDKLayoutTransition/Podfile create mode 100644 examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj create mode 100644 examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples/ASDKLayoutTransition/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme create mode 100644 examples/ASDKLayoutTransition/Sample/AppDelegate.h create mode 100644 examples/ASDKLayoutTransition/Sample/AppDelegate.m create mode 100644 examples/ASDKLayoutTransition/Sample/Info.plist create mode 100644 examples/ASDKLayoutTransition/Sample/ViewController.h create mode 100644 examples/ASDKLayoutTransition/Sample/ViewController.m create mode 100644 examples/ASDKLayoutTransition/Sample/main.m diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 08b2bfe469..3e2a227b01 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -749,6 +749,23 @@ NS_ASSUME_NONNULL_BEGIN @interface ASDisplayNode (LayoutTransitioning) +/** + * @abstract The amount of time it takes to complete the default transition animation. Default is 0.2. + */ +@property (nonatomic, assign) NSTimeInterval defaultLayoutTransitionDuration; + +/** + * @abstract The amount of time (measured in seconds) to wait before beginning the default transition animation. + * Default is 0.0. + */ +@property (nonatomic, assign) NSTimeInterval defaultLayoutTransitionDelay; + +/** + * @abstract A mask of options indicating how you want to perform the default transition animations. + * For a list of valid constants, see UIViewAnimationOptions. + */ +@property (nonatomic, assign) UIViewAnimationOptions defaultLayoutTransitionOptions; + /** * @discussion A place to perform your animation. New nodes have been inserted here. You can also use this time to re-order the hierarchy. */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index f4b804d6d7..3120d82549 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -291,6 +291,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _environmentState = ASEnvironmentStateMakeDefault(); + _defaultLayoutTransitionDuration = 0.2; + _defaultLayoutTransitionDelay = 0.0; + _defaultLayoutTransitionOptions = UIViewAnimationOptionBeginFromCurrentState; + _flags.canClearContentsOfLayer = YES; _flags.canCallNeedsDisplayOfLayer = NO; } @@ -857,14 +861,118 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark Layout Transition API +- (void)setDefaultLayoutTransitionDuration:(NSTimeInterval)defaultLayoutTransitionDuration +{ + ASDN::MutexLocker l(__instanceLock__); + _defaultLayoutTransitionDuration = defaultLayoutTransitionDuration; +} + +- (NSTimeInterval)defaultLayoutTransitionDuration +{ + ASDN::MutexLocker l(__instanceLock__); + return _defaultLayoutTransitionDuration; +} + +- (void)setDefaultLayoutTransitionDelay:(NSTimeInterval)defaultLayoutTransitionDelay +{ + ASDN::MutexLocker l(__instanceLock__); + _defaultLayoutTransitionDelay = defaultLayoutTransitionDelay; +} + +- (NSTimeInterval)defaultLayoutTransitionDelay +{ + ASDN::MutexLocker l(__instanceLock__); + return _defaultLayoutTransitionDelay; +} + +- (void)setDefaultLayoutTransitionOptions:(UIViewAnimationOptions)defaultLayoutTransitionOptions +{ + ASDN::MutexLocker l(__instanceLock__); + _defaultLayoutTransitionOptions = defaultLayoutTransitionOptions; +} + +- (UIViewAnimationOptions)defaultLayoutTransitionOptions +{ + ASDN::MutexLocker l(__instanceLock__); + return _defaultLayoutTransitionOptions; +} + /* - * Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default this just layouts - * applies all subnodes without animation and calls completes the transition on the context. + * Hook for subclasse to perform an animation based on the given ASContextTransitioning. By default a fade in and out + * animation is provided. */ - (void)animateLayoutTransition:(id)context { - [self __layoutSublayouts]; - [context completeTransition:YES]; + ASDisplayNode *node = self; + + NSAssert(node.isNodeLoaded == YES, @"Invalid node state"); + NSAssert([context isAnimated] == YES, @"Can't animate a non-animatable context"); + + NSArray *removedSubnodes = [context removedSubnodes]; + NSMutableArray *removedViews = [NSMutableArray array]; + NSMutableArray *insertedSubnodes = [[context insertedSubnodes] mutableCopy]; + NSMutableArray *movedSubnodes = [NSMutableArray array]; + + for (ASDisplayNode *subnode in [context subnodesForKey:ASTransitionContextToLayoutKey]) { + if ([insertedSubnodes containsObject:subnode] == NO) { + // This is an existing subnode, check if it is resized, moved or both + CGRect fromFrame = [context initialFrameForNode:subnode]; + CGRect toFrame = [context finalFrameForNode:subnode]; + if (CGSizeEqualToSize(fromFrame.size, toFrame.size) == NO) { + // To crossfade resized subnodes, show a snapshot of it on top. + // The node itself can then be treated as a newly-inserted one. + UIView *snapshotView = [subnode.view snapshotViewAfterScreenUpdates:YES]; + snapshotView.frame = [context initialFrameForNode:subnode]; + snapshotView.alpha = 1; + + [node.view insertSubview:snapshotView aboveSubview:subnode.view]; + [removedViews addObject:snapshotView]; + + [insertedSubnodes addObject:subnode]; + } + if (CGPointEqualToPoint(fromFrame.origin, toFrame.origin) == NO) { + [movedSubnodes addObject:subnode]; + } + } + } + + for (ASDisplayNode *insertedSubnode in insertedSubnodes) { + insertedSubnode.frame = [context finalFrameForNode:insertedSubnode]; + insertedSubnode.alpha = 0; + } + + [UIView animateWithDuration:self.defaultLayoutTransitionDuration delay:self.defaultLayoutTransitionDelay options:self.defaultLayoutTransitionOptions animations:^{ + // Fade removed subnodes and views out + for (ASDisplayNode *removedSubnode in removedSubnodes) { + removedSubnode.alpha = 0; + } + for (UIView *removedView in removedViews) { + removedView.alpha = 0; + } + + // Fade inserted subnodes in + for (ASDisplayNode *insertedSubnode in insertedSubnodes) { + insertedSubnode.alpha = 1; + } + + // Update frame of self and moved subnodes + CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size; + CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size; + BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO); + if (isResized == YES) { + CGPoint position = node.frame.origin; + node.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height); + } + for (ASDisplayNode *movedSubnode in movedSubnodes) { + movedSubnode.frame = [context finalFrameForNode:movedSubnode]; + } + } completion:^(BOOL finished) { + for (UIView *removedView in removedViews) { + [removedView removeFromSuperview]; + } + // Subnode removals are automatically performed + [context completeTransition:finished]; + }]; } /* diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index f6337ba03c..9f429e6717 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -125,6 +125,9 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // Main thread only _ASTransitionContext *_pendingLayoutTransitionContext; BOOL _usesImplicitHierarchyManagement; + NSTimeInterval _defaultLayoutTransitionDuration; + NSTimeInterval _defaultLayoutTransitionDelay; + UIViewAnimationOptions _defaultLayoutTransitionOptions; int32_t _pendingTransitionID; ASLayoutTransition *_pendingLayoutTransition; diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 95adca7a35..28748af28a 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -190,6 +190,8 @@ }; // Intentionally trigger view creation + [node view]; + [node1 view]; [node2 view]; XCTestExpectation *expectation = [self expectationWithDescription:@"Fix IHM layout transition also if one node is already loaded"]; diff --git a/examples/ASDKLayoutTransition/Default-568h@2x.png b/examples/ASDKLayoutTransition/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ee80b93937cd9dd79502629b14fe09aa03cacc2 GIT binary patch literal 17520 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 E06EB-U;qFB literal 0 HcmV?d00001 diff --git a/examples/ASDKLayoutTransition/Default-736h@3x.png b/examples/ASDKLayoutTransition/Default-736h@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8949cae16a4217842bd8ad813422c6b02e7cafa GIT binary patch literal 23380 zcmeI3&2QsG7{;dvp`~agBn}`rU}c2_5{^Co*u*CBswBI#5^1Zpi0)~3Y)@Ki6WiGC zChY|TCvFHXE5u>%NL;wV0fEGo^J@PC5E5KCaDuU&4|kFdg zdhMfNZ$I1by=i;VuulBQrS}<*{ybMEgw+Y z?`=z+D4~*BH)T)7hSad?*u+K?zba`e))iG(ur6cGRxKNw(&STfR@qT2@%#2p_u6DQ z7PV`KSr*%hG8&EQBfTCa2MV?4Y7lsEkRh;JT_T6Zzgu6CWjm;?#Ukp#wUkVU{u-UaE@^ zqby1fqcet_rOzCg%}K8}8++;b4u?yJPP41G8G;GYrOI^gIHt-DO{1g4qgQXUOS!b{ z>a(CfpPW-pdFIS>r{mxZS)M6n#Zo9|sKu_;?j)3CQL-0B1E*YN+f#&6rz5@GBVG{Z zNMC6weE<1m&#h>eWYl4c(U7q!V`EQKZH#RestsFJD<)-6&Z8IkLH~G(hhf@Aqv}!V z$$PNP%ca`4;^TXEKT3uqbAll`ph_Gbw3K;crRQu(*_~(*CG51Qqqmf0%@tL# z%OtV!#5ekuMW}3fo+cYjqbZZ7=E~GCvFmv%we)@gvDd507p%LH zca(3HiM7wHU9)NG4c*OMb=lB-OLlervW%Ms#tqVRUEP{mSL6%UTS>sm92r#lFw}aGvc-8^S+s2F7KLn=zH_>DnivE{L5fL|(tNwMYt#KUt6;MNm1~M^YZEUo zWsaBc2I{wzQ?2vUnkgr;U~vM^N4fN`$j=^QbVx(dhAOR!UT2%6Q9m1zgsvU1HSw1l zy|g^7;k{c*UiSyVzc33ax&2^sKn+c6P*;_G^K!n@JzsX48kQ}r_EkzOqv5;LIsT_} zU}&~ED@gy*9L(3RcSynm>O0ExvZf7>(zKng_C46vIdva-)Tgc7gQrX3w1O{|&Q|{L zV6(EzN&qR!9d0QLZSw_F_TSIT=isR5-_TU{QE>i$BCV!*>25)XkQ{H}i_^U`z-5-GJRH)BFa2HG_>+sQA=U>Gio()6`~F zT1ic$<#bgZor~I8wz3Cv_M1SN{U}%{tFv3r!#tQ@)5CP-ykHOxh&TjXVm@3JaB)Dy zA>b18;j(~>10oIqmzWQi1za2uaR|7?e7G#&;(&-lz$NCxWdRolL>vMxF&{1qxHur< z5O9h4a9O~`0TG9QOU#GM0xk}SI0Rf`K3o=XaX`c&;1cuUvVe;NA`StUm=Bi)TpSQ_ z2)M+2xGdn}fQUoDCFa9r0T%~E90D#eA1({HI3VH>aEbYFS-`~s5r=?F%!kVYE)Iw| z1YBZ1To!O~K*S;767%7*fQthn4gr^#50?d891w9R#I-tq&6bAj-P#d*iT2)B=M(k< zuH>!n^bk6E38D8sKf00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!= l00AHX1c1Q*gn;t`y7MJkdHVCOto({Mu5Na}c>U)4e*&NtopJyG literal 0 HcmV?d00001 diff --git a/examples/ASDKLayoutTransition/Podfile b/examples/ASDKLayoutTransition/Podfile new file mode 100644 index 0000000000..919de4b311 --- /dev/null +++ b/examples/ASDKLayoutTransition/Podfile @@ -0,0 +1,5 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '7.0' +target 'Sample' do + pod 'AsyncDisplayKit', :path => '../..' +end diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..494f407d91 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,368 @@ +// !$*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 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 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 */; }; + DFE855DDBC731242D3515B58 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C284F7E957985CA251284B05 /* libPods-Sample.a */; }; +/* 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 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 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; }; + 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + C284F7E957985CA251284B05 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DFE855DDBC731242D3515B58 /* libPods-Sample.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 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.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 = ( + C284F7E957985CA251284B05 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 79ED4D85CC60068C341CFD77 /* Pods-Sample.debug.xcconfig */, + 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.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 /* [CP] Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */, + ); + 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 = 0710; + 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 */ + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] 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 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05E2128D19D4DB510098F589 /* ViewController.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; + 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; + }; + 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 = 79ED4D85CC60068C341CFD77 /* Pods-Sample.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_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1C47DEC3F9D2BD9AD5F5CD67 /* Pods-Sample.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_BUNDLE_IDENTIFIER = "com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)"; + 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/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASDKLayoutTransition/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..0b71c455d1 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASDKLayoutTransition/Sample/AppDelegate.h b/examples/ASDKLayoutTransition/Sample/AppDelegate.h new file mode 100644 index 0000000000..27e560aafe --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/AppDelegate.h @@ -0,0 +1,24 @@ +// +// AppDelegate.h +// Sample +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/ASDKLayoutTransition/Sample/AppDelegate.m b/examples/ASDKLayoutTransition/Sample/AppDelegate.m new file mode 100644 index 0000000000..c62355c06c --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/AppDelegate.m @@ -0,0 +1,33 @@ +// +// AppDelegate.m +// Sample +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 "ViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[ViewController alloc] init]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/ASDKLayoutTransition/Sample/Info.plist b/examples/ASDKLayoutTransition/Sample/Info.plist new file mode 100644 index 0000000000..fb4115c84c --- /dev/null +++ b/examples/ASDKLayoutTransition/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/examples/ASDKLayoutTransition/Sample/ViewController.h b/examples/ASDKLayoutTransition/Sample/ViewController.h new file mode 100644 index 0000000000..fc52c022f2 --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/ViewController.h @@ -0,0 +1,22 @@ +// +// ViewController.h +// Sample +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 ViewController : UIViewController + +@end diff --git a/examples/ASDKLayoutTransition/Sample/ViewController.m b/examples/ASDKLayoutTransition/Sample/ViewController.m new file mode 100644 index 0000000000..c15ae1418b --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/ViewController.m @@ -0,0 +1,199 @@ +// +// ViewController.m +// Sample +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 "ViewController.h" + +#import + +#pragma mark - TransitionNode + +#define USE_CUSTOM_LAYOUT_TRANSITION 0 + +@interface TransitionNode : ASDisplayNode +@property (nonatomic, assign) BOOL enabled; +@property (nonatomic, strong) ASButtonNode *buttonNode; +@property (nonatomic, strong) ASTextNode *textNodeOne; +@property (nonatomic, strong) ASTextNode *textNodeTwo; +@end + +@implementation TransitionNode + + +#pragma mark - Lifecycle + +- (instancetype)init +{ + self = [super init]; + if (self == nil) { return self; } + + self.usesImplicitHierarchyManagement = YES; + + // Define the layout transition duration for the default transition + self.defaultLayoutTransitionDuration = 1.0; + + _enabled = NO; + + // Setup text nodes + _textNodeOne = [[ASTextNode alloc] init]; + _textNodeOne.attributedText = [[NSAttributedString alloc] initWithString:@"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled"]; + + _textNodeTwo = [[ASTextNode alloc] init]; + _textNodeTwo.attributedText = [[NSAttributedString alloc] initWithString:@"It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English."]; + + // Setup button + NSString *buttonTitle = @"Start Layout Transition"; + UIFont *buttonFont = [UIFont systemFontOfSize:16.0]; + UIColor *buttonColor = [UIColor blueColor]; + + _buttonNode = [[ASButtonNode alloc] init]; + [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateNormal]; + + // Note: Currently we have to set all the button properties to the same one as for ASControlStateNormal. Otherwise + // if the button is involved in the layout transition it would break the transition as it does a layout pass + // while changing the title. This needs and will be fixed in the future! + [_buttonNode setTitle:buttonTitle withFont:buttonFont withColor:buttonColor forState:ASControlStateHighlighted]; + + + // Some debug colors + _textNodeOne.backgroundColor = [UIColor orangeColor]; + _textNodeTwo.backgroundColor = [UIColor greenColor]; + + + return self; +} + +- (void)didLoad +{ + [super didLoad]; + + [self.buttonNode addTarget:self action:@selector(buttonPressed:) forControlEvents:ASControlNodeEventTouchDown]; +} + +#pragma mark - Actions + +- (void)buttonPressed:(id)sender +{ + self.enabled = !self.enabled; + + [self transitionLayoutAnimated:YES measurementCompletion:nil]; +} + + +#pragma mark - Layout + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASTextNode *nextTextNode = self.enabled ? self.textNodeTwo : self.textNodeOne; + nextTextNode.flexGrow = YES; + nextTextNode.flexShrink = YES; + + ASStackLayoutSpec *horizontalStackLayout = [ASStackLayoutSpec horizontalStackLayoutSpec]; + horizontalStackLayout.children = @[nextTextNode]; + + self.buttonNode.alignSelf = ASStackLayoutAlignSelfCenter; + + ASStackLayoutSpec *verticalStackLayout = [ASStackLayoutSpec verticalStackLayoutSpec]; + verticalStackLayout.spacing = 10.0; + verticalStackLayout.children = @[horizontalStackLayout, self.buttonNode]; + + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(15.0, 15.0, 15.0, 15.0) child:verticalStackLayout]; +} + + +#pragma mark - Transition + +#if USE_CUSTOM_LAYOUT_TRANSITION + +- (void)animateLayoutTransition:(id)context +{ + ASDisplayNode *fromNode = [[context removedSubnodes] objectAtIndex:0]; + ASDisplayNode *toNode = [[context insertedSubnodes] objectAtIndex:0]; + + ASButtonNode *buttonNode = nil; + for (ASDisplayNode *node in [context subnodesForKey:ASTransitionContextToLayoutKey]) { + if ([node isKindOfClass:[ASButtonNode class]]) { + buttonNode = (ASButtonNode *)node; + break; + } + } + + CGRect toNodeFrame = [context finalFrameForNode:toNode]; + toNodeFrame.origin.x += (self.enabled ? toNodeFrame.size.width : -toNodeFrame.size.width); + toNode.frame = toNodeFrame; + toNode.alpha = 0.0; + + CGRect fromNodeFrame = fromNode.frame; + fromNodeFrame.origin.x += (self.enabled ? -fromNodeFrame.size.width : fromNodeFrame.size.width); + + // We will use the same transition duration as the default transition + [UIView animateWithDuration:self.defaultLayoutTransitionDuration animations:^{ + toNode.frame = [context finalFrameForNode:toNode]; + toNode.alpha = 1.0; + + fromNode.frame = fromNodeFrame; + fromNode.alpha = 0.0; + + // Update frame of self + CGSize fromSize = [context layoutForKey:ASTransitionContextFromLayoutKey].size; + CGSize toSize = [context layoutForKey:ASTransitionContextToLayoutKey].size; + BOOL isResized = (CGSizeEqualToSize(fromSize, toSize) == NO); + if (isResized == YES) { + CGPoint position = self.frame.origin; + self.frame = CGRectMake(position.x, position.y, toSize.width, toSize.height); + } + + buttonNode.frame = [context finalFrameForNode:buttonNode]; + } completion:^(BOOL finished) { + [context completeTransition:finished]; + }]; +} + +#endif + +@end + + +#pragma mark - ViewController + +@interface ViewController () +@property (nonatomic, strong) TransitionNode *transitionNode; +@end + +@implementation ViewController + +#pragma mark - UIViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + _transitionNode = [TransitionNode new]; + [self.view addSubnode:_transitionNode]; + + // Some debug colors + _transitionNode.backgroundColor = [UIColor grayColor]; +} + +- (void)viewDidLayoutSubviews +{ + [super viewDidLayoutSubviews]; + + CGSize size = [self.transitionNode measure:self.view.frame.size]; + self.transitionNode.frame = CGRectMake(0, 20, size.width, size.height); +} + +@end diff --git a/examples/ASDKLayoutTransition/Sample/main.m b/examples/ASDKLayoutTransition/Sample/main.m new file mode 100644 index 0000000000..756080fb2b --- /dev/null +++ b/examples/ASDKLayoutTransition/Sample/main.m @@ -0,0 +1,26 @@ +// +// main.m +// Sample +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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])); + } +} From f05ccea8601dfbb93ed35837e46d72cbd5a46f7c Mon Sep 17 00:00:00 2001 From: David Rodrigues Date: Mon, 15 Aug 2016 19:26:10 +0100 Subject: [PATCH 22/50] Fix wrong annotation which is causing a warning at compile time (#2072) --- AsyncDisplayKit/ASViewController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index 31ff3ed9d4..84bd14d604 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -78,7 +78,7 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); -- (instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); @end From 11215bf97f31da460b488317f59f91c3ae765c0c Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 15 Aug 2016 12:06:06 -0700 Subject: [PATCH 23/50] [Automatic Hierarchy Management] Rename IHM to automatic hierarchy management and move out of beta header (#2066) * Rename automaticHierarchy to automaticallyManagesSubnodes * Comment adjustments --- AsyncDisplayKit/ASButtonNode.mm | 3 +- AsyncDisplayKit/ASCollectionView.mm | 1 - AsyncDisplayKit/ASDisplayNode+Beta.h | 5 -- AsyncDisplayKit/ASDisplayNode.h | 35 +++++++++- AsyncDisplayKit/ASDisplayNode.mm | 66 +++++++++---------- AsyncDisplayKit/ASTableView.mm | 1 - .../Private/ASDisplayNode+UIViewBridge.mm | 2 +- .../Private/ASDisplayNodeInternal.h | 2 +- .../Private/_ASHierarchyChangeSet.mm | 1 + .../ASDisplayNodeImplicitHierarchyTests.m | 27 +++----- 10 files changed, 77 insertions(+), 66 deletions(-) diff --git a/AsyncDisplayKit/ASButtonNode.mm b/AsyncDisplayKit/ASButtonNode.mm index f1f40a33cc..6341f13732 100644 --- a/AsyncDisplayKit/ASButtonNode.mm +++ b/AsyncDisplayKit/ASButtonNode.mm @@ -14,7 +14,6 @@ #import "ASDisplayNode+Subclasses.h" #import "ASBackgroundLayoutSpec.h" #import "ASInsetLayoutSpec.h" -#import "ASDisplayNode+Beta.h" #import "ASStaticLayoutSpec.h" @interface ASButtonNode () @@ -56,7 +55,7 @@ - (instancetype)init { if (self = [super init]) { - self.usesImplicitHierarchyManagement = YES; + self.automaticallyManagesSubnodes = YES; _contentSpacing = 8.0; _laysOutHorizontally = YES; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index d7cd5b404c..dd12112668 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -18,7 +18,6 @@ #import "ASCollectionViewFlowLayoutInspector.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNode+Beta.h" #import "ASInternalHelpers.h" #import "UICollectionViewLayout+ASConvenience.h" #import "ASRangeController.h" diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index aa3b3d96a1..9302b9cf2b 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -20,9 +20,6 @@ ASDISPLAYNODE_EXTERN_C_END @interface ASDisplayNode (Beta) -+ (BOOL)usesImplicitHierarchyManagement; -+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled; - /** * ASTableView and ASCollectionView now throw exceptions on invalid updates * like their UIKit counterparts. If YES, these classes will log messages @@ -63,8 +60,6 @@ ASDISPLAYNODE_EXTERN_C_END /** @name Layout Transitioning */ -@property (nonatomic) BOOL usesImplicitHierarchyManagement; - /** * @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network. * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 3e2a227b01..cd4f550835 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -813,8 +813,24 @@ NS_ASSUME_NONNULL_BEGIN @end /* - ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering. - See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h + * ASDisplayNode support for automatic subnode management. + */ +@interface ASDisplayNode (AutomaticSubnodeManagement) + +/** + * @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or + * absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method. + * + * @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence + * or absence of subnodes is completely determined in its layoutSpecThatFits: method. + */ +@property (nonatomic, assign) BOOL automaticallyManagesSubnodes; + +@end + +/* + * ASDisplayNode participates in ASAsyncTransactions, so you can determine when your subnodes are done rendering. + * See: -(void)asyncdisplaykit_asyncTransactionContainerStateDidChange in ASDisplayNodeSubclass.h */ @interface ASDisplayNode (ASDisplayNodeAsyncTransactionContainer) @end @@ -829,7 +845,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)addSubnode:(nonnull ASDisplayNode *)node; @end -/** CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer. */ +/* + * CALayer(AsyncDisplayKit) defines convenience method for adding sub-ASDisplayNode to a CALayer. + */ @interface CALayer (AsyncDisplayKit) /** * Convenience method, equivalent to [layer addSublayer:node.layer]. @@ -890,6 +908,17 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED; +/** + * @abstract A boolean that shows whether the node automatically inserts and removes nodes based on the presence or + * absence of the node and its subnodes is completely determined in its layoutSpecThatFits: method. + * + * @discussion If flag is YES the node no longer require addSubnode: or removeFromSupernode method calls. The presence + * or absence of subnodes is completely determined in its layoutSpecThatFits: method. + * + * @deprecated Deprecated in version 2.0: Use automaticallyManagesSubnodes + */ +@property (nonatomic, assign) BOOL usesImplicitHierarchyManagement ASDISPLAYNODE_DEPRECATED; + - (void)reclaimMemory ASDISPLAYNODE_DEPRECATED; - (void)recursivelyReclaimMemory ASDISPLAYNODE_DEPRECATED; @property (nonatomic, assign) BOOL placeholderFadesOut ASDISPLAYNODE_DEPRECATED; diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 3120d82549..fbcf0396d8 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -77,18 +77,6 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS @synthesize isFinalLayoutable = _isFinalLayoutable; @synthesize threadSafeBounds = _threadSafeBounds; -static BOOL usesImplicitHierarchyManagement = NO; - -+ (BOOL)usesImplicitHierarchyManagement -{ - return usesImplicitHierarchyManagement; -} - -+ (void)setUsesImplicitHierarchyManagement:(BOOL)enabled -{ - usesImplicitHierarchyManagement = enabled; -} - static BOOL suppressesInvalidCollectionUpdateExceptions = YES; + (BOOL)suppressesInvalidCollectionUpdateExceptions @@ -703,6 +691,20 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) return !self.isNodeLoaded; } +#pragma mark - Automatic Hierarchy + +- (BOOL)automaticallyManagesSubnodes +{ + ASDN::MutexLocker l(__instanceLock__); + return _automaticallyManagesSubnodes; +} + +- (void)setAutomaticallyManagesSubnodes:(BOOL)automaticallyManagesSubnodes +{ + ASDN::MutexLocker l(__instanceLock__); + _automaticallyManagesSubnodes = automaticallyManagesSubnodes; +} + #pragma mark - Layout Transition - (void)transitionLayoutAnimated:(BOOL)animated measurementCompletion:(void (^)())completion @@ -751,11 +753,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASLayoutableSetCurrentContext(ASLayoutableContextMake(transitionID, NO)); ASDN::MutexLocker l(__instanceLock__); - BOOL disableImplicitHierarchyManagement = self.usesImplicitHierarchyManagement == NO; - self.usesImplicitHierarchyManagement = YES; // Temporary flag for 1.9.x + BOOL automaticallyManagesSubnodesDisabled = (self.automaticallyManagesSubnodes == NO); + self.automaticallyManagesSubnodes = YES; // Temporary flag for 1.9.x newLayout = [self calculateLayoutThatFits:constrainedSize]; - if (disableImplicitHierarchyManagement) { - self.usesImplicitHierarchyManagement = NO; // Temporary flag for 1.9.x + if (automaticallyManagesSubnodesDisabled) { + self.automaticallyManagesSubnodes = NO; // Temporary flag for 1.9.x } ASLayoutableClearCurrentContext(); @@ -820,18 +822,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } -- (BOOL)usesImplicitHierarchyManagement -{ - ASDN::MutexLocker l(__instanceLock__); - return _usesImplicitHierarchyManagement ? : [[self class] usesImplicitHierarchyManagement]; -} - -- (void)setUsesImplicitHierarchyManagement:(BOOL)value -{ - ASDN::MutexLocker l(__instanceLock__); - _usesImplicitHierarchyManagement = value; -} - - (BOOL)_isTransitionInProgress { ASDN::MutexLocker l(__instanceLock__); @@ -1019,8 +1009,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) */ - (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition { - // Layout transition is not supported for non implicit hierarchy managed nodes yet - if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { + // Layout transition is not supported for nodes that are not have automatic subnode management enabled + if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) { return; } @@ -1186,7 +1176,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDN::MutexLocker l(__instanceLock__); // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout - // but implicit hierarchy management would require us to modify the node tree + // but automatic subnode management would require us to modify the node tree // in the background on a loaded node, which isn't currently supported. if (_pendingViewState.hasSetNeedsLayout) { [self __setNeedsLayout]; @@ -1982,7 +1972,7 @@ static NSInteger incrementIfFound(NSInteger i) { // If a node was added to a supernode, the supernode could be in a layout pending state. All of the hierarchy state // properties related to the transition need to be copied over as well as propagated down the subtree. - // This is especially important as with Implicit Hierarchy Management adding subnodes can happen while a transition + // This is especially important as with automatic subnode management, adding subnodes can happen while a transition // is in fly if (ASHierarchyStateIncludesLayoutPending(stateToEnterOrExit)) { int32_t pendingTransitionId = newSupernode.pendingTransitionID; @@ -2701,7 +2691,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASDisplayNodeAssertTrue(layout.size.width >= 0.0); ASDisplayNodeAssertTrue(layout.size.height >= 0.0); - if (layoutTransition == nil || self.usesImplicitHierarchyManagement == NO) { + if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) { return; } @@ -3294,6 +3284,16 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; [self cancelLayoutTransition]; } +- (BOOL)usesImplicitHierarchyManagement +{ + return self.automaticallyManagesSubnodes; +} + +- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled +{ + self.automaticallyManagesSubnodes = enabled; +} + - (void)setPlaceholderFadesOut:(BOOL)placeholderFadesOut { self.placeholderFadeDuration = placeholderFadesOut ? 0.1 : 0.0; diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 85d24d199d..079ca9e241 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -17,7 +17,6 @@ #import "ASChangeSetDataController.h" #import "ASDelegateProxy.h" #import "ASDisplayNodeExtras.h" -#import "ASDisplayNode+Beta.h" #import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index 38901bce61..324fb875ac 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -344,7 +344,7 @@ if (shouldApply) { _layer.layerProperty = (layerValueExpr); } else { ASDisplayNo // The node is loaded but we're not on main. // We will call [self __setNeedsLayout] when we apply // the pending state. We need to call it on main if the node is loaded - // to support implicit hierarchy management. + // to support automatic subnode management. [ASDisplayNodeGetPendingState(self) setNeedsLayout]; } else { // The node is not loaded and we're not on main. diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 9f429e6717..f3528bbfe4 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -124,7 +124,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // Main thread only _ASTransitionContext *_pendingLayoutTransitionContext; - BOOL _usesImplicitHierarchyManagement; + BOOL _automaticallyManagesSubnodes; NSTimeInterval _defaultLayoutTransitionDuration; NSTimeInterval _defaultLayoutTransitionDelay; UIViewAnimationOptions _defaultLayoutTransitionOptions; diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm index 2b080cece7..649f39e8c3 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm @@ -15,6 +15,7 @@ #import "NSIndexSet+ASHelpers.h" #import "ASAssert.h" #import "ASDisplayNode+Beta.h" + #import #define ASFailUpdateValidation(...)\ diff --git a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m index 28748af28a..6ce6312aeb 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeImplicitHierarchyTests.m @@ -47,28 +47,13 @@ @implementation ASDisplayNodeImplicitHierarchyTests -- (void)setUp { - [super setUp]; - [ASDisplayNode setUsesImplicitHierarchyManagement:YES]; -} - -- (void)tearDown { - [ASDisplayNode setUsesImplicitHierarchyManagement:NO]; - [super tearDown]; -} - - (void)testFeatureFlag { - XCTAssert([ASDisplayNode usesImplicitHierarchyManagement]); ASDisplayNode *node = [[ASDisplayNode alloc] init]; - XCTAssert(node.usesImplicitHierarchyManagement); - - [ASDisplayNode setUsesImplicitHierarchyManagement:NO]; - XCTAssertFalse([ASDisplayNode usesImplicitHierarchyManagement]); - XCTAssertFalse(node.usesImplicitHierarchyManagement); - - node.usesImplicitHierarchyManagement = YES; - XCTAssert(node.usesImplicitHierarchyManagement); + XCTAssertFalse(node.automaticallyManagesSubnodes); + + node.automaticallyManagesSubnodes = YES; + XCTAssertTrue(node.automaticallyManagesSubnodes); } - (void)testInitialNodeInsertionWithOrdering @@ -80,6 +65,7 @@ ASDisplayNode *node5 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASStaticLayoutSpec *staticLayout = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[node4]]; @@ -106,6 +92,7 @@ ASDisplayNode *node3 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize){ ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; if ([strongNode.layoutState isEqualToNumber:@1]) { @@ -136,6 +123,7 @@ ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; if ([strongNode.layoutState isEqualToNumber:@1]) { @@ -179,6 +167,7 @@ ASDisplayNode *node2 = [[ASDisplayNode alloc] init]; ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASSpecTestDisplayNode *strongNode = (ASSpecTestDisplayNode *)weakNode; From 05ebbdc8ed6295b6002b10cdf113611ca92f3c40 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 15 Aug 2016 13:07:49 -0700 Subject: [PATCH 24/50] Increase timeout for flaky image download test --- AsyncDisplayKitTests/ASBasicImageDownloaderTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m index 3bfad70289..bc8972557b 100644 --- a/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m +++ b/AsyncDisplayKitTests/ASBasicImageDownloaderTests.m @@ -45,7 +45,7 @@ }]; #pragma clang diagnostic pop - [self waitForExpectationsWithTimeout:3 handler:nil]; + [self waitForExpectationsWithTimeout:30 handler:nil]; } @end From ee87695fc89c4860ae61a137b56ef2d032bc7107 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 15 Aug 2016 17:23:02 -0700 Subject: [PATCH 25/50] Don't use the default layout transition animation of no animation is wanted (#2075) --- AsyncDisplayKit/ASDisplayNode.mm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index fbcf0396d8..d340108403 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -893,8 +893,14 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) */ - (void)animateLayoutTransition:(id)context { + if ([context isAnimated] == NO) { + [self __layoutSublayouts]; + [context completeTransition:YES]; + return; + } + ASDisplayNode *node = self; - + NSAssert(node.isNodeLoaded == YES, @"Invalid node state"); NSAssert([context isAnimated] == YES, @"Can't animate a non-animatable context"); From 067e4998e2b19984bf7f7120eda702d69cf6ba03 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 15 Aug 2016 17:23:39 -0700 Subject: [PATCH 26/50] Fix methods from ASCollectionDelegate are not passed through to pager delegate (#2074) --- AsyncDisplayKit/ASPagerNode.m | 3 ++- AsyncDisplayKit/Details/ASDelegateProxy.m | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 47704098ce..fc2e4a703e 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -63,7 +63,8 @@ [super didLoad]; ASCollectionView *cv = self.view; - cv.asyncDelegate = self; + cv.asyncDataSource = _proxyDataSource ?: self; + cv.asyncDelegate = _proxyDelegate ?: self; #if TARGET_OS_IOS cv.pagingEnabled = YES; cv.scrollsToTop = NO; diff --git a/AsyncDisplayKit/Details/ASDelegateProxy.m b/AsyncDisplayKit/Details/ASDelegateProxy.m index 13c0d211c9..110845bd1e 100644 --- a/AsyncDisplayKit/Details/ASDelegateProxy.m +++ b/AsyncDisplayKit/Details/ASDelegateProxy.m @@ -91,8 +91,7 @@ selector == @selector(collectionView:nodeForItemAtIndexPath:) || selector == @selector(collectionView:nodeBlockForItemAtIndexPath:) || selector == @selector(collectionView:numberOfItemsInSection:) || - selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:) || - selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:) + selector == @selector(collectionView:constrainedSizeForNodeAtIndexPath:) ); } From 365f739ce7bd9f4b946ab68c6eabf5ded6c464a5 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Mon, 15 Aug 2016 20:58:50 -0700 Subject: [PATCH 27/50] Fix ASPagerNode delegate and data source warning (#2076) --- AsyncDisplayKit/ASPagerNode.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index fc2e4a703e..561382c3f0 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -63,8 +63,8 @@ [super didLoad]; ASCollectionView *cv = self.view; - cv.asyncDataSource = _proxyDataSource ?: self; - cv.asyncDelegate = _proxyDelegate ?: self; + cv.asyncDataSource = (id)_proxyDataSource ?: self; + cv.asyncDelegate = (id)_proxyDelegate ?: self; #if TARGET_OS_IOS cv.pagingEnabled = YES; cv.scrollsToTop = NO; From 2091df9607f3c95b99be46396dc460fedee57907 Mon Sep 17 00:00:00 2001 From: Nikita Lutsenko Date: Tue, 16 Aug 2016 10:18:51 -0700 Subject: [PATCH 28/50] [ASTextNode] Fix highlighting when textContainerInsets are set. (#2073) * [ASTextNode] Fix highlighting when textContainerInsets are set. * Add ASTextNodeSnapshotTests to xcodeproj. * Add snapshot test for container inset and highlighting. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++++ AsyncDisplayKit/ASTextNode.mm | 7 ++++++ .../ASTextNodeSnapshotTests.m | 22 ++++++++++++++++++ .../testTextContainerInsetHighlight@2x.png | Bin 0 -> 4877 bytes 4 files changed, 33 insertions(+) create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 9cf13a4e4b..20082d807f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -225,6 +225,7 @@ 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */; }; + 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */; }; 81EE38501C8E94F000456208 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; 83A7D95A1D44542100BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; 83A7D95B1D44547700BF333E /* ASWeakMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A7D9591D44542100BF333E /* ASWeakMap.m */; }; @@ -977,6 +978,7 @@ 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRelativeLayoutSpecSnapshotTests.mm; sourceTree = ""; }; 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+ASConvenience.h"; sourceTree = ""; }; 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+ASConvenience.m"; sourceTree = ""; }; + 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextNodeSnapshotTests.m; sourceTree = ""; }; 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRunLoopQueue.h; path = ../ASRunLoopQueue.h; sourceTree = ""; }; 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRunLoopQueue.mm; path = ../ASRunLoopQueue.mm; sourceTree = ""; }; 83A7D9581D44542100BF333E /* ASWeakMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakMap.h; sourceTree = ""; }; @@ -1336,6 +1338,7 @@ ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, AC026B571BD3F61800BBC17E /* ASStaticLayoutSpecSnapshotTests.m */, + 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */, ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */, ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, @@ -2195,6 +2198,7 @@ 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, + 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */, AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index b658be8a4e..2517eefaab 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -781,6 +781,13 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ]; for (NSValue *rectValue in highlightRects) { UIEdgeInsets shadowPadding = _renderer.shadower.shadowPadding; CGRect rendererRect = ASTextNodeAdjustRenderRectForShadowPadding(rectValue.CGRectValue, shadowPadding); + + // The rects returned from renderer don't have `textContainerInset`, + // as well as they are using the `constrainedSize` for layout, + // so we can simply increase the rect by insets to get the full blown layout. + rendererRect.size.width += _textContainerInset.left + _textContainerInset.right; + rendererRect.size.height += _textContainerInset.top + _textContainerInset.bottom; + CGRect highlightedRect = [self.layer convertRect:rendererRect toLayer:highlightTargetLayer]; // We set our overlay layer's frame to the bounds of the highlight target layer. diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m index 78b420bfc5..a52a8c7509 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -32,4 +32,26 @@ ASSnapshotVerifyNode(textNode, nil); } +- (void)testTextContainerInsetHighlight +{ + UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectZero]; + backgroundView.layer.as_allowsHighlightDrawing = YES; + + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.attributedText = [[NSAttributedString alloc] initWithString:@"yolo" + attributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:30] }]; + + [textNode measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; + textNode.frame = CGRectMake(50, 50, textNode.calculatedSize.width, textNode.calculatedSize.height); + textNode.textContainerInset = UIEdgeInsetsMake(5, 10, 10, 5); + + [backgroundView addSubview:textNode.view]; + backgroundView.frame = UIEdgeInsetsInsetRect(textNode.bounds, UIEdgeInsetsMake(-50, -50, -50, -50)); + + textNode.highlightRange = NSMakeRange(0, textNode.attributedText.length); + + [ASSnapshotTestCase hackilySynchronouslyRecursivelyRenderNode:textNode]; + FBSnapshotVerifyLayer(backgroundView.layer, nil); +} + @end diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASTextNodeSnapshotTests/testTextContainerInsetHighlight@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2fa33448404b42eb3f35c85d2b6ff8845c8e1b GIT binary patch literal 4877 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 literal 0 HcmV?d00001 From 56ee00986f3edd4ac0b8e74f35d01cdba97697cf Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 16 Aug 2016 10:19:58 -0700 Subject: [PATCH 29/50] [Architecture] Add plans for new Section Info Objects (#2067) * [section-infos-api] Add ASSectionInfo.h * Create Processes.md * Update ASSectionInfo.h * Update ASSectionInfo.h * Add more changes/info * More stuff * More updates * Make it a protocol * Update things * Oops * Update Misc.m * Update Overview.md * Update ASCollectionSection.h * Update ASCollectionSection.h --- plans/section-infos-api/ASCollectionSection.h | 27 +++++++++++++++ .../ASExampleLayoutSectionInfo.m | 22 ++++++++++++ plans/section-infos-api/Misc.m | 31 +++++++++++++++++ plans/section-infos-api/Overview.md | 34 +++++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 plans/section-infos-api/ASCollectionSection.h create mode 100644 plans/section-infos-api/ASExampleLayoutSectionInfo.m create mode 100644 plans/section-infos-api/Misc.m create mode 100644 plans/section-infos-api/Overview.md diff --git a/plans/section-infos-api/ASCollectionSection.h b/plans/section-infos-api/ASCollectionSection.h new file mode 100644 index 0000000000..cbef6af785 --- /dev/null +++ b/plans/section-infos-api/ASCollectionSection.h @@ -0,0 +1,27 @@ +/** + * Information about a section of items in a collection. + * + * This class is private to ASDK. + * Data sources may override -collectionView:infoForSectionAtIndex: to provide the + * "userInfo" object when the section is initially inserted. ASCollectionView + * vends the user info object publicly via "infoForSectionAtIndex:". +*/ +@interface ASCollectionSection : NSObject + +// Autoincrementing value, set by collection view immediately after creation. +@property NSInteger sectionID; + +@property NSMutableDictionary *> *editingNodesByKind; +@property NSMutableDictionary *> *completedNodesByKind; + +@property (strong, nullable) id userInfo; + +@end + +@protocol ASSectionUserInfo +// This will be set once, immediately after the object is returned by the data source. +@property (weak, nonatomic, nullable) ASCollectionView *collectionView; + +// Could be optional, but need to cache -respondsToSelector: dynamically. +@property (nullable, readonly, copy) NSString *sectionName; +@end diff --git a/plans/section-infos-api/ASExampleLayoutSectionInfo.m b/plans/section-infos-api/ASExampleLayoutSectionInfo.m new file mode 100644 index 0000000000..26fef393fe --- /dev/null +++ b/plans/section-infos-api/ASExampleLayoutSectionInfo.m @@ -0,0 +1,22 @@ +/** + * An example of what a section info class might look like. + */ +@interface ASExampleLayoutSectionInfo : NSObject + +@property (nonatomic, weak, nullable) ASCollectionView *collectionView; +@property (nullable, copy) NSString *sectionName; + +@property CGSize cellSpacing; +@property NSInteger numberOfColumns; +@property CGFloat columnWidth; +@property CGSize headerSize; +@property CGSize footerSize; +@property UIEdgeInsets headerInsets; +@property UIEdgeInsets footerInsets; +@property ASExampleLayoutBackgroundType backgroundType; +@property ASExampleLayoutRowAlignmentType rowAlignmentType; +@end + +@implementation ASExampleLayoutSectionInfo + +@end \ No newline at end of file diff --git a/plans/section-infos-api/Misc.m b/plans/section-infos-api/Misc.m new file mode 100644 index 0000000000..a114acfe77 --- /dev/null +++ b/plans/section-infos-api/Misc.m @@ -0,0 +1,31 @@ +// Added to ASCollectionDataSource: + +/** + * Data sources can override this method to return custom info associated with a + * section of the collection view. + * + * These section info objects can be read by a UICollectionViewLayout subclass + * and used to configure the layout for that section. @see ASSectionUserInfo + */ +@optional +- (nullable id)collectionView:(ASCollectionView *)collectionView infoForSectionAtIndex:(NSInteger)sectionIndex; + +// ---- +// Added to ASCollectionView: + +// Reads from data controller's _completedSections. Asserts that section index is in bounds. +- (nullable id)infoForSectionAtIndex:(NSInteger)sectionIndex; + +// ---- +// In ASDataController.mm: + +// Replace _editingNodes and _completedNodes with: +NSMutableArray *_editingSections; +NSMutableArray *_completedSections; + +// Modify _reloadDataWithAnimationOptions and insertSections:withAnimationOptions:. +// In those methods we use _populateFromDataSourceWithSectionIndexSet to get the node blocks. +// Now we will also need to create the ASCollectionSections and ask for UserInfos, just before we get the node blocks. + +// In essence, wherever we use an NSMutableArray of nodes to represent a section, we now +// will use an ASCollectionSection instead. diff --git a/plans/section-infos-api/Overview.md b/plans/section-infos-api/Overview.md new file mode 100644 index 0000000000..485c86d587 --- /dev/null +++ b/plans/section-infos-api/Overview.md @@ -0,0 +1,34 @@ +# Overview + +There is an established pattern where UICollectionViewLayout talks directly to the collection view's delegate to get additional layout info e.g. the size of a header in a given section. + +This pattern is established by Apple's flow layout, and it is used by Pinterest and I'm sure others. It is dangerous when used with ASDK because we update asynchronously, so +for instance if you delete a section from your data source, the layout won't find out until later and in the meantime it may ask the delegate about a section that doesn't exist! + +The solution is to capture this kind of information from the data source immediately when a section is inserted, and make it available to the layout as we update the UICollectionView so that everyone is on the same page. + +Enter: ASSectionUserInfo + +Internally, we use a private object ASCollectionSection to represent one version of a section of items and supplementaries. If the user wants, they can provide us with an ASSectionUserInfo object to accompany the section, which will be read synchronously when the section is inserted. + +## Usage During Layout + +The collection view will make these section infos available in the same way that finished nodes are currently available. + +#### [Sequence Diagram:][diag-layout-usage] + +![][image-layout-usage] + +## Creation When Inserting Sections + +The section infos for any inserted/reloaded sections are queried synchronously, before the node blocks for their items. The top part of this diagram is the same as the current behavior but I think it's useful info =) + +#### [Sequence Diagram:][diag-inserting-sections] + +![][image-inserting-sections] + + +[diag-inserting-sections]: https://www.websequencediagrams.com/?lz=dGl0bGUgSW5zZXJ0aW5nL1JlbG9hZGluZyBTZWN0aW9ucwoKRGF0YSBTb3VyY2UtPkNWOiBpACoFABkIQXRJbmRleGVzOgpDVi0-Q2hhbmdlU2V0IAAzBUNvbnRyb2xsZXIAHRsAURFlbmRVcGRhdGVzADQgAB4MAGIYLT4AehFiZWdpbgAFNACBYxlsb29wIGZvciBlYWNoIHMAgjgGIGluZGV4CiAgIACBAhJDVjoAHwhJbmZvAIJABzoAKAVDVgCBLQcAgnEGAA8aAIMQDACDFwZSZXR1cm4gaWQ8QVMAgz0HVXNlckluZm8-AFQIAIF-EwAWIQCBUg5pdGVtIGluAIFgCACBXQUAgU0Zbm9kZUJsb2NrAIQkB1BhdGgAgWIGAIFXFQARHgCBWRlub2RlIGJsb2NrAE8MAIFRGgAhD2VuZAplbmQAhBktAIUcCwCEYxAAhW0cAIUPGwCGWwYKAIMaCgCDeQgKCg&s=napkin +[image-inserting-sections]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgSW5zZXJ0aW5nL1JlbG9hZGluZyBTZWN0aW9ucwoKRGF0YSBTb3VyY2UtPkNWOiBpACoFABkIQXRJbmRleGVzOgpDVi0-Q2hhbmdlU2V0IAAzBUNvbnRyb2xsZXIAHRsAURFlbmRVcGRhdGVzADQgAB4MAGIYLT4AehFiZWdpbgAFNACBYxlsb29wIGZvciBlYWNoIHMAgjgGIGluZGV4CiAgIACBAhJDVjoAHwhJbmZvAIJABzoAKAVDVgCBLQcAgnEGAA8aAIMQDACDFwZSZXR1cm4gaWQ8QVMAgz0HVXNlckluZm8-AFQIAIF-EwAWIQCBUg5pdGVtIGluAIFgCACBXQUAgU0Zbm9kZUJsb2NrAIQkB1BhdGgAgWIGAIFXFQARHgCBWRlub2RlIGJsb2NrAE8MAIFRGgAhD2VuZAplbmQAhBktAIUcCwCEYxAAhW0cAIUPGwCGWwYKAIMaCgCDeQgKCg&s=napkin +[image-layout-usage]: https://www.websequencediagrams.com/cgi-bin/cdraw?lz=dGl0bGUgcHJlcGFyZUxheW91dCAtIHNlY3Rpb24gbWV0cmljcwoKQ1YtPgAYBjoAHw4KbG9vcCBmb3IgZWFjaAAxCAogICAgCiAgICAATQYtPkNWOgBOCEluZm9BdEluZGV4OgAkBUNWLQBUClJldHVybiBQSU1hc29ucnlTACsKCgBFDQAOFDogY29sdW1uQ291bnQAgQAFADQUAFwSACYQAEYed2lkdGhPZkMAZgUAgT0NAEgmADsFAIIRDQCCUwhVc2UAgmsJZW5kCgoAgjgHAII6BihPSykK&s=napkin +[diag-layout-usage]: https://www.websequencediagrams.com/?lz=dGl0bGUgcHJlcGFyZUxheW91dCAtIHNlY3Rpb24gbWV0cmljcwoKQ1YtPgAYBjoAHw4KbG9vcCBmb3IgZWFjaAAxCAogICAgCiAgICAATQYtPkNWOgBOCEluZm9BdEluZGV4OgAkBUNWLQBUClJldHVybiBQSU1hc29ucnlTACsKCgBFDQAOFDogY29sdW1uQ291bnQAgQAFADQUAFwSACYQAEYed2lkdGhPZkMAZgUAgT0NAEgmADsFAIIRDQCCUwhVc2UAgmsJZW5kCgoAgjgHAII6BihPSykK&s=napkin From 14ccb0b8861f3fa9946e6fb9964256678d6bd64e Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Tue, 16 Aug 2016 10:20:59 -0700 Subject: [PATCH 30/50] [ASTextNode] placeholder image shouldn't draw for zero area strings (#2069) * placeholder should not draw if area is zero * styling fix * super precision --- AsyncDisplayKit/ASTextNode.mm | 4 +++- AsyncDisplayKit/Details/CGRect+ASConvenience.h | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 2517eefaab..2b84eb1944 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -27,6 +27,8 @@ #import "ASInternalHelpers.h" #import "ASLayout.h" +#import "CGRect+ASConvenience.h" + static const NSTimeInterval ASTextNodeHighlightFadeOutDuration = 0.15; static const NSTimeInterval ASTextNodeHighlightFadeInDuration = 0.1; static const CGFloat ASTextNodeHighlightLightOpacity = 0.11; @@ -910,7 +912,7 @@ static CGRect ASTextNodeAdjustRenderRectForShadowPadding(CGRect rendererRect, UI // FIXME: Replace this implementation with reusable CALayers that have .backgroundColor set. // This would completely eliminate the memory and performance cost of the backing store. CGSize size = self.calculatedSize; - if (CGSizeEqualToSize(size, CGSizeZero)) { + if ((size.width * size.height) < CGFLOAT_EPSILON) { return nil; } diff --git a/AsyncDisplayKit/Details/CGRect+ASConvenience.h b/AsyncDisplayKit/Details/CGRect+ASConvenience.h index 348a896e14..c6148fd786 100644 --- a/AsyncDisplayKit/Details/CGRect+ASConvenience.h +++ b/AsyncDisplayKit/Details/CGRect+ASConvenience.h @@ -14,6 +14,14 @@ #import "ASBaseDefines.h" #import "ASLayoutController.h" +#ifndef CGFLOAT_EPSILON + #if CGFLOAT_IS_DOUBLE + #define CGFLOAT_EPSILON DBL_EPSILON + #else + #define CGFLOAT_EPSILON FLT_EPSILON + #endif +#endif + NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN From c65b2efed78016fee93b94feba7ccfa8ed347771 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 16 Aug 2016 10:27:14 -0700 Subject: [PATCH 31/50] [_ASDisplayViewAccessiblity] Improve accessibility support (#2060) * First approach to improve accessiblity * Clear accessibleElements in addSubview: and willRemoveSubview: * Adjust comments and rename viewNode to node * Create new accessible elements if screen coordinates of view changes * Remove legacy clearing of accessibleElements * Performance improvements * Use bounds for screenFrame calculation and indexOfObjectIdentical: in indexOfAccessiblityElement: * Add ASDK_ACCESSIBILITY_DISABLE compiler flag to disable custom accessibility code in ASDK * No need to set a frame if a subnode view is an accessibility element and not layer backed --- AsyncDisplayKit/Details/_ASDisplayView.h | 2 + AsyncDisplayKit/Details/_ASDisplayView.mm | 23 ++- .../Details/_ASDisplayViewAccessiblity.mm | 139 ++++++++++++------ 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.h b/AsyncDisplayKit/Details/_ASDisplayView.h index 4e52eb2892..2168491f75 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.h +++ b/AsyncDisplayKit/Details/_ASDisplayView.h @@ -16,6 +16,8 @@ @interface _ASDisplayView : UIView +@property (copy, nonatomic) NSArray *accessibleElements; + // These methods expose a way for ASDisplayNode touch events to let the view call super touch events // Some UIKit mechanisms, like UITableView and UICollectionView selection handling, require this to work - (void)__forwardTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 2d4deb5259..8eab7fdacd 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -26,11 +26,15 @@ @implementation _ASDisplayView { __unsafe_unretained ASDisplayNode *_node; // Though UIView has a .node property added via category, since we can add an ivar to a subclass, use that for performance. + BOOL _inHitTest; BOOL _inPointInside; + NSArray *_accessibleElements; + CGRect _lastAccessibleElementsFrame; } +@synthesize accessibleElements = _accessibleElements; @synthesize asyncdisplaykit_node = _node; + (Class)layerClass @@ -126,7 +130,6 @@ [newSuperview.asyncdisplaykit_node addSubnode:_node]; } } - } - (void)didMoveToSuperview @@ -169,6 +172,24 @@ } } +- (void)addSubview:(UIView *)view +{ + [super addSubview:view]; + +#ifndef ASDK_ACCESSIBILITY_DISABLE + [self setAccessibleElements:nil]; +#endif +} + +- (void)willRemoveSubview:(UIView *)subview +{ + [super willRemoveSubview:subview]; + +#ifndef ASDK_ACCESSIBILITY_DISABLE + [self setAccessibleElements:nil]; +#endif +} + - (void)setNeedsDisplay { // Standard implementation does not actually get to the layer, at least for views that don't implement drawRect:. diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm index baa2b1b7a2..86a98de956 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -8,12 +8,39 @@ // of patent rights can be found in the PATENTS file in the same directory. // +#ifndef ASDK_ACCESSIBILITY_DISABLE + #import "_ASDisplayView.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNode+FrameworkPrivate.h" #pragma mark - UIAccessibilityElement +typedef NSComparisonResult (^SortAccessibilityElementsComparator)(UIAccessibilityElement *, UIAccessibilityElement *); + +/// Sort accessiblity elements first by y and than by x origin. +static void SortAccessibilityElements(NSMutableArray *elements) +{ + ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); + + static SortAccessibilityElementsComparator comparator = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + comparator = ^NSComparisonResult(UIAccessibilityElement *a, UIAccessibilityElement *b) { + CGPoint originA = a.accessibilityFrame.origin; + CGPoint originB = b.accessibilityFrame.origin; + if (originA.y == originB.y) { + if (originA.x == originB.x) { + return NSOrderedSame; + } + return (originA.x < originB.x) ? NSOrderedAscending : NSOrderedDescending; + } + return (originA.y < originB.y) ? NSOrderedAscending : NSOrderedDescending; + }; + }); + [elements sortUsingComparator:comparator]; +} + @implementation UIAccessibilityElement (_ASDisplayView) + (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node @@ -32,96 +59,116 @@ #pragma mark - _ASDisplayView / UIAccessibilityContainer -static NSArray *ASCollectUIAccessibilityElementsForNode(ASDisplayNode *viewNode, ASDisplayNode *subnode, id container) { - NSMutableArray *accessibleElements = [NSMutableArray array]; - ASDisplayNodePerformBlockOnEveryNodeBFS(subnode, ^(ASDisplayNode * _Nonnull currentNode) { +/// Collect all subnodes for the given node by walking down the subnode tree and calculates the screen coordinates based on the containerNode and container +static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplayNode *containerNode, id container, NSMutableArray *elements) +{ + ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); + + ASDisplayNodePerformBlockOnEveryNodeBFS(node, ^(ASDisplayNode * _Nonnull currentNode) { // For every subnode that is layer backed or it's supernode has shouldRasterizeDescendants enabled // we have to create a UIAccessibilityElement as no view for this node exists - if (currentNode != viewNode && currentNode.isAccessibilityElement) { + if (currentNode != containerNode && currentNode.isAccessibilityElement) { UIAccessibilityElement *accessibilityElement = [UIAccessibilityElement accessibilityElementWithContainer:container node:currentNode]; // As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the // coordinate system of the supernode - CGRect frame = [viewNode convertRect:currentNode.bounds fromNode:currentNode]; + CGRect frame = [containerNode convertRect:currentNode.bounds fromNode:currentNode]; accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, container); - [accessibleElements addObject:accessibilityElement]; + [elements addObject:accessibilityElement]; } }); +} + +/// Collect all accessibliity elements for a given view and view node +static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableArray *elements) +{ + ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray"); - return [accessibleElements copy]; + ASDisplayNode *node = view.asyncdisplaykit_node; + + // Handle rasterize case + if (node.shouldRasterizeDescendants) { + CollectUIAccessibilityElementsForNode(node, node, view, elements); + return; + } + + for (ASDisplayNode *subnode in node.subnodes) { + if (subnode.isAccessibilityElement) { + + // An accessiblityElement can either be a UIView or a UIAccessibilityElement + if (subnode.isLayerBacked) { + // No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node + UIAccessibilityElement *accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:view node:subnode]; + + CGRect frame = [node convertRect:subnode.bounds fromNode:subnode]; + [accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(frame, view)]; + [elements addObject:accessiblityElement]; + } else { + // Accessiblity element is not layer backed just add the view as accessibility element + [elements addObject:subnode.view]; + } + } else if (subnode.isLayerBacked) { + // Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement + CollectUIAccessibilityElementsForNode(subnode, node, view, elements); + } else if ([subnode accessibilityElementCount] > 0) { + // UIView is itself a UIAccessibilityContainer just add it + [elements addObject:subnode.view]; + } + } } @interface _ASDisplayView () { NSArray *_accessibleElements; + CGRect _lastAccessibleElementsFrame; } + @end @implementation _ASDisplayView (UIAccessibilityContainer) #pragma mark - UIAccessibility +- (void)setAccessibleElements:(NSArray *)accessibleElements +{ + _accessibleElements = nil; +} + - (NSArray *)accessibleElements { ASDisplayNode *viewNode = self.asyncdisplaykit_node; if (viewNode == nil) { - return nil; + return @[]; } - // Handle rasterize case - if (viewNode.shouldRasterizeDescendants) { - _accessibleElements = ASCollectUIAccessibilityElementsForNode(viewNode, viewNode, self); + CGRect screenFrame = UIAccessibilityConvertFrameToScreenCoordinates(self.bounds, self); + if (_accessibleElements != nil && CGRectEqualToRect(_lastAccessibleElementsFrame, screenFrame)) { return _accessibleElements; } - // Handle not rasterize case - NSMutableArray *accessibleElements = [NSMutableArray array]; + _lastAccessibleElementsFrame = screenFrame; - for (ASDisplayNode *subnode in viewNode.subnodes) { - if (subnode.isAccessibilityElement) { - // An accessiblityElement can either be a UIView or a UIAccessibilityElement - id accessiblityElement = nil; - if (subnode.isLayerBacked) { - // No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node - accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:self node:subnode]; - } else { - accessiblityElement = subnode.view; - } - [accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(subnode.frame, self)]; - [accessibleElements addObject:accessiblityElement]; - } else if (subnode.isLayerBacked) { - // Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement - [accessibleElements addObjectsFromArray:ASCollectUIAccessibilityElementsForNode(viewNode, subnode, self)]; - } else if ([subnode accessibilityElementCount] > 0) { - // Add UIAccessibilityContainer - [accessibleElements addObject:subnode.view]; - } - } - _accessibleElements = [accessibleElements copy]; + NSMutableArray *accessibleElements = [NSMutableArray array]; + CollectAccessibilityElementsForView(self, accessibleElements); + SortAccessibilityElements(accessibleElements); + _accessibleElements = accessibleElements; return _accessibleElements; } - (NSInteger)accessibilityElementCount { - return [self accessibleElements].count; + return self.accessibleElements.count; } - (id)accessibilityElementAtIndex:(NSInteger)index { - ASDisplayNodeAssertNotNil(_accessibleElements, @"At this point _accessibleElements should be created."); - if (_accessibleElements == nil) { - return nil; - } - - return _accessibleElements[index]; + return self.accessibleElements[index]; } - (NSInteger)indexOfAccessibilityElement:(id)element { - if (_accessibleElements == nil) { - return NSNotFound; - } - - return [_accessibleElements indexOfObject:element]; + return [self.accessibleElements indexOfObjectIdenticalTo:element]; } @end + +#endif From eb497b7db1f71c56a39c507f7084f1d89fa8c989 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 16 Aug 2016 15:40:55 -0700 Subject: [PATCH 32/50] ASCellNode was overriding old deprecated methods It needs to override the new signatures as well. --- AsyncDisplayKit/ASCellNode.mm | 45 ++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/AsyncDisplayKit/ASCellNode.mm b/AsyncDisplayKit/ASCellNode.mm index 151578d735..c5cbda3794 100644 --- a/AsyncDisplayKit/ASCellNode.mm +++ b/AsyncDisplayKit/ASCellNode.mm @@ -115,13 +115,35 @@ [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; } -- (void)transitionLayoutWithAnimation:(BOOL)animated - shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(void(^)())completion +- (void)transitionLayoutAnimated:(BOOL)animated + measurementCompletion:(void (^)())completion { CGSize oldSize = self.calculatedSize; - [super transitionLayoutWithAnimation:animated - shouldMeasureAsync:shouldMeasureAsync + [super transitionLayoutAnimated:animated + measurementCompletion:^{ + [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; + if (completion) { + completion(); + } + } + ]; +} + +//Deprecated +- (void)transitionLayoutWithAnimation:(BOOL)animated + shouldMeasureAsync:(BOOL)shouldMeasureAsync + measurementCompletion:(void(^)())completion +{ + [self transitionLayoutAnimated:animated measurementCompletion:completion]; +} + +- (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize + animated:(BOOL)animated + measurementCompletion:(void (^)())completion +{ + CGSize oldSize = self.calculatedSize; + [super transitionLayoutWithSizeRange:constrainedSize + animated:animated measurementCompletion:^{ [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; if (completion) { @@ -131,22 +153,13 @@ ]; } +//Deprecated - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated shouldMeasureAsync:(BOOL)shouldMeasureAsync measurementCompletion:(void(^)())completion { - CGSize oldSize = self.calculatedSize; - [super transitionLayoutWithSizeRange:constrainedSize - animated:animated - shouldMeasureAsync:shouldMeasureAsync - measurementCompletion:^{ - [self didRelayoutFromOldSize:oldSize toNewSize:self.calculatedSize]; - if (completion) { - completion(); - } - } - ]; + [self transitionLayoutWithSizeRange:constrainedSize animated:animated measurementCompletion:completion]; } - (void)didRelayoutFromOldSize:(CGSize)oldSize toNewSize:(CGSize)newSize From 05dba2263cf86f7fce42f1918aa513385232f34c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 16 Aug 2016 16:47:26 -0700 Subject: [PATCH 33/50] Make ASWeakSet and NSArray+Diffing public, add support for large arrays (#2078) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++++++------ .../{Private => Details}/ASWeakSet.h | 0 .../{Private => Details}/ASWeakSet.m | 0 .../{Private => Details}/NSArray+Diffing.h | 0 .../{Private => Details}/NSArray+Diffing.m | 17 +++++++++++++++-- 5 files changed, 21 insertions(+), 8 deletions(-) rename AsyncDisplayKit/{Private => Details}/ASWeakSet.h (100%) rename AsyncDisplayKit/{Private => Details}/ASWeakSet.m (100%) rename AsyncDisplayKit/{Private => Details}/NSArray+Diffing.h (100%) rename AsyncDisplayKit/{Private => Details}/NSArray+Diffing.m (77%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 20082d807f..fddfab7eab 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -265,7 +265,7 @@ 9C70F20B1CDBE9A4007D6C76 /* ASDataController+Subclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */; }; 9C70F20C1CDBE9B6007D6C76 /* ASCollectionDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */; }; 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */ = {isa = PBXBuildFile; fileRef = AEB7B0181C5962EA00662EF4 /* ASDefaultPlayButton.h */; }; - 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; }; + 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */ = {isa = PBXBuildFile; fileRef = DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */; }; 9C8221961BA237B80037F19A /* ASStackBaselinePositionedLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C8221931BA237B80037F19A /* ASStackBaselinePositionedLayout.h */; }; 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C8221941BA237B80037F19A /* ASStackBaselinePositionedLayout.mm */; }; @@ -410,7 +410,7 @@ CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; - CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; }; + CC3B208A1C3F7A5400798563 /* ASWeakSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20871C3F7A5400798563 /* ASWeakSet.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC3B208B1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; CC3B208C1C3F7A5400798563 /* ASWeakSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20881C3F7A5400798563 /* ASWeakSet.m */; }; CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */; }; @@ -1446,6 +1446,10 @@ 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */, 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */, 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.m */, + CC3B20871C3F7A5400798563 /* ASWeakSet.h */, + CC3B20881C3F7A5400798563 /* ASWeakSet.m */, + DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, + DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, ); path = Details; sourceTree = ""; @@ -1514,10 +1518,6 @@ ACF6ED4A1B17847A00DA7C62 /* ASStackUnpositionedLayout.mm */, 83A7D9581D44542100BF333E /* ASWeakMap.h */, 83A7D9591D44542100BF333E /* ASWeakMap.m */, - CC3B20871C3F7A5400798563 /* ASWeakSet.h */, - CC3B20881C3F7A5400798563 /* ASWeakSet.m */, - DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, - DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, 8B0768B21CE752EC002E1453 /* ASDefaultPlaybackButton.m */, ); diff --git a/AsyncDisplayKit/Private/ASWeakSet.h b/AsyncDisplayKit/Details/ASWeakSet.h similarity index 100% rename from AsyncDisplayKit/Private/ASWeakSet.h rename to AsyncDisplayKit/Details/ASWeakSet.h diff --git a/AsyncDisplayKit/Private/ASWeakSet.m b/AsyncDisplayKit/Details/ASWeakSet.m similarity index 100% rename from AsyncDisplayKit/Private/ASWeakSet.m rename to AsyncDisplayKit/Details/ASWeakSet.m diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.h b/AsyncDisplayKit/Details/NSArray+Diffing.h similarity index 100% rename from AsyncDisplayKit/Private/NSArray+Diffing.h rename to AsyncDisplayKit/Details/NSArray+Diffing.h diff --git a/AsyncDisplayKit/Private/NSArray+Diffing.m b/AsyncDisplayKit/Details/NSArray+Diffing.m similarity index 77% rename from AsyncDisplayKit/Private/NSArray+Diffing.m rename to AsyncDisplayKit/Details/NSArray+Diffing.m index c34dfc2003..d7352a5c70 100644 --- a/AsyncDisplayKit/Private/NSArray+Diffing.m +++ b/AsyncDisplayKit/Details/NSArray+Diffing.m @@ -11,6 +11,7 @@ // #import "NSArray+Diffing.h" +#import "ASAssert.h" @implementation NSArray (Diffing) @@ -58,12 +59,24 @@ NSInteger selfCount = self.count; NSInteger arrayCount = array.count; - NSInteger lengths[selfCount+1][arrayCount+1]; + // Allocate the diff map in the heap so we don't blow the stack for large arrays. + NSInteger (*lengths)[arrayCount+1] = NULL; + size_t lengthsSize = ((selfCount+1) * sizeof(*lengths)); + // Would rather use initWithCapacity: to skip the zeroing, but TECHNICALLY + // `mutableBytes` is only guaranteed to be non-NULL if the data object has a non-zero length. + NS_VALID_UNTIL_END_OF_SCOPE NSMutableData *lengthsData = [[NSMutableData alloc] initWithLength:lengthsSize]; + lengths = lengthsData.mutableBytes; + if (lengths == NULL) { + ASDisplayNodeFailAssert(@"Failed to allocate memory for diffing with size %tu", lengthsSize); + return nil; + } + for (NSInteger i = 0; i <= selfCount; i++) { + id selfObj = i > 0 ? self[i-1] : nil; for (NSInteger j = 0; j <= arrayCount; j++) { if (i == 0 || j == 0) { lengths[i][j] = 0; - } else if (comparison(self[i-1], array[j-1])) { + } else if (comparison(selfObj, array[j-1])) { lengths[i][j] = 1 + lengths[i-1][j-1]; } else { lengths[i][j] = MAX(lengths[i-1][j], lengths[i][j-1]); From 7d21ccc184fb570f7527060cb8d61fa856e9eb8d Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 16 Aug 2016 17:01:06 -0700 Subject: [PATCH 34/50] [_ASDisplayViewAccessiblity] Fix accessibility in scroll views (#2079) * Fixes issues with accessibility elements in scroll views There are two changes here: 1. Because we can't reliably update the screen positions of accessibility elements contained within a scroll view, we need to calculate them on demand, so we override the accessibilityFrame method to do that. 2. Throwing away our calculated accessibility elements on frame change seemed to cause the accessibility system to be confused. Combining these two fixes together results in success, yay! * Don't set the accessibilityFrame, getter is overridden. --- .../Details/_ASDisplayViewAccessiblity.mm | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm index 86a98de956..641430f613 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.mm @@ -41,11 +41,22 @@ static void SortAccessibilityElements(NSMutableArray *elements) [elements sortUsingComparator:comparator]; } -@implementation UIAccessibilityElement (_ASDisplayView) +@interface ASAccessibilityElement : UIAccessibilityElement -+ (UIAccessibilityElement *)accessibilityElementWithContainer:(id)container node:(ASDisplayNode *)node +@property (nonatomic, strong) ASDisplayNode *node; +@property (nonatomic, strong) ASDisplayNode *containerNode; + ++ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode; + +@end + +@implementation ASAccessibilityElement + ++ (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)container node:(ASDisplayNode *)node containerNode:(ASDisplayNode *)containerNode { - UIAccessibilityElement *accessibilityElement = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container]; + ASAccessibilityElement *accessibilityElement = [[ASAccessibilityElement alloc] initWithAccessibilityContainer:container]; + accessibilityElement.node = node; + accessibilityElement.containerNode = containerNode; accessibilityElement.accessibilityIdentifier = node.accessibilityIdentifier; accessibilityElement.accessibilityLabel = node.accessibilityLabel; accessibilityElement.accessibilityHint = node.accessibilityHint; @@ -54,8 +65,14 @@ static void SortAccessibilityElements(NSMutableArray *elements) return accessibilityElement; } -@end +- (CGRect)accessibilityFrame +{ + CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node]; + accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.accessibilityContainer); + return accessibilityFrame; +} +@end #pragma mark - _ASDisplayView / UIAccessibilityContainer @@ -68,11 +85,7 @@ static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplay // For every subnode that is layer backed or it's supernode has shouldRasterizeDescendants enabled // we have to create a UIAccessibilityElement as no view for this node exists if (currentNode != containerNode && currentNode.isAccessibilityElement) { - UIAccessibilityElement *accessibilityElement = [UIAccessibilityElement accessibilityElementWithContainer:container node:currentNode]; - // As the node hierarchy is flattened it's necessary to convert the frame for each subnode in the tree to the - // coordinate system of the supernode - CGRect frame = [containerNode convertRect:currentNode.bounds fromNode:currentNode]; - accessibilityElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(frame, container); + UIAccessibilityElement *accessibilityElement = [ASAccessibilityElement accessibilityElementWithContainer:container node:currentNode containerNode:containerNode]; [elements addObject:accessibilityElement]; } }); @@ -97,10 +110,7 @@ static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableA // An accessiblityElement can either be a UIView or a UIAccessibilityElement if (subnode.isLayerBacked) { // No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node - UIAccessibilityElement *accessiblityElement = [UIAccessibilityElement accessibilityElementWithContainer:view node:subnode]; - - CGRect frame = [node convertRect:subnode.bounds fromNode:subnode]; - [accessiblityElement setAccessibilityFrame:UIAccessibilityConvertFrameToScreenCoordinates(frame, view)]; + UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node]; [elements addObject:accessiblityElement]; } else { // Accessiblity element is not layer backed just add the view as accessibility element @@ -118,7 +128,6 @@ static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableA @interface _ASDisplayView () { NSArray *_accessibleElements; - CGRect _lastAccessibleElementsFrame; } @end @@ -139,13 +148,10 @@ static void CollectAccessibilityElementsForView(_ASDisplayView *view, NSMutableA return @[]; } - CGRect screenFrame = UIAccessibilityConvertFrameToScreenCoordinates(self.bounds, self); - if (_accessibleElements != nil && CGRectEqualToRect(_lastAccessibleElementsFrame, screenFrame)) { + if (_accessibleElements != nil) { return _accessibleElements; } - _lastAccessibleElementsFrame = screenFrame; - NSMutableArray *accessibleElements = [NSMutableArray array]; CollectAccessibilityElementsForView(self, accessibleElements); SortAccessibilityElements(accessibleElements); From 99574a728c141ab1e7238815e968a0c5e4fdfa34 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 17 Aug 2016 17:42:37 -0700 Subject: [PATCH 35/50] Make our snapshot testing possible without framework-private access, simplify (#2083) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++-- .../Private/ASDisplayNodeInternal.h | 2 - .../ASDisplayNodeSnapshotTests.m | 36 +++++++++++ .../ASImageNodeSnapshotTests.m | 2 +- .../ASLayoutSpecSnapshotTestsHelper.m | 26 ++------ AsyncDisplayKitTests/ASSnapshotTestCase.h | 2 +- AsyncDisplayKitTests/ASSnapshotTestCase.m | 28 +++++++++ AsyncDisplayKitTests/ASSnapshotTestCase.mm | 57 ------------------ .../ASTextNodeSnapshotTests.m | 1 - .../testBasicHierarchySnapshotTesting@2x.png | Bin 0 -> 1281 bytes 10 files changed, 79 insertions(+), 87 deletions(-) create mode 100644 AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m create mode 100644 AsyncDisplayKitTests/ASSnapshotTestCase.m delete mode 100644 AsyncDisplayKitTests/ASSnapshotTestCase.mm create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index fddfab7eab..94789e0a59 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -66,7 +66,7 @@ 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; }; 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; }; 05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; - 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */; }; + 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */; }; 18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; 18C2ED831B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; }; @@ -420,6 +420,7 @@ CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; }; CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; }; CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB55C2631C6408D6004EDCF5 /* _ASTransitionContext.m in Sources */ = {isa = PBXBuildFile; fileRef = DB55C2601C6408D6004EDCF5 /* _ASTransitionContext.m */; }; @@ -875,7 +876,7 @@ 058D0A44195D058D00B7D73C /* ASBaseDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBaseDefines.h; sourceTree = ""; }; 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = ""; }; 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = ""; }; - 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASSnapshotTestCase.mm; sourceTree = ""; }; + 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASSnapshotTestCase.m; sourceTree = ""; }; 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = ""; }; 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = ""; }; 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionNode.mm; sourceTree = ""; }; @@ -1089,6 +1090,7 @@ 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 = ""; }; + CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.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 = ""; }; @@ -1322,6 +1324,7 @@ 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { isa = PBXGroup; children = ( + CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */, 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, @@ -1329,7 +1332,7 @@ CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, - 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.mm */, + 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */, 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, @@ -2180,6 +2183,7 @@ 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */, 2538B6F31BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m in Sources */, 058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */, + CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */, 058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, 058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */, @@ -2196,7 +2200,7 @@ ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, - 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.mm in Sources */, + 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */, diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index f3528bbfe4..c1d09979f4 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -21,8 +21,6 @@ #import "ASLayoutTransition.h" #import "ASEnvironment.h" -#include - @protocol _ASDisplayLayerDelegate; @class _ASDisplayLayer; @class _ASPendingState; diff --git a/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m new file mode 100644 index 0000000000..68f3c5b4be --- /dev/null +++ b/AsyncDisplayKitTests/ASDisplayNodeSnapshotTests.m @@ -0,0 +1,36 @@ +// +// ASDisplayNodeSnapshotTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 8/16/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "ASSnapshotTestCase.h" +#import + +@interface ASDisplayNodeSnapshotTests : ASSnapshotTestCase + +@end + +@implementation ASDisplayNodeSnapshotTests + +- (void)testBasicHierarchySnapshotTesting +{ + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.backgroundColor = [UIColor blueColor]; + + ASTextNode *subnode = [[ASTextNode alloc] init]; + subnode.backgroundColor = [UIColor whiteColor]; + + subnode.attributedText = [[NSAttributedString alloc] initWithString:@"Hello"]; + node.automaticallyManagesSubnodes = YES; + node.layoutSpecBlock = ^(ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5, 5, 5, 5) child:subnode]; + }; + [node measure:CGSizeMake(100, 100)]; + + ASSnapshotVerifyNode(node, nil); +} + +@end diff --git a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m index 69d9e8bc7b..b922df272e 100644 --- a/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASImageNodeSnapshotTests.m @@ -29,7 +29,7 @@ // trivial test case to ensure ASSnapshotTestCase works ASImageNode *imageNode = [[ASImageNode alloc] init]; imageNode.image = [self testImage]; - imageNode.frame = CGRectMake(0, 0, 100, 100); + [imageNode measure:CGSizeMake(100, 100)]; ASSnapshotVerifyNode(imageNode, nil); } diff --git a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m index bb182aa4a7..fe17358653 100644 --- a/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m +++ b/AsyncDisplayKitTests/ASLayoutSpecSnapshotTestsHelper.m @@ -15,7 +15,7 @@ #import "ASLayout.h" @interface ASTestNode : ASDisplayNode -- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange; +@property (strong, nonatomic, nullable) ASLayoutSpec *layoutSpecUnderTest; @end @implementation ASLayoutSpecSnapshotTestCase @@ -37,18 +37,15 @@ [node addSubnode:subnode]; } - [node setLayoutSpecUnderTest:layoutSpec sizeRange:sizeRange]; + node.layoutSpecUnderTest = layoutSpec; + [node measureWithSizeRange:sizeRange]; ASSnapshotVerifyNode(node, identifier); } @end @implementation ASTestNode -{ - ASLayout *_layoutUnderTest; -} - - (instancetype)init { if (self = [super init]) { @@ -57,22 +54,9 @@ return self; } -- (void)setLayoutSpecUnderTest:(ASLayoutSpec *)layoutSpecUnderTest sizeRange:(ASSizeRange)sizeRange +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { - ASLayout *layout = [layoutSpecUnderTest measureWithSizeRange:sizeRange]; - layout.position = CGPointZero; - layout = [ASLayout layoutWithLayoutableObject:self - constrainedSizeRange:sizeRange - size:layout.size - sublayouts:@[layout]]; - _layoutUnderTest = [layout filteredNodeLayoutTree]; - self.frame = CGRectMake(0, 0, _layoutUnderTest.size.width, _layoutUnderTest.size.height); - [self measure:_layoutUnderTest.size]; -} - -- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize -{ - return _layoutUnderTest; + return _layoutSpecUnderTest; } @end diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.h b/AsyncDisplayKitTests/ASSnapshotTestCase.h index 5c1cf3d677..215d550ccd 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.h +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.h @@ -10,7 +10,7 @@ #import -#import +@class ASDisplayNode; #define ASSnapshotVerifyNode(node__, identifier__) \ { \ diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.m b/AsyncDisplayKitTests/ASSnapshotTestCase.m new file mode 100644 index 0000000000..ff5248bda2 --- /dev/null +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.m @@ -0,0 +1,28 @@ +// +// ASSnapshotTestCase.m +// AsyncDisplayKit +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "ASSnapshotTestCase.h" +#import "ASDisplayNode+Beta.h" +#import "ASDisplayNodeExtras.h" +#import "ASDisplayNode+Subclasses.h" + +@implementation ASSnapshotTestCase + ++ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node +{ + ASDisplayNodeAssertNotNil(node.calculatedLayout, @"Node %@ must be measured before it is rendered.", node); + node.bounds = (CGRect) { .size = node.calculatedSize }; + ASDisplayNodePerformBlockOnEveryNode(nil, node, ^(ASDisplayNode * _Nonnull node) { + [node.layer setNeedsDisplay]; + }); + [node recursivelyEnsureDisplaySynchronously:YES]; +} + +@end diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.mm b/AsyncDisplayKitTests/ASSnapshotTestCase.mm deleted file mode 100644 index 3b2aaaadd0..0000000000 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.mm +++ /dev/null @@ -1,57 +0,0 @@ -// -// ASSnapshotTestCase.mm -// AsyncDisplayKit -// -// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "ASSnapshotTestCase.h" -#import "ASDisplayNode+FrameworkPrivate.h" -#import "ASDisplayNodeInternal.h" - -@implementation ASSnapshotTestCase - -+ (void)_layoutAndDisplayNode:(ASDisplayNode *)node -{ - if (![node __shouldLoadViewOrLayer]) { - return; - } - - CALayer *layer = node.layer; - - [layer setNeedsLayout]; - [layer layoutIfNeeded]; - - [layer setNeedsDisplay]; - [layer displayIfNeeded]; -} - -+ (void)_recursivelyLayoutAndDisplayNode:(ASDisplayNode *)node -{ - for (ASDisplayNode *subnode in node.subnodes) { - [self _recursivelyLayoutAndDisplayNode:subnode]; - } - - [self _layoutAndDisplayNode:node]; -} - -+ (void)_recursivelySetDisplaysAsynchronously:(BOOL)flag forNode:(ASDisplayNode *)node -{ - node.displaysAsynchronously = flag; - - for (ASDisplayNode *subnode in node.subnodes) { - [self _recursivelySetDisplaysAsynchronously:flag forNode:subnode]; - } -} - -+ (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node -{ -// TODO: Reconfigure this to be able to use [node recursivelyEnsureDisplay]; - [self _recursivelySetDisplaysAsynchronously:NO forNode:node]; - [self _recursivelyLayoutAndDisplayNode:node]; -} - -@end diff --git a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m index a52a8c7509..5fb5a3f556 100644 --- a/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m +++ b/AsyncDisplayKitTests/ASTextNodeSnapshotTests.m @@ -26,7 +26,6 @@ textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar" attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}]; [textNode measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))]; - textNode.frame = CGRectMake(0, 0, textNode.calculatedSize.width, textNode.calculatedSize.height); textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2); ASSnapshotVerifyNode(textNode, nil); diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASDisplayNodeSnapshotTests/testBasicHierarchySnapshotTesting@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..e5f40f70fd1b5806ee6a1a617a5eadcbcc60298e GIT binary patch literal 1281 zcmV+c1^)VpP)001xu1^@s6S*wTW00001b5ch_0Itp) z=>Px#32;bRa{vGqB>(^xB>_oNB=7(L032yVPgDQ^00IC20000002lxO04M+e02lxO z02lxO0tL^Q%h3P;0ntfBK~!i3?3Pc<0Z|mjC&k9fT1nQju%%{Ur7Z0D4kE?E(w9)m z2T&Hi0Sg;!Y%It^AsgjS*jVtq-BWY(=gr*FJCXNH-R90S$LZYPJm+4TrU5)u889ph zW?@(i%bu-;oyE>#XT7}(-d=#}vHb~g42$=yx9(Z^;!7Q@VjF%jtG{nUh9wRdb{0F! zN++lq#*(v6rxSX;9*zBeZ(7fAIHcIwY-Sp37BCnLDA)0Ll%cEDinQ@~EVWlAw!m5} z7Sy+baHrETtt%W3Q+%ONFpV_}@Or(JtKaX-&|0lVT09Y$otbU z8jX@33Wd;ax3SypApRQU^Lf&pPABQtD^i8E*=!(yi;s{>r7#+eAY43|Oi(J7NOw3K z=ytm@z3s5>9r5h>8djsxpn`oq9}b6uEZCT2vsrqdOePJ9?y9hg#UlMa7K`D0KHuaJ z4@Q3Rp^Qrv)(-#x0RR8aSsYjZ00N;&L_t*Tn7wN%K@h-AH2496L`VvQBBV*1E@&|b*l_4SpeveGqlH=>^YYI#Z%VM#RERje+sZ{!-7mGzO7!0J-Y&Mx+Ij}~f5!oFb9l`tiJF^uJ z-rn9or_+(1kb^2=;YtdtD(8tXg(e49qtPJ$XJ==EndH;ybdp{!m!o-A!kSDb3|1r( zfv2aZFMF@oW63IkWwlzF-Tq*`UV~Pvr8sJ}ntEYjWT&U66zIdl!+suLzRQ6%9*@~# ztX3;Yf7NQ0>Gb=3(otn)VPtoAcNDDK?UqcY)9CGCqco3O@#Euzrc?{7TrN|vdc7WAUS7m`zJ`g@plny{ zatZhM_dC9Y-@%F%Z*SE97Fg)`kZm@bL8VfG$Hzy==kqWe4iD*`F451=&yY^10q<0} z&86CpfF%|9|B!K53aE2f9F_tC3RDkXS@In!IbD95uFQWOI4oRP4(ssuEDn`dmV&C{ rusE#m`(*GByn^cDusE#m*owaZeH2+H0HO1)00000NkvXXu0mjfqGV6? literal 0 HcmV?d00001 From 86d2ab617eec00263af22f7f77d5f71c3c3d5632 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Wed, 17 Aug 2016 17:59:28 -0700 Subject: [PATCH 36/50] I didn't really understand NSProxy (#2089) * I didn't really understand NSProxy But I think I do now :) Previously I inherited from NSObject because I didn't think I needed anything fancy, but it turns out NSProxy is *less* fancy than NSObject and allows for overriding more methods. This change is probably purely academic in that we don't use this class to forward NSObject specific messages, but I believe this is the 'right thing'. * Update comment, thanks @maicki! --- AsyncDisplayKit/Details/ASWeakProxy.h | 2 +- AsyncDisplayKit/Details/ASWeakProxy.m | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/Details/ASWeakProxy.h b/AsyncDisplayKit/Details/ASWeakProxy.h index 1891085238..a3c2276b06 100644 --- a/AsyncDisplayKit/Details/ASWeakProxy.h +++ b/AsyncDisplayKit/Details/ASWeakProxy.h @@ -12,7 +12,7 @@ #import -@interface ASWeakProxy : NSObject +@interface ASWeakProxy : NSProxy @property (nonatomic, weak, readonly) id target; diff --git a/AsyncDisplayKit/Details/ASWeakProxy.m b/AsyncDisplayKit/Details/ASWeakProxy.m index 506f274e7a..d26439f178 100644 --- a/AsyncDisplayKit/Details/ASWeakProxy.m +++ b/AsyncDisplayKit/Details/ASWeakProxy.m @@ -16,7 +16,7 @@ - (instancetype)initWithTarget:(id)target { - if (self = [super init]) { + if (self) { _target = target; } return self; @@ -32,4 +32,24 @@ return _target; } +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + // Check for a compiled definition for the selector + NSMethodSignature *methodSignature = [[_target class] instanceMethodSignatureForSelector:aSelector]; + + // Unfortunately, in order to get this object to work properly, the use of a method which creates an NSMethodSignature + // from a C string. -methodSignatureForSelector is called when a compiled definition for the selector cannot be found. + // This is the place where we have to create our own dud NSMethodSignature. This is necessary because if this method + // returns nil, a selector not found exception is raised. The string argument to -signatureWithObjCTypes: outlines + // the return type and arguments to the message. To return a dud NSMethodSignature, pretty much any signature will + // suffice. Since the -forwardInvocation call will do nothing if the delegate does not respond to the selector, + // the dud NSMethodSignature simply gets us around the exception. + return methodSignature ?: [NSMethodSignature signatureWithObjCTypes:"@^v^c"]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + // If we are down here this means _target where nil. Just don't do anything to prevent a crash +} + @end From 4baffea8f6f46ac24eabc520d73215a549d2c3fb Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 17 Aug 2016 19:58:40 -0700 Subject: [PATCH 37/50] [ASTableView] Ignore table view cell layouts when there's no node assigned (#2091) --- AsyncDisplayKit/ASTableView.mm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 079ca9e241..8dc5052495 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -1163,8 +1163,12 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (void)didLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell { - CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; ASCellNode *node = tableViewCell.node; + if (node == nil) { + return; + } + + CGFloat contentViewWidth = tableViewCell.contentView.bounds.size.width; ASSizeRange constrainedSize = node.constrainedSizeForCalculatedLayout; // Table view cells should always fill its content view width. From 00af76226dc2ca4f39e75a26ec34a55e3735b801 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Aug 2016 09:45:43 -0700 Subject: [PATCH 38/50] [ASDisplayNode] Clarify logic around calling setNeedsDisplay (#2094) --- AsyncDisplayKit/ASDisplayNode.mm | 26 +++++++++---------- .../Private/ASDisplayNodeInternal.h | 4 +-- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index d340108403..27c08e8e58 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -284,7 +284,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) _defaultLayoutTransitionOptions = UIViewAnimationOptionBeginFromCurrentState; _flags.canClearContentsOfLayer = YES; - _flags.canCallNeedsDisplayOfLayer = NO; + _flags.canCallSetNeedsDisplayOfLayer = YES; } - (instancetype)init @@ -454,12 +454,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } // Update flags related to special handling of UIImageView layers. More details on the flags - if (_flags.synchronous) { - if ([view isKindOfClass:[UIImageView class]]) { - _flags.canClearContentsOfLayer = NO; - } else { - _flags.canCallNeedsDisplayOfLayer = YES; - } + if (_flags.synchronous && [_viewClass isSubclassOfClass:[UIImageView class]]) { + _flags.canClearContentsOfLayer = NO; + _flags.canCallSetNeedsDisplayOfLayer = NO; } return view; @@ -2056,11 +2053,11 @@ static NSInteger incrementIfFound(NSInteger i) { _flags.implementsInstanceDrawRect || _flags.implementsInstanceImageDisplay; } -// Helper method to determine if it's save to call setNeedsDisplay on a layer without throwing away the content. -// For details look at the comment on the canCallNeedsDisplayOfLayer flag -- (BOOL)__canCallNeedsDisplayOfLayer +// Helper method to determine if it's safe to call setNeedsDisplay on a layer without throwing away the content. +// For details look at the comment on the canCallSetNeedsDisplayOfLayer flag +- (BOOL)__canCallSetNeedsDisplayOfLayer { - return _flags.canCallNeedsDisplayOfLayer; + return _flags.canCallSetNeedsDisplayOfLayer; } - (BOOL)placeholderShouldPersist @@ -2111,9 +2108,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) ASDisplayNode *node = [layer asyncdisplaykit_node]; - if ([node __canCallNeedsDisplayOfLayer]) { - // Layers for UIKit components that are wrapped wtihin a node needs to be set to be displayed as the contents of - // the layer get's cleared and would not be recreated otherwise + if (node.isSynchronous && [node __canCallSetNeedsDisplayOfLayer]) { + // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of + // the layer get's cleared and would not be recreated otherwise. + // We do not call this for _ASDisplayLayer as an optimization. [layer setNeedsDisplay]; } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index c1d09979f4..3e9bc3985b 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -83,10 +83,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo // Prevent calling setNeedsDisplay on a layer that backs a UIImageView. Usually calling setNeedsDisplay on a CALayer // triggers a recreation of the contents of layer unfortunately calling it on a CALayer that backs a UIImageView - // it goes trough the normal flow to assign the contents to a layer via the CALayerDelegate methods. Unfortunately + // it goes through the normal flow to assign the contents to a layer via the CALayerDelegate methods. Unfortunately // UIImageView does not do recreate the layer contents the usual way, it actually does not implement some of the // methods at all instead it throws away the contents of the layer and nothing will show up. - unsigned canCallNeedsDisplayOfLayer:1; + unsigned canCallSetNeedsDisplayOfLayer:1; // whether custom drawing is enabled unsigned implementsInstanceDrawRect:1; From f61b7d5d5d5d8d5b93d288afcf20b99558c925a9 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Aug 2016 10:16:09 -0700 Subject: [PATCH 39/50] [ASDisplayView] Remove some gunk (#2095) --- AsyncDisplayKit/Details/_ASDisplayLayer.mm | 13 ++------- AsyncDisplayKit/Details/_ASDisplayView.mm | 32 ++-------------------- 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 8fe1963538..4c70356f17 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -101,18 +101,11 @@ #endif - (void)layoutSublayers -{ +{ + ASDisplayNodeAssertMainThread(); [super layoutSublayers]; - ASDisplayNode *node = self.asyncdisplaykit_node; - if (ASDisplayNodeThreadIsMain()) { - [node __layout]; - } else { - ASDisplayNodeFailAssert(@"not reached assertion"); - dispatch_async(dispatch_get_main_queue(), ^ { - [node __layout]; - }); - } + [self.asyncdisplaykit_node __layout]; } - (void)setNeedsDisplay diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 8eab7fdacd..cef6e16955 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -43,10 +43,6 @@ } #pragma mark - NSObject Overrides -- (instancetype)init -{ - return [self initWithFrame:CGRectZero]; -} - (NSString *)description { @@ -57,14 +53,6 @@ #pragma mark - UIView Overrides -- (instancetype)initWithFrame:(CGRect)frame -{ - if (!(self = [super initWithFrame:frame])) - return nil; - - return self; -} - - (void)willMoveToWindow:(UIWindow *)newWindow { BOOL visible = (newWindow != nil); @@ -192,25 +180,9 @@ - (void)setNeedsDisplay { + ASDisplayNodeAssertMainThread(); // Standard implementation does not actually get to the layer, at least for views that don't implement drawRect:. - if (ASDisplayNodeThreadIsMain()) { - [self.layer setNeedsDisplay]; - } else { - dispatch_async(dispatch_get_main_queue(), ^ { - [self.layer setNeedsDisplay]; - }); - } -} - -- (void)setNeedsLayout -{ - if (ASDisplayNodeThreadIsMain()) { - [super setNeedsLayout]; - } else { - dispatch_async(dispatch_get_main_queue(), ^ { - [super setNeedsLayout]; - }); - } + [self.layer setNeedsDisplay]; } - (UIViewContentMode)contentMode From 85f2a2a1280ecb214d04e58b06f73d94363a4c8e Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 18 Aug 2016 13:08:30 -0700 Subject: [PATCH 40/50] Remove reference to gone file ASLayoutSpec+Private.h (#2101) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 94789e0a59..e046ec7d7a 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -439,7 +439,6 @@ DE4843DB1C93EAB100A1F33B /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; - DE72E2AC1D4D90E000DDB258 /* ASLayoutSpec+Private.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 69F6058C1D3DA27E00C8CA38 /* ASLayoutSpec+Private.h */; }; DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; }; @@ -710,7 +709,6 @@ F7CE6C611D2CDB3E00BE4C15 /* ASLayoutable.h in CopyFiles */, F7CE6C621D2CDB3E00BE4C15 /* ASLayoutablePrivate.h in CopyFiles */, F7CE6C631D2CDB3E00BE4C15 /* ASLayoutSpec.h in CopyFiles */, - DE72E2AC1D4D90E000DDB258 /* ASLayoutSpec+Private.h in CopyFiles */, F7CE6C641D2CDB3E00BE4C15 /* ASOverlayLayoutSpec.h in CopyFiles */, F7CE6C651D2CDB3E00BE4C15 /* ASRatioLayoutSpec.h in CopyFiles */, F7CE6C661D2CDB3E00BE4C15 /* ASRelativeLayoutSpec.h in CopyFiles */, From c409e797147e1cf96c0f7adc5f025dd39304d526 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 18 Aug 2016 13:12:19 -0700 Subject: [PATCH 41/50] Fix accessibleElements warning in _ASDisplayView (#2100) --- AsyncDisplayKit/Details/_ASDisplayView.h | 2 -- AsyncDisplayKit/Details/_ASDisplayView.mm | 6 +++--- AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h | 4 ++++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/AsyncDisplayKit/Details/_ASDisplayView.h b/AsyncDisplayKit/Details/_ASDisplayView.h index 2168491f75..4e52eb2892 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.h +++ b/AsyncDisplayKit/Details/_ASDisplayView.h @@ -16,8 +16,6 @@ @interface _ASDisplayView : UIView -@property (copy, nonatomic) NSArray *accessibleElements; - // These methods expose a way for ASDisplayNode touch events to let the view call super touch events // Some UIKit mechanisms, like UITableView and UICollectionView selection handling, require this to work - (void)__forwardTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index cef6e16955..b0d1c3a8ab 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -9,6 +9,7 @@ // #import "_ASDisplayView.h" +#import "_ASDisplayViewAccessiblity.h" #import "_ASCoreAnimationExtras.h" #import "ASDisplayNodeInternal.h" @@ -34,7 +35,6 @@ CGRect _lastAccessibleElementsFrame; } -@synthesize accessibleElements = _accessibleElements; @synthesize asyncdisplaykit_node = _node; + (Class)layerClass @@ -165,7 +165,7 @@ [super addSubview:view]; #ifndef ASDK_ACCESSIBILITY_DISABLE - [self setAccessibleElements:nil]; + self.accessibleElements = nil; #endif } @@ -174,7 +174,7 @@ [super willRemoveSubview:subview]; #ifndef ASDK_ACCESSIBILITY_DISABLE - [self setAccessibleElements:nil]; + self.accessibleElements = nil; #endif } diff --git a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h index 0041d667bf..b3dab91ab4 100644 --- a/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h +++ b/AsyncDisplayKit/Details/_ASDisplayViewAccessiblity.h @@ -9,3 +9,7 @@ // #import + +@interface _ASDisplayView (UIAccessibilityContainer) +@property (copy, nonatomic) NSArray *accessibleElements; +@end From 971d43ad4dccaa39d82d06b4b9c7cdcfa4eb337b Mon Sep 17 00:00:00 2001 From: Hannah Trosi Date: Thu, 18 Aug 2016 15:01:41 -0700 Subject: [PATCH 42/50] [Build settings] add missing newlines at end of file --- AsyncDisplayKit.xcodeproj/project.pbxproj | 3 +++ AsyncDisplayKit/ASMapNode.h | 2 +- AsyncDisplayKit/ASMapNode.mm | 2 +- AsyncDisplayKit/ASRunLoopQueue.h | 2 +- AsyncDisplayKit/ASVisibilityProtocols.m | 2 +- AsyncDisplayKit/AsyncDisplayKit-Prefix.pch | 2 +- AsyncDisplayKit/Details/ASCollectionDataController.h | 2 +- AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h | 2 +- AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m | 2 +- AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h | 2 +- AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m | 2 +- AsyncDisplayKit/Details/ASWeakSet.h | 2 +- .../Details/NSMutableAttributedString+TextKitAdditions.h | 2 +- AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h | 2 +- AsyncDisplayKit/Layout/ASStackLayoutSpec.h | 2 +- AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h | 2 +- AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h | 2 +- AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h | 2 +- AsyncDisplayKit/UIImage+ASConvenience.m | 2 +- 19 files changed, 21 insertions(+), 18 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e046ec7d7a..2ce5ad2830 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -2494,6 +2494,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; @@ -2515,6 +2516,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; @@ -2696,6 +2698,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "AsyncDisplayKit/AsyncDisplayKit-Prefix.pch"; GCC_TREAT_WARNINGS_AS_ERRORS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; OTHER_CFLAGS = "-Wall"; diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 80d63d4bb0..5af8547ae5 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -74,4 +74,4 @@ typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) NS_ASSUME_NONNULL_END -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index e334ab633a..a7d8aa6d80 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -403,4 +403,4 @@ } } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/ASRunLoopQueue.h b/AsyncDisplayKit/ASRunLoopQueue.h index 37b0031eda..32382ef957 100644 --- a/AsyncDisplayKit/ASRunLoopQueue.h +++ b/AsyncDisplayKit/ASRunLoopQueue.h @@ -24,4 +24,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASVisibilityProtocols.m b/AsyncDisplayKit/ASVisibilityProtocols.m index 62f9879e14..6a85a7b4d0 100644 --- a/AsyncDisplayKit/ASVisibilityProtocols.m +++ b/AsyncDisplayKit/ASVisibilityProtocols.m @@ -22,4 +22,4 @@ ASLayoutRangeMode ASLayoutRangeModeForVisibilityDepth(NSUInteger visibilityDepth return ASLayoutRangeModeVisibleOnly; } return ASLayoutRangeModeLowMemory; -} \ No newline at end of file +} diff --git a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch b/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch index c2e8081429..0062314263 100644 --- a/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch +++ b/AsyncDisplayKit/AsyncDisplayKit-Prefix.pch @@ -16,4 +16,4 @@ // search path e.g. they've dragged in the framework (technically this will not be able to detect if // a user does not include the framework in the link binary with build step). #define PIN_REMOTE_IMAGE __has_include() -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 2f19e09757..92c31f1feb 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -44,4 +44,4 @@ - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h b/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h index 0fdda5b829..af00fe7195 100644 --- a/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h +++ b/AsyncDisplayKit/Details/ASMutableAttributedStringBuilder.h @@ -61,4 +61,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m index 70e3b194da..241fb71810 100644 --- a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -271,4 +271,4 @@ } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h index d54d17ba11..72b45ea561 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.h @@ -68,4 +68,4 @@ extern NSString *const ASPhotosURLScheme; @end // NS_ASSUME_NONNULL_END -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m index 79a1c060c3..aac715563c 100644 --- a/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m +++ b/AsyncDisplayKit/Details/ASPhotosFrameworkImageRequest.m @@ -163,4 +163,4 @@ static NSString *const _ASPhotosURLQueryKeyCropHeight = @"crop_h"; } @end -#endif \ No newline at end of file +#endif diff --git a/AsyncDisplayKit/Details/ASWeakSet.h b/AsyncDisplayKit/Details/ASWeakSet.h index 9a8707eb84..14d1a9d7cf 100644 --- a/AsyncDisplayKit/Details/ASWeakSet.h +++ b/AsyncDisplayKit/Details/ASWeakSet.h @@ -43,4 +43,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h b/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h index 0486bb28c2..fe1e9cf98d 100644 --- a/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h +++ b/AsyncDisplayKit/Details/NSMutableAttributedString+TextKitAdditions.h @@ -24,4 +24,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h index e064a7d58e..461c0966fd 100644 --- a/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h +++ b/AsyncDisplayKit/Details/UICollectionViewLayout+ASConvenience.h @@ -18,4 +18,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index 590c39bbf7..289dc234b1 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -85,4 +85,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h b/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h index e2675beba9..f26ac6b732 100644 --- a/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h +++ b/AsyncDisplayKit/Private/ASBasicImageDownloaderInternal.h @@ -18,4 +18,4 @@ - (BOOL)isCancelled; - (void)cancel; -@end \ No newline at end of file +@end diff --git a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h b/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h index c558e859dc..20255c13ae 100644 --- a/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h +++ b/AsyncDisplayKit/TextKit/ASEqualityHashHelpers.h @@ -168,4 +168,4 @@ namespace ASTupleOperations } }; -} \ No newline at end of file +} diff --git a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h index 7ef30af71a..58df9cc83d 100644 --- a/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h +++ b/AsyncDisplayKit/TextKit/ASTextNodeWordKerner.h @@ -32,4 +32,4 @@ NS_ASSUME_NONNULL_BEGIN @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/UIImage+ASConvenience.m b/AsyncDisplayKit/UIImage+ASConvenience.m index 4b3ce606ec..afdaa39195 100644 --- a/AsyncDisplayKit/UIImage+ASConvenience.m +++ b/AsyncDisplayKit/UIImage+ASConvenience.m @@ -128,4 +128,4 @@ return result; } -@end \ No newline at end of file +@end From 0bd664f9ed245da7cac493a98d556abb52af9cbe Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 19 Aug 2016 11:18:31 -0700 Subject: [PATCH 43/50] Remove numberOfSectionsForSupplementaryNodeOfKind: Method (#2102) * Add unit test to confirm that supplementary items must be contained within actual sections * [ASLayoutInspecting] Deprecate collectionView:numberOfSectionsForSupplementaryNodeOfKind: * Add another test to dig deeper into UICollectionView --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++ AsyncDisplayKit/ASCollectionView.mm | 5 -- .../Details/ASCollectionDataController.h | 2 - .../Details/ASCollectionDataController.mm | 4 +- .../ASCollectionViewFlowLayoutInspector.h | 15 ++-- .../ASCollectionViewFlowLayoutInspector.m | 15 ---- ...ASCollectionViewFlowLayoutInspectorTests.m | 28 -------- .../ASUICollectionViewTests.m | 70 +++++++++++++++++++ 8 files changed, 86 insertions(+), 57 deletions(-) create mode 100644 AsyncDisplayKitTests/ASUICollectionViewTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 2ce5ad2830..ac58f4a2f1 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -407,6 +407,7 @@ B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; }; B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058D09AF195D04C000B7D73C /* Foundation.framework */; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */; }; CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; }; CC3B20851C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; CC3B20861C3F76D600798563 /* ASPendingStateController.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC3B20821C3F76D600798563 /* ASPendingStateController.mm */; }; @@ -1076,6 +1077,7 @@ 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 = ""; }; BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; + CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASUICollectionViewTests.m; sourceTree = ""; }; CC3B20811C3F76D600798563 /* ASPendingStateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPendingStateController.h; sourceTree = ""; }; CC3B20821C3F76D600798563 /* ASPendingStateController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASPendingStateController.mm; sourceTree = ""; }; CC3B20871C3F7A5400798563 /* ASWeakSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASWeakSet.h; sourceTree = ""; }; @@ -1322,6 +1324,7 @@ 058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = { isa = PBXGroup; children = ( + CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */, CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */, 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, @@ -2195,6 +2198,7 @@ 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */, ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, + CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index dd12112668..7ddcf1d5af 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -1033,11 +1033,6 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section]; } -- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind; -{ - return [self.layoutInspector collectionView:self numberOfSectionsForSupplementaryNodeOfKind:kind]; -} - #pragma mark - ASRangeControllerDataSource - (ASRangeController *)rangeController diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 92c31f1feb..b99bb9e5dc 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -26,8 +26,6 @@ - (NSArray *)supplementaryNodeKindsInDataController:(ASCollectionDataController *)dataController; -- (NSUInteger)dataController:(ASCollectionDataController *)dataController numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind; - - (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; @optional diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 8f5d505815..170af2b319 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -65,7 +65,7 @@ [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; // Insert each section - NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; + NSUInteger sectionCount = self.itemCountsFromDataSource.size(); NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; @@ -194,7 +194,7 @@ ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; id source = self.collectionDataSource; - NSUInteger sectionCount = [source dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; + NSUInteger sectionCount = self.itemCountsFromDataSource.size(); for (NSUInteger i = 0; i < sectionCount; i++) { NSUInteger rowCount = [source dataController:self supplementaryNodesOfKind:kind inSection:i]; for (NSUInteger j = 0; j < rowCount; j++) { diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h index 608a93cea1..0dafa8cbff 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.h @@ -33,11 +33,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForSupplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; -/** - * Asks the inspector for the number of supplementary sections in the collection view for the given kind. - */ -- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind; - /** * Asks the inspector for the number of supplementary views for the given kind in the specified section. */ @@ -57,6 +52,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)didChangeCollectionViewDataSource:(nullable id)dataSource; +#pragma mark Deprecated Methods + +/** + * Asks the inspector for the number of supplementary sections in the collection view for the given kind. + * + * @deprecated This method will not be called, and it is only deprecated as a reminder to remove it. + * Supplementary elements must exist in the same sections as regular collection view items i.e. -numberOfSectionsInCollectionView: + */ +- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind ASDISPLAYNODE_DEPRECATED; + @end /** diff --git a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m index 9ae4d1e158..4d8aa8e287 100644 --- a/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m +++ b/AsyncDisplayKit/Details/ASCollectionViewFlowLayoutInspector.m @@ -74,12 +74,6 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView return ASSizeRangeMake(CGSizeZero, CGSizeZero); } -- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind -{ - ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); - return 0; -} - - (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section { ASDisplayNodeAssert(NO, @"To support supplementary nodes in ASCollectionView, it must have a layoutInspector for layout inspection. (See ASCollectionViewFlowLayoutInspector for an example.)"); @@ -171,15 +165,6 @@ static inline ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView return ASSizeRangeMake(CGSizeZero, constrainedSize); } -- (NSUInteger)collectionView:(ASCollectionView *)collectionView numberOfSectionsForSupplementaryNodeOfKind:(NSString *)kind -{ - if (_dataSourceFlags.implementsNumberOfSectionsInCollectionView) { - return [collectionView.asyncDataSource numberOfSectionsInCollectionView:collectionView]; - } else { - return 1; - } -} - - (NSUInteger)collectionView:(ASCollectionView *)collectionView supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section { return [self layoutHasSupplementaryViewOfKind:kind inSection:section collectionView:collectionView] ? 1 : 0; diff --git a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m index ba02de42e7..58de30ff19 100644 --- a/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m +++ b/AsyncDisplayKitTests/ASCollectionViewFlowLayoutInspectorTests.m @@ -294,34 +294,6 @@ collectionView.asyncDelegate = nil; } -#pragma mark - #collectionView:numberOfSectionsForSupplementaryNodeOfKind: - -- (void)testThatItRespondsWithTheDefaultNumberOfSections -{ - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; - NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - XCTAssert(sections == 1, @"should return 1 by default"); - - collectionView.asyncDataSource = nil; - collectionView.asyncDelegate = nil; -} - -- (void)testThatItProvidesTheNumberOfSectionsInTheDataSource -{ - InspectorTestDataSource *dataSource = [[InspectorTestDataSource alloc] init]; - UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; - ASCollectionView *collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - collectionView.asyncDataSource = dataSource; - ASCollectionViewFlowLayoutInspector *inspector = [[ASCollectionViewFlowLayoutInspector alloc] initWithCollectionView:collectionView flowLayout:layout]; - NSUInteger sections = [inspector collectionView:collectionView numberOfSectionsForSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; - XCTAssert(sections == 2, @"should return 2"); - - collectionView.asyncDataSource = nil; - collectionView.asyncDelegate = nil; -} - #pragma mark - #collectionView:supplementaryNodesOfKind:inSection: - (void)testThatItReturnsOneWhenAValidSizeIsImplementedOnTheDelegate diff --git a/AsyncDisplayKitTests/ASUICollectionViewTests.m b/AsyncDisplayKitTests/ASUICollectionViewTests.m new file mode 100644 index 0000000000..e325265741 --- /dev/null +++ b/AsyncDisplayKitTests/ASUICollectionViewTests.m @@ -0,0 +1,70 @@ +// +// ASUICollectionViewTests.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 8/18/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import +#import + +@interface ASUICollectionViewTests : XCTestCase + +@end + +@implementation ASUICollectionViewTests + +/// Test normal item-affiliated supplementary node +- (void)testNormalTwoIndexSupplementaryElement +{ + [self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:1] sectionCount:2 expectException:NO]; +} + +/// If your supp is indexPathForItem:inSection:, the section index must be in bounds +- (void)testThatSupplementariesWithItemIndexesMustBeWithinNormalSections +{ + [self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:3] sectionCount:2 expectException:YES]; +} + +/// If your supp is indexPathWithIndex:, that's OK even if that section is out of bounds! +- (void)testThatSupplementariesWithOneIndexAreOKOutOfSectionBounds +{ + [self _testSupplementaryNodeAtIndexPath:[NSIndexPath indexPathWithIndex:3] sectionCount:2 expectException:NO]; +} + +- (void)_testSupplementaryNodeAtIndexPath:(NSIndexPath *)indexPath sectionCount:(NSInteger)sectionCount expectException:(BOOL)shouldFail +{ + UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:@"SuppKind" withIndexPath:indexPath]; + attr.frame = CGRectMake(0, 0, 20, 20); + UICollectionViewLayout *layout = [[UICollectionViewLayout alloc] init]; + id layoutMock = [OCMockObject partialMockForObject:layout]; + + [[[[layoutMock expect] ignoringNonObjectArgs] andReturn:@[ attr ]] layoutAttributesForElementsInRect:CGRectZero]; + UICollectionView *cv = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) collectionViewLayout:layoutMock]; + [cv registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:@"SuppKind" withReuseIdentifier:@"ReuseID"]; + + id dataSource = [OCMockObject niceMockForProtocol:@protocol(UICollectionViewDataSource)]; + __block id view = nil; + [[[dataSource expect] andDo:^(NSInvocation *invocation) { + NSIndexPath *indexPath = nil; + [invocation getArgument:&indexPath atIndex:4]; + view = [cv dequeueReusableSupplementaryViewOfKind:@"SuppKind" withReuseIdentifier:@"ReuseID" forIndexPath:indexPath]; + [invocation setReturnValue:&view]; + }] collectionView:cv viewForSupplementaryElementOfKind:@"SuppKind" atIndexPath:indexPath]; + [[[dataSource expect] andReturnValue:[NSNumber numberWithInteger:sectionCount]] numberOfSectionsInCollectionView:cv]; + + cv.dataSource = dataSource; + if (shouldFail) { + XCTAssertThrowsSpecificNamed([cv layoutIfNeeded], NSException, NSInternalInconsistencyException); + } else { + [cv layoutIfNeeded]; + XCTAssertEqualObjects(attr, [cv layoutAttributesForSupplementaryElementOfKind:@"SuppKind" atIndexPath:indexPath]); + XCTAssertEqual(view, [cv supplementaryViewForElementKind:@"SuppKind" atIndexPath:indexPath]); + } + + [dataSource verify]; + [layoutMock verify]; +} + +@end From fa3a988b5439f7a9989dd901fe734e17a5fb4b45 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 19 Aug 2016 11:47:56 -0700 Subject: [PATCH 44/50] [ASPagerNode] Rename constrainedSizeForNodeAtIndexPath: to constrainedSizeForNodeAtIndex: for consistency (#2097) --- AsyncDisplayKit/ASPagerNode.h | 8 ++++---- AsyncDisplayKit/ASPagerNode.m | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASPagerNode.h b/AsyncDisplayKit/ASPagerNode.h index 1b028b020b..5ca8f79674 100644 --- a/AsyncDisplayKit/ASPagerNode.h +++ b/AsyncDisplayKit/ASPagerNode.h @@ -61,13 +61,13 @@ NS_ASSUME_NONNULL_BEGIN @optional /** - * Provides the constrained size range for measuring the node at the index path. + * Provides the constrained size range for measuring the node at the index. * * @param pagerNode The sender. - * @param indexPath The index path of the node. - * @returns A constrained size range for layout the node at this index path. + * @param index The index of the node. + * @returns A constrained size range for layout the node at this index. */ -- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath; +- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndex:(NSInteger)index; @end diff --git a/AsyncDisplayKit/ASPagerNode.m b/AsyncDisplayKit/ASPagerNode.m index 561382c3f0..3b3db1360f 100644 --- a/AsyncDisplayKit/ASPagerNode.m +++ b/AsyncDisplayKit/ASPagerNode.m @@ -130,7 +130,7 @@ - (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { if (_pagerDelegateImplementsConstrainedSizeForNode) { - return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndexPath:indexPath]; + return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndex:indexPath.item]; } return ASSizeRangeMake(CGSizeZero, self.view.bounds.size); @@ -163,7 +163,7 @@ if (delegate != _pagerDelegate) { _pagerDelegate = delegate; - _pagerDelegateImplementsConstrainedSizeForNode = [_pagerDelegate respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndexPath:)]; + _pagerDelegateImplementsConstrainedSizeForNode = [_pagerDelegate respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndex:)]; _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; From 69e73065e51f07473d9a74f25e1c857f2390d96a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 19 Aug 2016 12:10:19 -0700 Subject: [PATCH 45/50] Avoid accessing itemCountsFromDataSource off-main (#2106) --- AsyncDisplayKit/Details/ASCollectionDataController.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 170af2b319..71366e0085 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -65,7 +65,10 @@ [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; // Insert each section - NSUInteger sectionCount = self.itemCountsFromDataSource.size(); + NSUInteger sectionCount = 0; + for (ASIndexedNodeContext *context in contexts) { + sectionCount = MAX(sectionCount, context.indexPath.section + 1); + } NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; for (int i = 0; i < sectionCount; i++) { [sections addObject:[NSMutableArray array]]; From 1951954ba5203131b38ac7d37882668acc546e48 Mon Sep 17 00:00:00 2001 From: Yue-Wang-Google Date: Fri, 19 Aug 2016 17:51:41 -0700 Subject: [PATCH 46/50] remove non unicode char (#2109) see https://github.com/facebook/AsyncDisplayKit/issues/2108 --- AsyncDisplayKit/ASDisplayNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 27c08e8e58..566b4ed9c6 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -2111,7 +2111,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) if (node.isSynchronous && [node __canCallSetNeedsDisplayOfLayer]) { // Layers for UIKit components that are wrapped within a node needs to be set to be displayed as the contents of // the layer get's cleared and would not be recreated otherwise. - // We do not call this for _ASDisplayLayer as an optimization. + // We do not call this for _ASDisplayLayer as an optimization. [layer setNeedsDisplay]; } From a38f3db6b0567e4e8001fbea812ede63d6858d6e Mon Sep 17 00:00:00 2001 From: Yue-Wang-Google Date: Sun, 21 Aug 2016 13:06:21 -0700 Subject: [PATCH 47/50] set the right attributes for block properties. (#2113) * set the right attributes for block properties. * oops. should remove strong. * Update ASImageNode.mm * revert to use copy for blocks. See apple documentation below https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html#//apple_ref/doc/uid/TP40011210-CH8-SW12 * make the block properties nonatomic. --- AsyncDisplayKit/ASImageNode.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/ASImageNode.mm b/AsyncDisplayKit/ASImageNode.mm index fa73b6a38f..13543e3b5c 100644 --- a/AsyncDisplayKit/ASImageNode.mm +++ b/AsyncDisplayKit/ASImageNode.mm @@ -54,9 +54,9 @@ struct ASImageNodeDrawParameters { @property CGRect imageDrawRect; @property BOOL isOpaque; @property (nonatomic, strong) UIColor *backgroundColor; -@property ASDisplayNodeContextModifier preContextBlock; -@property ASDisplayNodeContextModifier postContextBlock; -@property asimagenode_modification_block_t imageModificationBlock; +@property (nonatomic, copy) ASDisplayNodeContextModifier preContextBlock; +@property (nonatomic, copy) ASDisplayNodeContextModifier postContextBlock; +@property (nonatomic, copy) asimagenode_modification_block_t imageModificationBlock; @end From c0be871812d88f0b6f455d4482849481c790bb16 Mon Sep 17 00:00:00 2001 From: ricky Date: Mon, 22 Aug 2016 10:06:53 -0700 Subject: [PATCH 48/50] [ASViewController] Propagate the traits on willTransitionToTraitCollection (#2115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we were only propagating the trait collection on `viewWillTransitionToSize` since it is called shortly after `willTransitionToTraitCollection`. However, some important things can happen in that time “shortly after” (like collection view layout). As long as nothing changes from `willTransitionToTraitCollection` to `viewWillTransitionToSize` (which it shouldn’t) the traits will not be re-propagated anyhow. Also make sure to use the `ASEnvironmentTraitCollectionMakeDefault` method when creating new envTraitCollection so the struct isn’t filled with junk. --- AsyncDisplayKit/ASViewController.mm | 13 +++++++------ AsyncDisplayKit/Details/ASEnvironment.mm | 14 +++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/AsyncDisplayKit/ASViewController.mm b/AsyncDisplayKit/ASViewController.mm index a0e3681381..d05e9b83a0 100644 --- a/AsyncDisplayKit/ASViewController.mm +++ b/AsyncDisplayKit/ASViewController.mm @@ -313,12 +313,13 @@ ASVisibilityDepthImplementation; { [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator]; - // here we take the new UITraitCollection and use it to create a new ASEnvironmentTraitCollection on self.node - // We will propagate when the corresponding viewWillTransitionToSize:withTransitionCoordinator: is called and we have the - // new windowSize. There are cases when viewWillTransitionToSize: is called when willTransitionToTraitCollection: is not. - // Since we do the propagation on viewWillTransitionToSize: our subnodes should always get the proper trait collection. - ASEnvironmentTraitCollection asyncTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(newCollection); - self.node.environmentTraitCollection = asyncTraitCollection; + ASEnvironmentTraitCollection environmentTraitCollection = ASEnvironmentTraitCollectionFromUITraitCollection(newCollection); + // A call to willTransitionToTraitCollection:withTransitionCoordinator: is always followed by a call to viewWillTransitionToSize:withTransitionCoordinator:. + // However, it is not immediate and we may need the new trait collection before viewWillTransitionToSize:withTransitionCoordinator: is called. Our view already has the + // new bounds, so go ahead and set the containerSize and propagate the traits here. As long as nothing else in the traits changes (which it shouldn't) + // then we won't we will skip the propagation in viewWillTransitionToSize:withTransitionCoordinator:. + environmentTraitCollection.containerSize = self.view.bounds.size; + [self progagateNewEnvironmentTraitCollection:environmentTraitCollection]; } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator diff --git a/AsyncDisplayKit/Details/ASEnvironment.mm b/AsyncDisplayKit/Details/ASEnvironment.mm index 68aca910da..eb528d36e3 100644 --- a/AsyncDisplayKit/Details/ASEnvironment.mm +++ b/AsyncDisplayKit/Details/ASEnvironment.mm @@ -36,17 +36,17 @@ ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault() ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) { - ASEnvironmentTraitCollection asyncTraitCollection; + ASEnvironmentTraitCollection environmentTraitCollection = ASEnvironmentTraitCollectionMakeDefault(); if (AS_AT_LEAST_IOS8) { - asyncTraitCollection.displayScale = traitCollection.displayScale; - asyncTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; - asyncTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; - asyncTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; + environmentTraitCollection.displayScale = traitCollection.displayScale; + environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; + environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; + environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; if (AS_AT_LEAST_IOS9) { - asyncTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; } } - return asyncTraitCollection; + return environmentTraitCollection; } BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs) From 873bae2eed446d00fa12169cac739c7729e3af28 Mon Sep 17 00:00:00 2001 From: Michal Ziman Date: Mon, 22 Aug 2016 19:44:29 +0200 Subject: [PATCH 49/50] [ASMapNode] Add custom pin annotation for static maps (#1890) * Adds possibility to have custom annotation pins on static map. This resolves #1889. * Removes wrong example for map annotations and adds some annotations to correct map example. #1889 * Static map node now uses specific property block to get annotation views. * Changes self to strongSelf inside of the snapshotters completion block. * MapNode: Adds statement in documentation. * MapNode: Block for annotation view/image now returns UIImage and center offset is returned in inout param. * MapNode and map example: Fixes from review. * MapNode example: Gets image directly from custom annotation, without creating annotation view. --- AsyncDisplayKit/ASMapNode.h | 6 ++ AsyncDisplayKit/ASMapNode.mm | 41 ++++++++++-- .../Sample.xcodeproj/project.pbxproj | 8 ++- .../Sample/Assets.xcassets/Contents.json | 6 ++ .../Hill.imageset/Contents.json | 23 +++++++ .../Assets.xcassets/Hill.imageset/hill.png | Bin 0 -> 2959 bytes .../Assets.xcassets/Hill.imageset/hill@2x.png | Bin 0 -> 6094 bytes .../Assets.xcassets/Hill.imageset/hill@3x.png | Bin 0 -> 11050 bytes .../Water.imageset/Contents.json | 23 +++++++ .../Assets.xcassets/Water.imageset/water.png | Bin 0 -> 3256 bytes .../Water.imageset/water@2x.png | Bin 0 -> 7347 bytes .../Water.imageset/water@3x.png | Bin 0 -> 13644 bytes .../ASMapNode/Sample/CustomMapAnnotation.h | 28 ++++++++ .../ASMapNode/Sample/CustomMapAnnotation.m | 22 +++++++ examples/ASMapNode/Sample/MapHandlerNode.m | 61 ++++++++++++++++++ 15 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Contents.json create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@2x.png create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/Contents.json create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water.png create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png create mode 100644 examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png create mode 100644 examples/ASMapNode/Sample/CustomMapAnnotation.h create mode 100644 examples/ASMapNode/Sample/CustomMapAnnotation.m diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h index 5af8547ae5..1d9b96c7be 100644 --- a/AsyncDisplayKit/ASMapNode.h +++ b/AsyncDisplayKit/ASMapNode.h @@ -70,6 +70,12 @@ typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) */ @property (nonatomic, assign) ASMapNodeShowAnnotationsOptions showAnnotationsOptions; +/** + * @abstract The block which should return annotation image for static map based on provided annotation. + * @discussion This block is executed on an arbitrary serial queue. If this block is nil, standard pin is used. + */ +@property (nonatomic, copy, nullable) UIImage * _Nullable (^imageForStaticMapAnnotationBlock)(id annotation, CGPoint *centerOffset); + @end NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index a7d8aa6d80..7c64abef9a 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -167,6 +167,14 @@ self.options = options; } +- (void)setMapDelegate:(id)mapDelegate { + _mapDelegate = mapDelegate; + + if (_mapView) { + _mapView.delegate = mapDelegate; + } +} + #pragma mark - Snapshotter - (void)takeSnapshot @@ -209,15 +217,27 @@ UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); [image drawAtPoint:CGPointZero]; - // Get a standard annotation view pin. Future implementations should use a custom annotation image property. - MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; - UIImage *pinImage = pin.image; - CGSize pinSize = pin.bounds.size; + UIImage *pinImage; + CGPoint pinCenterOffset = CGPointZero; + + // Get a standard annotation view pin if there is no custom annotation block. + if (!strongSelf.imageForStaticMapAnnotationBlock) { + pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; + } for (id annotation in annotations) { + if (strongSelf.imageForStaticMapAnnotationBlock) { + // Get custom annotation image from custom annotation block. + pinImage = strongSelf.imageForStaticMapAnnotationBlock(annotation, &pinCenterOffset); + if (!pinImage) { + // just for case block returned nil, which can happen + pinImage = [strongSelf.class defaultPinImageWithCenterOffset:&pinCenterOffset]; + } + } + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; if (CGRectContainsPoint(finalImageRect, point)) { - CGPoint pinCenterOffset = pin.centerOffset; + CGSize pinSize = pinImage.size; point.x -= pinSize.width / 2.0; point.y -= pinSize.height / 2.0; point.x += pinCenterOffset.x; @@ -235,6 +255,17 @@ }]; } ++ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset +{ + static MKAnnotationView *pin; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + }); + *centerOffset = pin.centerOffset; + return pin.image; +} + - (void)setUpSnapshotter { _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:self.options]; diff --git a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj index b7da6bb298..1bc3e8c8b3 100644 --- a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj +++ b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj @@ -14,13 +14,14 @@ 694993D81C8B334F00491CA5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 694993D71C8B334F00491CA5 /* ViewController.m */; }; 694993DD1C8B334F00491CA5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 694993DC1C8B334F00491CA5 /* Assets.xcassets */; }; 694993E01C8B334F00491CA5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 694993DE1C8B334F00491CA5 /* LaunchScreen.storyboard */; }; + 905C815E1D362E9400EA2625 /* CustomMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 15AD337503831C4D33FF8B3A /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; 465082D55CCF1B0CB1AEBACC /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5E5E62821D13F39400D81E38 /* MapHandlerNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MapHandlerNode.h; sourceTree = ""; }; - 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = ""; }; + 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.c.objc; path = MapHandlerNode.m; sourceTree = ""; tabWidth = 2; }; 694993CD1C8B334F00491CA5 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 694993D11C8B334F00491CA5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 694993D31C8B334F00491CA5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -30,6 +31,8 @@ 694993DC1C8B334F00491CA5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 694993DF1C8B334F00491CA5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 694993E11C8B334F00491CA5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 905C815C1D362E9400EA2625 /* CustomMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomMapAnnotation.h; sourceTree = ""; }; + 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomMapAnnotation.m; sourceTree = ""; }; 97482F27BE2F7583EFE1BC2C /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -89,6 +92,8 @@ 694993D71C8B334F00491CA5 /* ViewController.m */, 5E5E62821D13F39400D81E38 /* MapHandlerNode.h */, 5E5E62831D13F39400D81E38 /* MapHandlerNode.m */, + 905C815C1D362E9400EA2625 /* CustomMapAnnotation.h */, + 905C815D1D362E9400EA2625 /* CustomMapAnnotation.m */, 694993DC1C8B334F00491CA5 /* Assets.xcassets */, 694993D01C8B334F00491CA5 /* Supporting Files */, ); @@ -229,6 +234,7 @@ 694993D81C8B334F00491CA5 /* ViewController.m in Sources */, 694993D51C8B334F00491CA5 /* AppDelegate.m in Sources */, 694993D21C8B334F00491CA5 /* main.m in Sources */, + 905C815E1D362E9400EA2625 /* CustomMapAnnotation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json new file mode 100644 index 0000000000..273884cba6 --- /dev/null +++ b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "hill.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "hill@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "hill@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill.png new file mode 100644 index 0000000000000000000000000000000000000000..8998668eb084abc2ce7c8a27caf1693bd8f95efe GIT binary patch literal 2959 zcmY*b2{_bU`~HnB`;sL?G>vtLu{C8ZS+k5a+gLMWohIuH1}zv_Vh|BoB2<>FMT!X7 z8=)+DZ6SN!7iEoa^uGW1|9$7W&Uv15pZnbRdCqm6>pJlkW(FLrLaYD)a2OftU7XaLxwK@p1p5TpPA%dP-` z%mjc_{yDEL)oBYX{)YAh0AS-i?jYb{jvxSl>~Yp;BHGjh>E`Dnk8$_IddQP}{Ap|e zP$wa2sgDN{10nfb_az`n8qnViBrQLN;ZVqL3K6dXMVnecbo~N7Am`;(NnzTyU z(=I;WNKe}u&GNzCC(+3Ue5*eeE1lVsJdc1Dax z2>umYeRwv~$22ZJzN13XIBGmqkav|!@9VJPAoQASQ3|VdkDa}@!yaaAZFMA9jgZ{G zIT;?JiBwiAejb*ZwI4*?*IfAZcK!U|8p%Yd=4v@Rs$JsLr9c!0y8rFR$#hLGiJhYK z<#}5(e!@?y1jeCneVd($e3FUo9h9B@c9^==IrumMyBMEZb6UWlLO`q@^-`GG^PQ1E zI|4LUWqomEB6xyhI7hI?5=Z$!b&_$(dZjmpVMAc>O0O2CJH;BJ-)08(V`jG~9&7KD z`#2k`ghLpK&Y|38lq&;{&kvFG#e8WZ;e{g=cl+LCi}_(+?hv4$E*tCDGMIdjEIzSY z))enmtxuLi`;yy0;s!VR*f+4PUpc2-ezx>Y6Z)5ikS<{pTO*>2mF;Qa;s=Wv`v>G- z=7*!6Nt0f`!1urtszsm1So-%o zmO}B8-E_CNHtnLRkyGQX17$^e5i!dlyb&p{1#F7?dQ`mkZTEw8J)^NIcLoDh?~;PhIwpaZOfv!gSbAyySBrK zL)(i{Nynle!YWK6lq(9^jgT8n@1UWZR8QxaPtVmph{-X1m%Uxj@19nh=E*qiq%75( zq`sTQ+k?zbUq{*Vu7V8_8KtjO(@LCvjIj>NMb#UQK|}Uy+266ZvfX)HihN|5KHOW9 zcs=dT&Gkek_c*TnZ3-0u8+Dm`g=< zvsLxVVRN`qhJ?E{rz4=rQ9i77tka@5f_jiO4sYewar!FE2%%R z&g@u_q^)=RVpSerGWd$bCr)ph4V8f129Xq5A8@+6G+Y`T7R0=w7+720CYd>rnrbmS zZFYSrO-!IY4R&+C+6ptCg;Q=2QUL{xFDhGb6&+l;N5t$KOj=ob0=QQmxYZ& zR_1Yr3cYvBB1cVA*xpUSDz(4Xi!-IY`w}*ULl63cF1Iu1r}uuAvNx|2{XF_5<>YX$ zwv7~+tII0o&vj{>1u`GCU5_#Q)2Y@hxS&eZI;{Fbhyah`S?K0k^-*8m9a(UYY#l!!mHXD8XU8hLTUo% zu{UojG}vm1MQlwor|sN^M)H)bS?UP8{}Hp1zeX;c8Kv)Ak9ZBFxn_-pfXkgDqk0K>6xWbaLnxllGwbyKuvF|1sMqRL$NJ3u}*SD8& ztj^uK#oMuql1YOToff(sGO1^~(p_gM{pRf~3OtUkE}Bf68&ck6Zbz|?%vw&PmiCiO z15b$>QvxdnrOP0&?$!aX_z@dp|GrVE?WLQCIWq}QHaS#5Pw?Gv%Qv4GA{~r?D^3B!kYIw||k2Pn$)gU}kly$LGk)^SP%J zqOZ9=dO-;EJEx#;#Vb_BQ`p8C6RpB?K7vS>+oh!q?{VI^pc(zNp6g?Y8%Ms4B@fZi zJ)xqc9@kWG`vsP~rogBsw0y|yOWr`0Pf$m^`|h?0JmSqB+Dz<4^mg|Zv<=INO=YW0 zm37@Ss)OMK1e;93Pdm`uKN)Ab>1nTH;)$JTXOD0SU$EGN`FXx!7Y;NI6=xwR8vCQG z`4fWhsy7m>fE+=tR`8YCtp<#-^{DR^C}w91rs62iUZ!|h-tez#L6h41POF+tv{Z}I z^=Hq8o3NO=v5Ai+OJAF#Q*U5?y@4UuKPEMRYgzb{?l9--%4f+W@)Ulj{3?1@?VqHO zz7F@(D&58|g`;!mXNH%(7NnC->T@6P*t!(1mez99Tlh_w$G zveZ;axgL^k?Y-3U`Rjn>$aJz4QuV$Wd^%LbRRU-Gms`59?o(#)L*|p)<@hMkB5JQ_ ziv}iqaG;T5@~&P3Q9jDn+OAS8p=M&(p@*MasSKsE)*SNRf$6TvsdkKHxtkehLv~S2 zq}nq!&PtDSHC@Fz&;>>v7&{7w?6W?DhWo?mumay?Sf}!(RH=Q77Oiu}kaxD@EM(=B zPLal=0F;yt%8NUj%J2Vspb zb4dG%!zdFf!Jkb$ER8ny_WB;k2py`58Y`#de0Z#iD9&yC5q?wv%R(<-7PHWNM!%vz zQm#U%8l$^zD{?re;!Oc@#*dtIk&Qd&8@H5@ti8@!XP-Y$JXl|Y5RVoQ000nbX{s7NM9ANXgY~e2+(>#K0*0%So)Q31 z{|5iY9`j+((pdn&Bk$byC$bL(TyITt zUjP7~{O<$;a&oBw0H8S(V&-S2rz`E~<-u?7fn%U`4aj(^|N59;#2PM*I1Y3rdufxi_2A$~!D|7t%R zmHmrK8~8w-ADsW{%L~c=!~B2Pe|%&G{+j=v%ly03f6#|ke+) zbuCpTND%PI8aEv>n|-$)YCLOg?1xVHUW7RTYa30HCQ1kNc00h~Ejn$gCJV5A>eXm= z|FBfzIL4S1Rhp^~*@V`puxp}aV4@C_6M1Xm;ix@G)z2yir(Ve$NJ01g*rGk-eDC`1 zoWXh(Vmv?R(YSJ4**at0eeHGLeZJ~C^fcORH2v34grX1E^vUbI`Vjx88kLR~bw*>g}o28un8kkDq{PMQUv8DefO|)HN5KUEWo$6d1FPN#h?c^v^?=Z8KA4wzk zXpyX7SMwADs%eo7@Bh)0qqmP;M)U@ShPC6ACD|}1dm4q$`iSZ~@jd}=x2N{+;E~*) zX65$_oFQGOgY(oXea}j}(EgZh@9*6k&TkI^wV5PppR<)tYL3))-E0OO&t6KEg?4%T z&fQiGQD&jJ-3Qu-`w&7#dNa2H)g=1L76uyA$&we;Sn@>U#3a%$TiN2gU~Fva#Zn&9 zV|2WZ(@W+4r%T6b=N-egU`C@-Tl++cx`{fjmn`bKJlUR&H8laz#@XH3G1U@3oKh^pvU*oXx zJaak$#q3{=U|^RSGOT;TW1S@zxBq4daSgXwWuE?c$a%-0uYKHnP^ zW}1RMXvAw-NGq|^37WA+F^*QJVZ(_wiy@`~D=m#|xHT<=mDxQ*jsB@7grV=y=v3{b zNidmlf9JK;E9u)T&7Ucjfm>{pq^$CA%9AmN8F)$T!~?=kD{luH1)Nze$KM8Vpx1DO zD$OT1C4#7$$w(n|Sj{yjejHKloKrEH*Ql!54##B(B4em{$N-K`E2FS6G7^()uAr}B z)7B?`p7mC=!W*!I&CdC{scy$(ZWf2OTxK~X;coqi@W^$gSgV|TVGd8lnRf!s!N7Uq zjU3HI0eS7EHOCdwD&7c|Gc*|UQTA07`na&WWic7ffNRb5^^ro}IRq8?F)~3sbGhLZ z_@~GHa#n1t6QLlNpX4b;Zs(AtQ3)DBS@TmQmJN7g6~>^Q80EV)@OPDmP8o#q0GnkM zfAye-Z|z_3xBHf2bEwPs3$61&|KJR=v+3s)TAWnWidtrl*I>+SI&(!g4(^rc66+@V zC-#x(o>Agrc)X^&MSM%n?nG!Xmc_AR?Q&I4Q)qu35@A%P7h(MA#IblIh?y3CWgl+H zZS=Le)~dLB$9`*YR3W1jyp)YYNY8kGl8VV;d1O>sA~H3O=noH^J)2AWZ2N_KhE8l! z{JqpB+t%986YnRA(LkvS{ugNa<2V}VMHlNY*k@brh%U^C6NzF61ayni-%;zgG))vt zd3BuOb5d%=X2*q=L};~5x-5&vD`A;=L&{Z-SYqybDRMYjBk9XLhGOo+THKnJ1>Yp^ zgh%Ks5Mhy(d5$)9hP1Gx#=_r>_EKm+0x7rMuDCT$%>y{BJwe(G-+|PC*QmCd#?MFl zR#i`f%ywBtsKIZAZnDXb%_YS=+-)n8iG;rMAq{+A<&&e^0r!dbnoXwlD!SI0KPpgz z$8VYXMFe2$$*ANuoA)YE_nKM8UI9Cx0qts9{Ko65l}M}68)a+&`}9wSsjvC?sH?Y! z6Ml>9Z)CBOa|4UrQVJRd=lyE}!xx;YTWi@&4)T!ir-6!6MKLL&wziwE1f3%g{X(U) zzFDjdPM}iW3tTtXuMu*{)3}V})NOZ+vY@zZef5KQ3DPQ+%B+R4K%1~+kAR8iWqbn^ z{(B{)c4s56^{q@KI~zd$VFYRKsB5S>&U$tT;c;wx z!O+bGrkb%Oh3bnx=MD691dj)M$){*+yjse_F&C5=gCCv2%qzt#2-|mb**$v15sTlj zKRy|^tgxd0qK(dK=d5$SYvo(mKw;KC;s{O(@;nR+1?`RbR|e`nJp;AN0J03T4Bv6y zW5jT#;BQ3jLYnto>oNh~+GNprV)|n47N_uLF5cF^|DlQqSJC-m$ojH6mCJvxT-ARq ztm&EYM^IqPOmp}nq(ktN-rsvSVx*a8-yAS0^?xbyc>xV0wejuJ(dy7#%rbY;68tUG zW%Pd?W#;$q3aup&*-p+^8KT(v*$T6bl24gLe0d^0*A7dJVP)fSVS=|gq!4KyDkOtb z816R_!pgn!Wangc!T4+sltL_Zv7#WkvUJ^{vbEbz-PCcX^i0gg?xZZf?}inX3Z~`{ zo{Cz?kZ@OON)If`MUI5NJCBfPlK*rw-mL$D19IkSo6&a!rvh@ceoW$9#g7;3`f zGVMidm3eAUe^6aYqFF>Ed_gRF;%KqRgybc9mP1P)^9wyCX?@hq3X0_TLpJl_lti%; zi-QXNYr1~f)U#U_7wB^#j6$n|VfJXA;5NMSvz}QRqMTTEfd(WLZUh`>0Vy1rooY>@ z7<#O5AbBA4pA#~J!@CxQNX@07U5rTNMfIu3(>uoAi&ijxLUt-)KY`&pYBHU)*Vgo` zQpUjDR~pq3_)WCUDY1Z^KRK(u;pfbPw=CwwZ!a~B>78I+A(+p7+8(`Xxx+VM8Z&+A zNWc;)-0-%JCYYsgWp_H3Osbd`I%Y5m;F@dGXV2g;CDP&2x{N{2N2p|BHa9YNf63-b zaV;L2*i^yE+<^33pjo^A8hWYTQ|zA1^Ku^(7!7dl)2l#F^WcgHWyZRt=s+jFxe za(jqX8(NYP*1|sfWRDlFQJ3p3)+80NC%5&^=pc{g7VkDM2o4u}4ChF0CJD_0_uw*}sgl z6{2pWu@bYI-l2v|kA90I)Fz`0jp~%#KAuR2x7fl}J^~7Px5D03ug7lYr4{6?fSctl|B6MD#s|S4J~|0zj&B{h7mfaVcJGra@E67RyrU^ z;3sZXh*P)~-iMBx>GnH4V8EXmEo>&Qw3-~=@DZM$1&#Wh9Oc6LOn=sElo%CfKO|X1 zvz9s#uR2dsMTOVQT>W$-04aVcfEia6u7k-#G*9x9dTst%YxP3Zq2X*@o4x8l!Y9E? zKf0*o?Wt?J8eKDgq4GL+bdJ6gi&OcdXA$JDh7XuuD}RaJvRNdJu zE6}@}=-aVE*P65*rB4S!ed_tmZcfqlj&sbL{rzEbSRigo=0AJE%}!;;t3fUX_c(_h zbn!1-mKNvUzqvlaZ9YJQlYW0L$2Oxr#9J>W^QzLaECgbd7hOwJ)64uDMHrQ)Yv&FnpA32j9*n@q1YUlasjIrd2Fd=XDUc2 z4J5pIq=z>gLp4yM!V$b3GFJzSNY~D8SM?MzJbjU6G};|9W`IhGf~tlFFH|i?e6K1O zVpP}!H$ECvcy^`SvhPjP81d2Yoh;6tWtH_b*I*8j)8oEiPr}%d(&F_BQKjsx3rqWc zA(H1}FX2X9S4|8F66ZNFq+uiIO~qG1sk3MOmoMM921b?&ewRoL5HLxQh>Udq1jJ*l zz(WF-1BZtF#ophX{u-+|qSpGtT(c5f!6T~hl(b}ca7?d{D1bZa5)=NCO|9y4Pu#wZ zo(VqtrA1J^yVAhAF;6s2G@1$A_5+?{!!M|o;$;z`?^KdkIbA)^S*+z}qQLBosiwuD zC!IW!yNz;rLw=t=2+oW~8q-wT2kL!$amiYKl;Q>rHixc8HeT{yq9T~xU^gMTU*8sf zs8?N6+TJf-5w%cjthLIneD%fkjE}X0CR`tt?)NHwQ`;!=acz!Yl%01p#bM#7CUWR95?cjOp+l@M8v2Ex%Hp%R z1JuV7w=W*07$6ElegF?NJ?$(~olosRyj|m(0^6duBKyp&|G2?_5cvK=u z_*%wqO0x>c%r`pOIblq+;RQ6fb)r#Hz%kXqfYWt22c4a|^Nu>7Lr7j*5%@(Q;z*HE zz8FoNZ`9Dku~DU`j)uCjVbJ-^D}RH5zMNwxMt<9-oa>%pkWxuEau@t9FQjp$sI)*u zYLL$Q5>w`+py-7}gVx2p^k}&VL@9VY*{7kBYdqM--DvP-9~-90??kqoX+mK&;D-&d zwou)|+kQgCKxY9OJ~}7d>WlEU^cY}E4s+}R&q7P8PxKG|9XJKQuPy^OVT+;7d%}TS zNbHs#AoC~+0^-5rCl0QWWriCaUju}zs@q`m9{9N++x}dN+b8aH&v__@DoV3y@4K&; zVe8R2>Wa~_PO;8SAIHpP6fhNfy(x`xrg~OspOaJjP-R6i&7>V+n-;JNDRGtpPe6&k z6r#q%DBN^t{YYs>_O|Ma08iS}LUH7IvB%zn5Od5U_L(rs++L0haAchtFFq;9orwo8 z*J!vWS@d-AT!mZX3$Jx2%TIM^#J5H_h`_uB4HMUA@1)d5rOqY5o1D#PA)iWX^`2gd zd%%(}!!h~i#@~AY?TQ2)+F+30e)hYI#sNNa{@qguJwhYZ0WIYzkXu(7nD%@uIPzpD zR9ZUeu;iwvhL+(MDb^QiaUo9EFCm{sg zop|uGUdnR`3a)Bc>v4v2or;$+gSYR8=-$PM^>Fq~Ul*g}6b?myGFU!_orO1>WZD!d z`Exl^mZ)=%&AvmIo4HrNaD0kzf8a^t@nZ$(7*=F2Pyo|NBc9CQVr_{Wk2eMIKal}Z z3(H`j+A#BR_e*a&1e!zZS$}NGUsW!o`8FwCZ|Xr)4hgw%>jr5d5h5yj<#pAwQ(>I~ z!g|bC!`(kbGIXMWlIjD%I0L5n2{c`Wx(ME7ERbzaP6YyNSi7~xmi{6)vM&sWNPdGw z;-{uo*8K@ox~h2>e$Cuj%9OmG!5J^VM;;E^xbR|M~7|kgS0w~_iuIU`m8`X`>BWYQ5P8nDsceHGtRBjRM^Ep4ItwsZ3O19%Pk zg3#0kO3s}2AnUmo$8_)qwwMpoWSERK3c$2Dasm$hZh_USQ)SzLtLi^$^?`ckc+kAd z1w2o(57)Fx@L&Z7ITC!-A60jj?60e?>5zK@#qBDz^YNT9qQQm=XMsQon}85$x>etL zCm*}-LcbtGiennLiK8exfB(s6ngszb){WoDqC(2W_g{A~^G;~DD_gSb)ZM1L`bRxp zP5b_Bd-ZsGwQS?$n2%C+O^b`-VMl|x=tKBk3i}@^+$rpp11iT6a-qYPg9pN$bX7t{kW-g-y0}&s{la|Sn;`fh6 zdcvb9Wn!_%BdHS43Vo-AR}(xm=|+aWDW)F)oPBa2m+vg-EPsrux7u&>+J$I(JfrU~ zo%<>-KCF=$FmG>kcmVu!&E_@JL*F_*kPL6%PwPICruxz6Zxbp73vLGCdwe?14RUNa z%6=!d+|dHD)VJ*aZ1HGAaQkllR@`d3Ccr;D?<30XNw4@_1xx7yWF$$~ z@t3FBH)nw>&m}PVb7278gnBa8LWeQNkZxGI3Mu&KEai{wS;_^dt8Pa-8hP#+#zV+I z6S_S}xbbu_P-#0jAX;I(@qq`P_)ro=T`IE7+ZmEcW_=a)E*8NzChC?f{@MX)aJ;kT zBRt>b$&!$`PVOBs=Ad^FFnyAE+J%Ess=Z&7R^SX3X#KEHPT6sX%@qp~-Tb3qHjsqt zi~QVRb1%YBdk>ZBx4ln`t;i=TGFzitze7N88|Fimj2$u@*-Ev!NWFPw^)+NN@=amh zNX{7|U8ShA%ckgQp*MIUaqsYBuHUcWSAL#wJXv8sW# H+eQ8hbdOZ} literal 0 HcmV?d00001 diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png b/examples/ASMapNode/Sample/Assets.xcassets/Hill.imageset/hill@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..761c66684a66ce9d33a3a8fbe27f4505a3427f14 GIT binary patch literal 11050 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 Wc0R{rrOZ} literal 0 HcmV?d00001 diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2cd019f20cb7c6fd8ee64fe00795cf711f95f7be GIT binary patch literal 7347 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
l|W3{%@QTNW9TTW)z zZ>>7!wK`cgX?w*L`}giCglwuuqYm1x>m0esW4NAGaf)#R60A;Tye2nK4yUf}VoN6=Wr8Icg7r9s>=(iex?hEU@bSc- z91dgAiDcJTh0mj%oHw<5!^WrGjR(@#Y5Y9<(d$(5-I$tqnPdFi9}p{OD_0`S?Q{sa&>r&c>k<@xSM>oeNmwc%l+`Y_Q4 zY7HalIC^2)M9vZB)i8}5>hdem2aVnFhiekb!HhWXnVbiCbsdo=UAPX0aVJHw_44E3 zOg{Jf#|LQ$4>m==@8NUJFR<5BoF0{GM?XGF96tc~9fg?szHIjXeMyv;R+g%eFbVt* Dg=7n% literal 0 HcmV?d00001 diff --git a/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png b/examples/ASMapNode/Sample/Assets.xcassets/Water.imageset/water@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e45cd67f2d080f2d17af25f8278089381502e68f GIT binary patch literal 13644 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 VhnZ>~k$--1k`R# +#import + +@interface CustomMapAnnotation : NSObject + +@property (assign, nonatomic) CLLocationCoordinate2D coordinate; +@property (copy, nonatomic, nullable) UIImage *image; +@property (copy, nonatomic, nullable) NSString *title; +@property (copy, nonatomic, nullable) NSString *subtitle; + +@end diff --git a/examples/ASMapNode/Sample/CustomMapAnnotation.m b/examples/ASMapNode/Sample/CustomMapAnnotation.m new file mode 100644 index 0000000000..a5da10ac94 --- /dev/null +++ b/examples/ASMapNode/Sample/CustomMapAnnotation.m @@ -0,0 +1,22 @@ +// +// CustomMapAnnotation.m +// ASDKMapTest +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 "CustomMapAnnotation.h" + +@implementation CustomMapAnnotation + +@end diff --git a/examples/ASMapNode/Sample/MapHandlerNode.m b/examples/ASMapNode/Sample/MapHandlerNode.m index 8a78445bb2..fc55eb6bc6 100644 --- a/examples/ASMapNode/Sample/MapHandlerNode.m +++ b/examples/ASMapNode/Sample/MapHandlerNode.m @@ -16,6 +16,7 @@ // #import "MapHandlerNode.h" +#import "CustomMapAnnotation.h" #import @@ -90,6 +91,22 @@ [_liveMapToggleButton setTitle:[self liveMapStr] withFont:nil withColor:[UIColor blueColor] forState:ASControlStateNormal]; [_liveMapToggleButton setTitle:[self liveMapStr] withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:ASControlStateHighlighted]; [_liveMapToggleButton addTarget:self action:@selector(toggleLiveMap) forControlEvents:ASControlNodeEventTouchUpInside]; + + // avoiding retain cycles + __weak MapHandlerNode *weakSelf = self; + + self.mapNode.imageForStaticMapAnnotationBlock = ^UIImage *(id annotation, CGPoint *centerOffset){ + MapHandlerNode *grabbedSelf = weakSelf; + if (grabbedSelf) { + if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { + CustomMapAnnotation *customAnnotation = (CustomMapAnnotation *)annotation; + return customAnnotation.image; + } + } + return nil; + }; + + [self addAnnotations]; } #pragma mark - Layout @@ -183,6 +200,30 @@ #pragma mark - Helpers +- (void)addAnnotations { + + MKPointAnnotation *brno = [MKPointAnnotation new]; + brno.coordinate = CLLocationCoordinate2DMake(49.2002211, 16.6078411); + brno.title = @"Brno city"; + + CustomMapAnnotation *atlantic = [CustomMapAnnotation new]; + atlantic.coordinate = CLLocationCoordinate2DMake(38.6442228, -29.9956942); + atlantic.title = @"Atlantic ocean"; + atlantic.image = [UIImage imageNamed:@"Water"]; + + CustomMapAnnotation *kilimanjaro = [CustomMapAnnotation new]; + kilimanjaro.coordinate = CLLocationCoordinate2DMake(-3.075833, 37.353333); + kilimanjaro.title = @"Kilimanjaro"; + kilimanjaro.image = [UIImage imageNamed:@"Hill"]; + + CustomMapAnnotation *mtblanc = [CustomMapAnnotation new]; + mtblanc.coordinate = CLLocationCoordinate2DMake(45.8325, 6.864444); + mtblanc.title = @"Mont Blanc"; + mtblanc.image = [UIImage imageNamed:@"Hill"]; + + self.mapNode.annotations = @[brno, atlantic, kilimanjaro, mtblanc]; +} + -(NSString *)liveMapStr { return _mapNode.liveMap ? @"Live Map is ON" : @"Live Map is OFF"; @@ -235,6 +276,21 @@ return YES; } +- (MKAnnotationView *)annotationViewForAnnotation:(id)annotation +{ + MKAnnotationView *av; + if ([annotation isKindOfClass:[CustomMapAnnotation class]]) { + av = [[MKAnnotationView alloc] init]; + av.centerOffset = CGPointMake(21, 21); + av.image = [(CustomMapAnnotation *)annotation image]; + } else { + av = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + } + + av.opaque = NO; + return av; +} + #pragma mark - MKMapViewDelegate - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { @@ -244,4 +300,9 @@ _deltaLonEditableNode.attributedText = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%f", mapView.region.span.longitudeDelta]]; } +- (MKAnnotationView *)mapView:(MKMapView *)__unused mapView viewForAnnotation:(id)annotation +{ + return [self annotationViewForAnnotation:annotation]; +} + @end From 8edc9fe08febed871eab512a24917764c37ddbb0 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Aug 2016 20:50:09 -0700 Subject: [PATCH 50/50] Ensure supplementary section count tracks item section count (#2118) --- .../Details/ASCollectionDataController.mm | 33 ++++--------------- AsyncDisplayKit/Details/ASDataController.mm | 8 ++--- .../Private/ASDataController+Subclasses.h | 4 +-- 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 71366e0085..4edc939ce1 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -43,17 +43,18 @@ return self; } -- (void)prepareForReloadData +- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount { + NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)]; for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@", kind); NSMutableArray *contexts = [NSMutableArray array]; - [self _populateSupplementaryNodesOfKind:kind withMutableContexts:contexts]; + [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableContexts:contexts]; _pendingContexts[kind] = contexts; } } -- (void)willReloadData +- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount { [_pendingContexts enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull kind, NSMutableArray * _Nonnull contexts, __unused BOOL * _Nonnull stop) { // Remove everything that existed before the reload, now that we're ready to insert replacements @@ -65,15 +66,11 @@ [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; // Insert each section - NSUInteger sectionCount = 0; - for (ASIndexedNodeContext *context in contexts) { - sectionCount = MAX(sectionCount, context.indexPath.section + 1); - } - NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; - for (int i = 0; i < sectionCount; i++) { + NSMutableArray *sections = [NSMutableArray arrayWithCapacity:newSectionCount]; + for (int i = 0; i < newSectionCount; i++) { [sections addObject:[NSMutableArray array]]; } - [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] completion:nil]; + [self insertSections:sections ofKind:kind atIndexSet:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newSectionCount)] completion:nil]; [self batchLayoutNodesFromContexts:contexts batchCompletion:^(NSArray *nodes, NSArray *indexPaths) { [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; @@ -191,22 +188,6 @@ [_pendingContexts removeAllObjects]; } -- (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableContexts:(NSMutableArray *)contexts -{ - id environment = [self.environmentDelegate dataControllerEnvironment]; - ASEnvironmentTraitCollection environmentTraitCollection = environment.environmentTraitCollection; - - id source = self.collectionDataSource; - NSUInteger sectionCount = self.itemCountsFromDataSource.size(); - for (NSUInteger i = 0; i < sectionCount; i++) { - NSUInteger rowCount = [source dataController:self supplementaryNodesOfKind:kind inSection:i]; - for (NSUInteger j = 0; j < rowCount; j++) { - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i]; - [self _populateSupplementaryNodeOfKind:kind atIndexPath:indexPath mutableContexts:contexts environmentTraitCollection:environmentTraitCollection]; - } - } -} - - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withSections:(NSIndexSet *)sections mutableContexts:(NSMutableArray *)contexts { id environment = [self.environmentDelegate dataControllerEnvironment]; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index e079f74b3c..d06e2b45b4 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -399,7 +399,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; NSArray *contexts = [self _populateFromDataSourceWithSectionIndexSet:sectionIndexSet]; // Allow subclasses to perform setup before going into the edit transaction - [self prepareForReloadData]; + [self prepareForReloadDataWithSectionCount:sectionCount]; dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ LOG(@"Edit Transaction - reloadData"); @@ -414,7 +414,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; [self _deleteSectionsAtIndexSet:indexSet withAnimationOptions:animationOptions]; } - [self willReloadData]; + [self willReloadDataWithSectionCount:sectionCount]; // Insert empty sections NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; @@ -636,12 +636,12 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; #pragma mark - Backing store manipulation optional hooks (Subclass API) -- (void)prepareForReloadData +- (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount { // Optional template hook for subclasses (See ASDataController+Subclasses.h) } -- (void)willReloadData +- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount { // Optional template hook for subclasses (See ASDataController+Subclasses.h) } diff --git a/AsyncDisplayKit/Private/ASDataController+Subclasses.h b/AsyncDisplayKit/Private/ASDataController+Subclasses.h index dd3905d96c..ddb7af0dee 100644 --- a/AsyncDisplayKit/Private/ASDataController+Subclasses.h +++ b/AsyncDisplayKit/Private/ASDataController+Subclasses.h @@ -95,7 +95,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS * The data source is locked at this point and accessing it is safe. Use this method to set up any nodes or * data stores before entering into editing the backing store on a background thread. */ - - (void)prepareForReloadData; + - (void)prepareForReloadDataWithSectionCount:(NSInteger)newSectionCount; /** * Notifies the subclass that the data controller is about to reload its data entirely @@ -104,7 +104,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray *nodes, NS * concrete implementation. This is a great place to perform new node creation like supplementary views * or header/footer nodes. */ -- (void)willReloadData; +- (void)willReloadDataWithSectionCount:(NSInteger)newSectionCount; /** * Notifies the subclass to perform setup before sections are inserted in the data controller