From e6b33dd062866897a605b9ef7f9b33d953ba7ad1 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 11 Nov 2015 00:44:01 -0800 Subject: [PATCH 01/12] Fix cell selection and highlighting in ASCollectionView --- AsyncDisplayKit/ASCollectionView.mm | 14 ++++-- .../Sample.xcodeproj/project.pbxproj | 6 +++ examples/ASCollectionView/Sample/ItemNode.h | 18 +++++++ examples/ASCollectionView/Sample/ItemNode.m | 49 +++++++++++++++++++ .../ASCollectionView/Sample/ViewController.m | 7 +-- 5 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 examples/ASCollectionView/Sample/ItemNode.h create mode 100644 examples/ASCollectionView/Sample/ItemNode.m diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 273f66e105..4f9ccead3a 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -115,13 +115,22 @@ @interface _ASCollectionViewCell : UICollectionViewCell @implementation _ASCollectionViewCell +- (void)setNode:(ASCellNode *)node +{ + _node = node; + node.selected = self.selected; + node.highlighted = self.highlighted; +} + - (void)setSelected:(BOOL)selected { + [super setSelected:selected]; _node.selected = selected; } - (void)setHighlighted:(BOOL)highlighted { + [super setHighlighted:highlighted]; _node.highlighted = highlighted; } @@ -478,11 +487,8 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell _ASCollectionViewCell *cell = [self dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath]; ASCellNode *node = [_dataController nodeAtIndexPath:indexPath]; - - [_rangeController configureContentView:cell.contentView forCellNode:node]; - cell.node = node; - + [_rangeController configureContentView:cell.contentView forCellNode:node]; return cell; } diff --git a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj index 9e3d98c2b5..56aef5f7f6 100644 --- a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25FDEC911BF31EE700CEB123 /* ItemNode.m */; }; 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */; }; 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; }; AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; }; @@ -17,6 +18,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 25FDEC901BF31EE700CEB123 /* ItemNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemNode.h; sourceTree = ""; }; + 25FDEC911BF31EE700CEB123 /* ItemNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemNode.m; sourceTree = ""; }; 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SupplementaryNode.h; sourceTree = ""; }; 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SupplementaryNode.m; sourceTree = ""; }; @@ -83,6 +86,8 @@ AC3C4A611A11F47200143C57 /* Supporting Files */, 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */, 9B92C8801BC17D3000EE46B2 /* SupplementaryNode.m */, + 25FDEC901BF31EE700CEB123 /* ItemNode.h */, + 25FDEC911BF31EE700CEB123 /* ItemNode.m */, ); indentWidth = 2; path = Sample; @@ -228,6 +233,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */, AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */, AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, diff --git a/examples/ASCollectionView/Sample/ItemNode.h b/examples/ASCollectionView/Sample/ItemNode.h new file mode 100644 index 0000000000..d95f2fa4d2 --- /dev/null +++ b/examples/ASCollectionView/Sample/ItemNode.h @@ -0,0 +1,18 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface ItemNode : ASTextCellNode + +- (instancetype)initWithString:(NSString *)string; + +@end diff --git a/examples/ASCollectionView/Sample/ItemNode.m b/examples/ASCollectionView/Sample/ItemNode.m new file mode 100644 index 0000000000..1a5f5ce40a --- /dev/null +++ b/examples/ASCollectionView/Sample/ItemNode.m @@ -0,0 +1,49 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "ItemNode.h" + +@implementation ItemNode + +- (instancetype)initWithString:(NSString *)string +{ + self = [super init]; + if (self != nil) { + self.text = string; + [self updateBackgroundColor]; + } + return self; +} + +- (void)updateBackgroundColor +{ + if (self.highlighted) { + self.backgroundColor = [UIColor grayColor]; + } else if (self.selected) { + self.backgroundColor = [UIColor darkGrayColor]; + } else { + self.backgroundColor = [UIColor lightGrayColor]; + } +} + +- (void)setSelected:(BOOL)selected +{ + [super setSelected:selected]; + [self updateBackgroundColor]; +} + +- (void)setHighlighted:(BOOL)highlighted +{ + [super setHighlighted:highlighted]; + [self updateBackgroundColor]; +} + +@end diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index 4f337ce1d1..a9d3e12a7b 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -13,6 +13,7 @@ #import #import "SupplementaryNode.h" +#import "ItemNode.h" @interface ViewController () { @@ -78,11 +79,7 @@ - (void)reloadTapped - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath { NSString *text = [NSString stringWithFormat:@"[%zd.%zd] says hi", indexPath.section, indexPath.item]; - ASTextCellNode *node = [[ASTextCellNode alloc] init]; - node.text = text; - node.backgroundColor = [UIColor lightGrayColor]; - - return node; + return [[ItemNode alloc] initWithString:text]; } - (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath From 2d93192b9f19420f7330f5910501407735f209d9 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 11 Nov 2015 01:04:31 -0800 Subject: [PATCH 02/12] Fix selection behavior of ASTableView cells --- AsyncDisplayKit/ASTableView.mm | 9 +++++++++ examples/Kittens/Sample/KittenNode.mm | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 7facfe0bc5..612d0332a9 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -135,13 +135,22 @@ - (void)didTransitionToState:(UITableViewCellStateMask)state [super didTransitionToState:state]; } +- (void)setNode:(ASCellNode *)node +{ + _node = node; + node.selected = self.selected; + node.highlighted = self.highlighted; +} + - (void)setSelected:(BOOL)selected animated:(BOOL)animated { + [super setSelected:selected animated:animated]; _node.selected = selected; } - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { + [super setHighlighted:highlighted]; _node.highlighted = highlighted; } diff --git a/examples/Kittens/Sample/KittenNode.mm b/examples/Kittens/Sample/KittenNode.mm index adc656aa22..2b3800601e 100644 --- a/examples/Kittens/Sample/KittenNode.mm +++ b/examples/Kittens/Sample/KittenNode.mm @@ -204,4 +204,27 @@ - (void)toggleNodesSwap }]; } +- (void)updateBackgroundColor +{ + if (self.highlighted) { + self.backgroundColor = [UIColor lightGrayColor]; + } else if (self.selected) { + self.backgroundColor = [UIColor blueColor]; + } else { + self.backgroundColor = [UIColor whiteColor]; + } +} + +- (void)setSelected:(BOOL)selected +{ + [super setSelected:selected]; + [self updateBackgroundColor]; +} + +- (void)setHighlighted:(BOOL)highlighted +{ + [super setHighlighted:highlighted]; + [self updateBackgroundColor]; +} + @end From 01876ae683c2c4aa902b203b61bf96cfed3b14f3 Mon Sep 17 00:00:00 2001 From: Levi McCallum Date: Wed, 11 Nov 2015 10:50:20 -0800 Subject: [PATCH 03/12] Fix bad rebase --- AsyncDisplayKit/ASTableView.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 612d0332a9..385df19ac4 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -150,7 +150,7 @@ - (void)setSelected:(BOOL)selected animated:(BOOL)animated - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { - [super setHighlighted:highlighted]; + [super setHighlighted:highlighted animated:animated]; _node.highlighted = highlighted; } From 97eaa15b04e2fcb6b57375cc5b3f1a3c58939c15 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Mon, 16 Nov 2015 11:36:10 -0800 Subject: [PATCH 04/12] Introduce ASInterfaceState on ASDisplayNode. Later, this will be added to ASViewController. Right now it is not hooked up to drive anything. It will become the codepath that drives calls like -fetchData and -clearContents. --- AsyncDisplayKit/ASDisplayNode.h | 22 +++++++++++++++++++ AsyncDisplayKit/ASDisplayNode.mm | 12 ++++++++++ .../Private/ASDisplayNodeInternal.h | 2 ++ 3 files changed, 36 insertions(+) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index cf9b7a54f3..62663267ec 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -32,6 +32,27 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); */ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node); +/** + Interface state is available on ASDisplayNode and ASViewController, and + allows checking whether a node is in an interface situation where it is prudent to trigger certain + actions: measurement, data fetching, display, and visibility (the latter for animations or other onscreen-only effects). + */ + +typedef NS_OPTIONS(NSUInteger, ASInterfaceState) +{ + /** The element is not predicted to be onscreen soon and preloading should not be performed */ + ASInterfaceStateNone = 1 << 0, + /** The element may be added to a view soon that could become visible. Measure the layout, including size calculation. */ + ASInterfaceStateMeasureLayout = 1 << 1, + /** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */ + ASInterfaceStateFetchData = 1 << 2, + /** The elemant is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ + ASInterfaceStateDisplay = 1 << 3, + /** The element is physically onscreen by at least 1 pixel. + In practice, all other bit fields should also be set when this flag is set. */ + ASInterfaceStateVisible = 1 << 4, +}; + /** * An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view * hierarchy off the main thread, and could do rendering off the main thread as well. @@ -160,6 +181,7 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node); */ @property (nonatomic, readonly, retain) CALayer *layer; +@property (nonatomic, readwrite) ASInterfaceState interfaceState; /** @name Managing dimensions */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 7287cfdee1..18a22dd560 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1683,6 +1683,18 @@ - (void)recursivelyClearFetchedData [self clearFetchedData]; } +- (ASInterfaceState)interfaceState +{ + return _interfaceState; +} + +- (void)setInterfaceState:(ASInterfaceState)interfaceState +{ + if (interfaceState != _interfaceState) { + _interfaceState = interfaceState; + } +} + - (void)layout { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index cf0681b72b..4953a5b538 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -72,6 +72,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { NSMutableSet *_pendingDisplayNodes; _ASPendingState *_pendingViewState; + + ASInterfaceState _interfaceState; struct ASDisplayNodeFlags { // public properties From 52c45cf5e389e406978c838be57b9d6216ddbeb1 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Tue, 17 Nov 2015 18:09:16 +0200 Subject: [PATCH 05/12] Trigger a relayout once the media image of PostNode in SocialAppLayout is loaded --- examples/SocialAppLayout/Sample/PostNode.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/SocialAppLayout/Sample/PostNode.m b/examples/SocialAppLayout/Sample/PostNode.m index 9992bb6ebb..b7f2a9c961 100644 --- a/examples/SocialAppLayout/Sample/PostNode.m +++ b/examples/SocialAppLayout/Sample/PostNode.m @@ -14,6 +14,9 @@ #import "LikesNode.h" #import "CommentsNode.h" +@interface PostNode() +@end + @implementation PostNode - (instancetype)initWithPost:(Post *)post { @@ -90,6 +93,7 @@ - (instancetype)initWithPost:(Post *)post { _mediaNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); _mediaNode.cornerRadius = 4.0; _mediaNode.URL = [NSURL URLWithString:_post.media]; + _mediaNode.delegate = self; _mediaNode.imageModificationBlock = ^UIImage *(UIImage *image) { UIImage *modifiedImage; @@ -258,4 +262,12 @@ - (void)textNode:(ASTextNode *)richTextNode tappedLinkAttribute:(NSString *)attr [[UIApplication sharedApplication] openURL:URL]; } +#pragma mark - +#pragma mark ASNetworkImageNodeDelegate methods. + +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image +{ + [self setNeedsLayout]; +} + @end From 0077c3eec63ed7e5cf668d543c751b8f117e0941 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Fri, 20 Nov 2015 17:50:05 +0200 Subject: [PATCH 06/12] Implement horizontal and vertical alignments for stack layout --- AsyncDisplayKit/Layout/ASStackLayoutDefines.h | 24 +++++ AsyncDisplayKit/Layout/ASStackLayoutSpec.h | 17 +++- AsyncDisplayKit/Layout/ASStackLayoutSpec.mm | 52 +++++++++- .../Private/ASStackLayoutSpecUtilities.h | 60 ++++++++++++ .../ASStackLayoutSpecSnapshotTests.mm | 90 +++++++++++++++++- ...calAlignments_horizontalBottomRight@2x.png | Bin 0 -> 4455 bytes ...VerticalAlignments_horizontalCenter@2x.png | Bin 0 -> 4812 bytes ...erticalAlignments_horizontalTopLeft@2x.png | Bin 0 -> 4728 bytes ...ticalAlignments_verticalBottomRight@2x.png | Bin 0 -> 4497 bytes ...ndVerticalAlignments_verticalCenter@2x.png | Bin 0 -> 5596 bytes ...dVerticalAlignments_verticalTopLeft@2x.png | Bin 0 -> 4289 bytes 11 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalBottomRight@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalCenter@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalTopLeft@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalBottomRight@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalCenter@2x.png create mode 100644 AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalTopLeft@2x.png diff --git a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h index 0ca7bb3c49..47d72b34d6 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutDefines.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutDefines.h @@ -67,3 +67,27 @@ typedef NS_ENUM(NSUInteger, ASStackLayoutAlignSelf) { /** Expand to fill cross axis */ ASStackLayoutAlignSelfStretch, }; + +/** Orientation of children along horizontal axis */ +typedef NS_ENUM(NSUInteger, ASHorizontalAlignment) { + /** No alignment specified. Default value */ + ASHorizontalAlignmentNone, + /** Left aligned */ + ASAlignmentLeft, + /** Center aligned */ + ASAlignmentMiddle, + /** Right aligned */ + ASAlignmentRight, +}; + +/** Orientation of children along vertical axis */ +typedef NS_ENUM(NSUInteger, ASVerticalAlignment) { + /** No alignment specified. Default value */ + ASVerticalAlignmentNone, + /** Top aligned */ + ASAlignmentTop, + /** Center aligned */ + ASAlignmentCenter, + /** Bottom aligned */ + ASAlignmentBottom, +}; diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h index ebf7130bfe..a2afc29609 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.h +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.h @@ -35,10 +35,25 @@ */ @interface ASStackLayoutSpec : ASLayoutSpec -/** Specifies the direction children are stacked in. */ +/** + Specifies the direction children are stacked in. If horizontalAlignment and verticalAlignment were set, + they will be resolved again, causing justifyContent and alignItems to be updated accordingly + */ @property (nonatomic, assign) ASStackLayoutDirection direction; /** The amount of space between each child. */ @property (nonatomic, assign) CGFloat spacing; +/** + Specifies how children are aligned horizontally. Depends on the stack direction, setting the alignment causes either + justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes. + Thus, it is preferred to those properties + */ +@property (nonatomic, assign) ASHorizontalAlignment horizontalAlignment; +/** + Specifies how children are aligned vertically. Depends on the stack direction, setting the alignment causes either + justifyContent or alignItems to be updated. The alignment will remain valid after future direction changes. + Thus, it is preferred to those properties + */ +@property (nonatomic, assign) ASVerticalAlignment verticalAlignment; /** The amount of space between each child. */ @property (nonatomic, assign) ASStackLayoutJustifyContent justifyContent; /** Orientation of children along cross axis */ diff --git a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm index 0763bf2615..ed07b7d6e0 100644 --- a/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm +++ b/AsyncDisplayKit/Layout/ASStackLayoutSpec.mm @@ -25,7 +25,7 @@ @implementation ASStackLayoutSpec { - ASDN::RecursiveMutex _propertyLock; + ASDN::RecursiveMutex _propertyLock; } - (instancetype)init @@ -58,8 +58,10 @@ - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGF return nil; } _direction = direction; - _alignItems = alignItems; _spacing = spacing; + _horizontalAlignment = ASHorizontalAlignmentNone; + _verticalAlignment = ASVerticalAlignmentNone; + _alignItems = alignItems; _justifyContent = justifyContent; [self setChildren:children]; @@ -69,18 +71,44 @@ - (instancetype)initWithDirection:(ASStackLayoutDirection)direction spacing:(CGF - (void)setDirection:(ASStackLayoutDirection)direction { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); - _direction = direction; + if (_direction != direction) { + _direction = direction; + [self resolveHorizontalAlignment]; + [self resolveVerticalAlignment]; + } +} + +- (void)setHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + if (_horizontalAlignment != horizontalAlignment) { + _horizontalAlignment = horizontalAlignment; + [self resolveHorizontalAlignment]; + } +} + +- (void)setVerticalAlignment:(ASVerticalAlignment)verticalAlignment +{ + ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + if (_verticalAlignment != verticalAlignment) { + _verticalAlignment = verticalAlignment; + [self resolveVerticalAlignment]; + } } - (void)setAlignItems:(ASStackLayoutAlignItems)alignItems { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used"); + ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used"); _alignItems = alignItems; } - (void)setJustifyContent:(ASStackLayoutJustifyContent)justifyContent { ASDisplayNodeAssert(self.isMutable, @"Cannot set properties when layout spec is not mutable"); + ASDisplayNodeAssert(_horizontalAlignment == ASHorizontalAlignmentNone, @"Cannot set this property directly because horizontalAlignment is being used"); + ASDisplayNodeAssert(_verticalAlignment == ASVerticalAlignmentNone, @"Cannot set this property directly because verticalAlignment is being used"); _justifyContent = justifyContent; } @@ -149,6 +177,24 @@ - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize sublayouts:sublayouts]; } +- (void)resolveHorizontalAlignment +{ + if (_direction == ASStackLayoutDirectionHorizontal) { + _justifyContent = justifyContent(_horizontalAlignment, _justifyContent); + } else { + _alignItems = alignment(_horizontalAlignment, _alignItems); + } +} + +- (void)resolveVerticalAlignment +{ + if (_direction == ASStackLayoutDirectionHorizontal) { + _alignItems = alignment(_verticalAlignment, _alignItems); + } else { + _justifyContent = justifyContent(_verticalAlignment, _justifyContent); + } +} + @end @implementation ASStackLayoutSpec (Debugging) diff --git a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h index a61218cfe4..ae018a1f87 100644 --- a/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h +++ b/AsyncDisplayKit/Private/ASStackLayoutSpecUtilities.h @@ -68,3 +68,63 @@ inline ASStackLayoutAlignItems alignment(ASStackLayoutAlignSelf childAlignment, return stackAlignment; } } + +inline ASStackLayoutAlignItems alignment(ASHorizontalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) +{ + switch (alignment) { + case ASAlignmentLeft: + return ASStackLayoutAlignItemsStart; + case ASAlignmentMiddle: + return ASStackLayoutAlignItemsCenter; + case ASAlignmentRight: + return ASStackLayoutAlignItemsEnd; + case ASHorizontalAlignmentNone: + default: + return defaultAlignment; + } +} + +inline ASStackLayoutAlignItems alignment(ASVerticalAlignment alignment, ASStackLayoutAlignItems defaultAlignment) +{ + switch (alignment) { + case ASAlignmentTop: + return ASStackLayoutAlignItemsStart; + case ASAlignmentCenter: + return ASStackLayoutAlignItemsCenter; + case ASAlignmentBottom: + return ASStackLayoutAlignItemsEnd; + case ASVerticalAlignmentNone: + default: + return defaultAlignment; + } +} + +inline ASStackLayoutJustifyContent justifyContent(ASHorizontalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) +{ + switch (alignment) { + case ASAlignmentLeft: + return ASStackLayoutJustifyContentStart; + case ASAlignmentMiddle: + return ASStackLayoutJustifyContentCenter; + case ASAlignmentRight: + return ASStackLayoutJustifyContentEnd; + case ASHorizontalAlignmentNone: + default: + return defaultJustifyContent; + } +} + +inline ASStackLayoutJustifyContent justifyContent(ASVerticalAlignment alignment, ASStackLayoutJustifyContent defaultJustifyContent) +{ + switch (alignment) { + case ASAlignmentTop: + return ASStackLayoutJustifyContentStart; + case ASAlignmentCenter: + return ASStackLayoutJustifyContentCenter; + case ASAlignmentBottom: + return ASStackLayoutJustifyContentEnd; + case ASVerticalAlignmentNone: + default: + return defaultJustifyContent; + } +} diff --git a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm index 2252078da6..5c3733e707 100644 --- a/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm +++ b/AsyncDisplayKitTests/ASStackLayoutSpecSnapshotTests.mm @@ -28,6 +28,8 @@ - (void)setUp self.recordMode = NO; } +#pragma mark - Utility methods + static NSArray *defaultSubnodes() { return defaultSubnodesWithSameSize(CGSizeZero, NO); @@ -63,6 +65,24 @@ - (void)testStackLayoutSpecWithJustify:(ASStackLayoutJustifyContent)justify [self testStackLayoutSpecWithStyle:style sizeRange:sizeRange subnodes:subnodes identifier:identifier]; } +- (void)testStackLayoutSpecWithDirection:(ASStackLayoutDirection)direction + itemsHorizontalAlignment:(ASHorizontalAlignment)horizontalAlignment + itemsVerticalAlignment:(ASVerticalAlignment)verticalAlignment + identifier:(NSString *)identifier +{ + NSArray *subnodes = defaultSubnodesWithSameSize({50, 50}, NO); + + ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; + stackLayoutSpec.direction = direction; + stackLayoutSpec.children = subnodes; + [stackLayoutSpec setHorizontalAlignment:horizontalAlignment]; + [stackLayoutSpec setVerticalAlignment:verticalAlignment]; + + CGSize exactSize = CGSizeMake(200, 200); + static ASSizeRange kSize = ASSizeRangeMake(exactSize, exactSize); + [self testStackLayoutSpec:stackLayoutSpec sizeRange:kSize subnodes:subnodes identifier:identifier]; +} + - (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style sizeRange:(ASSizeRange)sizeRange subnodes:(NSArray *)subnodes @@ -76,13 +96,23 @@ - (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style sizeRange:(ASSizeRange)sizeRange subnodes:(NSArray *)subnodes identifier:(NSString *)identifier +{ + ASStackLayoutSpec *stackLayoutSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:style.direction + spacing:style.spacing + justifyContent:style.justifyContent + alignItems:style.alignItems + children:children]; + [self testStackLayoutSpec:stackLayoutSpec sizeRange:sizeRange subnodes:subnodes identifier:identifier]; +} + +- (void)testStackLayoutSpec:(ASStackLayoutSpec *)stackLayoutSpec + sizeRange:(ASSizeRange)sizeRange + subnodes:(NSArray *)subnodes + identifier:(NSString *)identifier { ASDisplayNode *backgroundNode = ASDisplayNodeWithBackgroundColor([UIColor whiteColor]); - ASLayoutSpec *layoutSpec = - [ASBackgroundLayoutSpec - backgroundLayoutSpecWithChild:[ASStackLayoutSpec stackLayoutSpecWithDirection:style.direction spacing:style.spacing justifyContent:style.justifyContent alignItems:style.alignItems children:children] - background:backgroundNode]; + ASLayoutSpec *layoutSpec = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:stackLayoutSpec background:backgroundNode]; NSMutableArray *newSubnodes = [NSMutableArray arrayWithObject:backgroundNode]; [newSubnodes addObjectsFromArray:subnodes]; @@ -90,6 +120,8 @@ - (void)testStackLayoutSpecWithStyle:(ASStackLayoutSpecStyle)style [self testLayoutSpec:layoutSpec sizeRange:sizeRange subnodes:newSubnodes identifier:identifier]; } +#pragma mark - + - (void)testUnderflowBehaviors { // width 300px; height 0-300px @@ -522,4 +554,54 @@ - (void)testViolationIsDistributedEquallyAmongFlexibleChildren [self testStackLayoutSpecWithStyle:style sizeRange:kSize subnodes:subnodes identifier:nil]; } +- (void)testHorizontalAndVerticalAlignments +{ + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentLeft itemsVerticalAlignment:ASAlignmentTop identifier:@"horizontalTopLeft"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentMiddle itemsVerticalAlignment:ASAlignmentCenter identifier:@"horizontalCenter"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal itemsHorizontalAlignment:ASAlignmentRight itemsVerticalAlignment:ASAlignmentBottom identifier:@"horizontalBottomRight"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentLeft itemsVerticalAlignment:ASAlignmentTop identifier:@"verticalTopLeft"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentMiddle itemsVerticalAlignment:ASAlignmentCenter identifier:@"verticalCenter"]; + [self testStackLayoutSpecWithDirection:ASStackLayoutDirectionVertical itemsHorizontalAlignment:ASAlignmentRight itemsVerticalAlignment:ASAlignmentBottom identifier:@"verticalBottomRight"]; +} + +- (void)testDirectionChangeAfterSettingHorizontalAndVerticalAlignments +{ + ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; // Default direction is horizontal + stackLayoutSpec.horizontalAlignment = ASAlignmentRight; + stackLayoutSpec.verticalAlignment = ASAlignmentCenter; + XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsCenter); + XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentEnd); + + stackLayoutSpec.direction = ASStackLayoutDirectionVertical; + XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsEnd); + XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentCenter); +} + +- (void)testAlignItemsAndJustifyContentRestrictionsIfHorizontalAndVerticalAlignmentsAreUsed +{ + ASStackLayoutSpec *stackLayoutSpec = [[ASStackLayoutSpec alloc] init]; + + // No assertions should be thrown here because alignments are not used + stackLayoutSpec.alignItems = ASStackLayoutAlignItemsEnd; + stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd; + + // Set alignments and assert that assertions are thrown + stackLayoutSpec.horizontalAlignment = ASAlignmentMiddle; + stackLayoutSpec.verticalAlignment = ASAlignmentCenter; + XCTAssertThrows(stackLayoutSpec.alignItems = ASStackLayoutAlignItemsEnd); + XCTAssertThrows(stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd); + + // Unset alignments. alignItems and justifyContent should not be changed + stackLayoutSpec.horizontalAlignment = ASHorizontalAlignmentNone; + stackLayoutSpec.verticalAlignment = ASVerticalAlignmentNone; + XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsCenter); + XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentCenter); + + // Now that alignments are none, setting alignItems and justifyContent should be allowed again + stackLayoutSpec.alignItems = ASStackLayoutAlignItemsEnd; + stackLayoutSpec.justifyContent = ASStackLayoutJustifyContentEnd; + XCTAssertEqual(stackLayoutSpec.alignItems, ASStackLayoutAlignItemsEnd); + XCTAssertEqual(stackLayoutSpec.justifyContent, ASStackLayoutJustifyContentEnd); +} + @end diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalBottomRight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalBottomRight@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..489764fe6e29e2f920e2cae42680346d205addee GIT binary patch literal 4455 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgH{PkH}&M z1~F9-X7mhF`2ZA@$#n4#0n$uB3<4*BSOZD}+3a4HPqU@~X)#Y1$B+ufH&-|EGB9u) zHuzV+>(7LnZ(?v2qV&M?rU=lY|1JVi(2~3YTL9_xVhewjJl0yRnBjZPAkkkYg z7L|>Xf(i}|4GllQx;&VeCf(=(sdjMq!vGReVPy2oaRaGVP^bfHWn!7s(4b1FYK{(hplJ+^DhdjnXFEWuS&r}nO>1xv5}0r#%>|^I=@I*=b4EjF zH0_M$tkH63v{a^ICGmiff#LuEnNOES0BhGnpc?Y+b;qou1`N)Q_y1oy_x}l7l+<^z)FES$g<+d9UN9H5#hLC8Qsc(yaB&P#CE1FF^pX0Y&_IPy#Y z#Fh}q2dh?NWH#Dp39ixDj!1)RJp+ zBzG!{y05dVZC&wZTEj6f(`gn#g%T6yIIOn2>z2G!%3EfoMbX!n+%BoxSxmxKZ9Fou z>u|#pmZ+~U&0uD)_K#Q4+O4`^V^FVZUx*Bwiu#3B{@X7?Z0d`t{lye`QClP{g$MSBW&#rW03}>kCZ1i^t22qh213<n8@slU@r>M=k~psW5oD`njxgN@xNA3q(B! literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalCenter@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalCenter@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..eb0ea8c3dae6ddb3f4683a4cfce5efc6b629f3e8 GIT binary patch literal 4812 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgH{PkH}&M z1~F9-X7mhF`2ZA@$#n4#0n$uB3<4*BSOZD}*&Mh0ik`{?>1CcSjv*C{Z*DlQQgIhy zIk@Zp($&&ccct4KR17)S9?@2rJ*S}7cD+4^fPzDVgFwZhUSTE{Am;;Dhr2@q10$2C zgM-j)WssPFKt4!-Wl}?fikUk|k%GcKkN}4YBco?dAIJy?hdm$x0S_joNjGE!fo3)| zR4{|oPGDhC*=VT*GL!Km8%V7JCx=H;u?xsdrboPB`-KE19C_9OGLz+qG*B}`ql$t; z=UG9JnH(MFqYiQy4WZFAG@8o>dpT6#?=bKE{;vIfh9A5O&*&e0zy0X%AD3bkac56hPW#6x-AM7{q30vgay|(Y4@1S!x<>dyyh}Zlr?Pr(IcX*XBzf2uw z1yhkHmp`}T-{*n?-sX{3YlOoX1t;wKI@y|WW69*DhpiGt1sG?xET{{CnZoMg@amfX zJC-Ate5difZV7c@NfB!L6^NvKWwiZHVI^74o7;%1139 z4R$OE&JNgc`Tu{K@sV}F*3NWLi{&ePCjt+10ouJ}?NlAgpNU#9rkiPyoci;b+Kj|dzA8!4x!Z1~JOg}xi{vTR9|Iaig zn9&U!CsbIjPv?TE0h)9@+P>1yA!gS}cxrp_D+t-*s@Ll0IgvC>7Kn-0Qqa5mf@;I%jFk)^X^pry#`wexM%ku>-?71XY5*ikasyS?;~mI71J-mjCN zvmj~kpsmv(qP1S@as}!KYhLCKTjA*gPP)weIZDG+IjES z$zo&;qk%xHl(6$XOUj|gOJS|}lQV=teFag038#`hK{YY3i!iCj64XWyVPsrsGi_Ai pXz+|CiqWi~GFnm$oAU7oKhJTm1tGl!t-!V#gQu&X%Q~loCIDXX&oux5 literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalTopLeft@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_horizontalTopLeft@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..886440276800ad57d42d8f68558079a4e263acd8 GIT binary patch literal 4728 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgH{PkH}&M z1~F9-X7mhF`2ZA@$#n4#0n$uB3<4*BSOZD}+1#8}t)`n87`U!_x;TbZFuu9rSfm^- za3tW~|4UO!8lt!*|MwnVeQ!bFW0u?V-#`ESHUD^H@sWR4b@u-_C4c;yZ1mxGynDLM zMho|M>ytBVmcA)p{l`;sL&V0TT@N#MH7wy-a!vTm{rkM}3aaeq9@Z!RytIS&#+zx3 zjGek=Jj<_K`=99f_uszuuT$06{&Z1R*m$(>;h*yv)9t!gRZs z`S5+l@4n}pRhI&5b$4JvSMYoKPi_CG&z)`#N!_xC1qB#AB@J%sGBXK9zme!`Sn}-i z)b~BVZKLrh_?PD8(XxRDO9?of%Z3k`4;LghFtSNR7%C`ePd{{$ftlwBuzGl-P#}@e&?w5xw5dpQ z4v)bBhcmzen~$k|ROx64jE2BS4uJ#wz;^Ec|8F%@gcumuML-SQw^ujvGB9u)HuzV+ z>(7LnZ(?v2qV&M?rU=lY|1JVi(2~3YTL9_xVhewjJl0yRnBjZPAkkkYg7L|>X zf(i}|4GllQx;&VeCf(=(sdjMq!vGReVPy2oaRaGVP^bfHWn!7s(4b1FYK{(hplJ+^DhdjnXFEWuS&r}nO>1xv5}0r#%>|^I=@I*=b4EjFH0_M$ ktkH63v{a^ICGmjq`ZlixpM5ia11m}fPgg&ebxsLQ0QI-*zW@LL literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalBottomRight@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalBottomRight@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4e46d0f9dd951aae44acb3a06104cc1f9c548157 GIT binary patch literal 4497 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgH{PkH}&M z1~F9-X7mhF`2ZA@$#n4#0n$uB3<4*BSOZD}*&G%OWoh0(n#I$_F{Fa=&DDdP%!VS) zj^FQZxqiFJPsm5X{cw7~r*mHw9U2%InHmpp-m7o|u~^nMNH78^4i_dSp^cR;ATg#S zW)=k^m5ouZE4ac1C)1O8sD=1jo%T5?hI_`_LGp>7fj#~WbgYk0Hf>QWYt5KbU7 zg!hu+sOV@ojHZXte1TaKc!WCG0h<>@!| zpEzE5u;F9imX5I1A3pyA*6RLdCfkqV|%R@@W6w-2DKQ3 zfGic>J|$2iLV)p+D9}0uP9SR%n>(mw!P3F-kqcj1 zTr$-H=Ai?74_y0n3wrfs4)<+*BM7FN+q*Cc3DukJ%w##;9rcc*e zs}Jo|tdZKsO~%-grU$ulE6P`muWlHTk1@mC_hhT*{&)Yme_iuD=lPv^fA9C#?>)co z12*~SGZryuG@8DjuU8NWh)jq?plpwZ0isR!{3 zv>OJX)m|@7Fsvv%)K3P_(%)BjXY>X+s20Im=I6C8I0+ee(iCGE%Jf>N(QwwD@u_nr z44&*%bTEWEmd}mKTOKOMUa&Db6DJ}T4@bYv@}XYI>R4nYiUN46E?st_!IMJ3aaV$* zA*cg_gxyHagf|SUnfIR39>*PA4UK@1BwMHrdNGFWyx+T+V!>ou!Uiq<97z_peC0*4 zKzC?S97~OvLesnqiUmRJr#7$<6fN5^%A{D(GxguA;!hAE`>ky$*~krQV@|36##8B8 z)#BXq{FM35on=KCdUV-KtxQQ((z_bDMsb&oq3C&2;!r$>71mWR)x|K(j{H|Qo$i~H zzS)=#EA7ccBc^bL)@3^gppbU`%x5_Mr+MG_K^ovlFw2J^D&}XMhPV;v)lJzXdC+2^ zeL9363&^)Zkk`&%_@fAd=wA5a8WerJE8;8&M2+VJ&bf3hY39n}|F6@OXcG73dK=52 zv(}DH7R`v-O`GI8y0|Tl8-@62dlBl;I6i{h)+DM=xh-HKO4+dbaXEu7+jhHAxeK+$ z`--RA+)RZDQdNJRUMA_K)0=dhB!g4+Csa(>p4j%Q+thL#=X$G^J{aaj@OJuU2kX&& z$DSp$8?taTzI|+}TL26%2l9rNA&8MsG*6!4v<$~{)X%5bw@A9L#y{>C%1u$A|BL%nS`HuI7!N%!oUx;sz2}_jJ*y zWv3&KE6mv46S|qCmGjtTZV-_@GnL@vgrldo<Iwe5c#-e-v*%6a#> zk`V7#2A_e06obW9KQ8LwVjvoU?jC?

wT8Ccwub3i1p1#;2XDLD%8QHnWV zb%`YDvcH;)Olku?%6)9gz61c%#5XQ%7LGS2D_`qqLnN$!JYjRElq9!L);GPofm@$Rx`DqfHZ~`k# z%y)0)z$nP^s~F9oFl>$uXKaIPa+E%8l%54ZUueHcp*CjcnA)EPQawfde@5%cHghLb z;UN&owCK@1cNh~3PD_;vQ;?~wk#xX_XYonrE;bCY8qKv}eI`t=dDQee*s?jsD%4ne z^B}*uOdaKEOdQP`dXevQ4Ebu&ny^FR6=)-yH|32pC*R8ZIGXmA^z0U2l+B-x95PQ= zm@@_WN$M0cp3zdQIOO8KpIo;k=BFNw$!^ZD9EyptqrKyL&s(5LA2c+Z4grqwn%9D4ljC)AIEwOey`C7{P zRgr@F7I56)XmYE_1Tb2A#=2|(U>Kx4R$v4)%KECwn_?(CadONk2Qt!GT4+67hoU39 zJhW!)s45UihEo*aE5)IoTw*^1jGI~3b|y?=*7$e?DDgsrwI!t>w+}or~1j)8L`<2An3nC$Lef~o22l-I=YNG0M zN?wn>b3`~n^a}b{*#rQ_Dz|4rd?0bZ+|$V0N&w_u-7CYYVv?*JX!fV6>l#sT2Z6?UExW0tv#u{oYMb!a%Ia4K)|K_)NE$L-dme zYL$B%fj;W0&15d5w9Zqt+JPZcn(m>27k_AQJ_H(`-y}czlQ)Pz`Lwl2?F}lU@^a}C zO66bmM-rt+LEPPCk}emjj=P>8>E=K}vVK#)73e~DN!=f$z7`6u4|SKkj522;7U3Mh z_(pK_Mq#a*OPQsye94zw1}^ez29;+(>FPyCz5|1IbQQ@#>5`$DD-&kI^`hzOL`vzd z3gu=Fe6p~9ld1$1VB!xGJn3O8G~ENZ6}C9Il*3ogo3A>0_ks}}yK^NnE>xBH%Nc(K?B1Id2tB%Lrc1Asz*;;@E6&-~gK zlMTy*8xE=#nv|Z#7znZ~xWP)5T5)`f@d z{zjw7GV=rTpUEuUk5aSK+}nwd%(ZuJsQw@|VPN$2yd58yFT*3zk5VHGIBv`j%r_tw z|7lYCN9J=T%|&T0@!)ivtEEpJ?^Am)#)&StjTC@F4mx;&0HHd*#S*RUB z{@&)`mTEPOr8)+!4nQ>9)HcA~l@+u%t&9OvAewEQOW;P!fKHdCdyby~qS@Kx3|#Qc zP9`bdKs4Je{Xn7?H0ds(#1@EV^#h63aL=+PP8NO&h-Mq}oSOejZ+6+tf8c*X%cjF0 Q=2e=X_a?6rkBFoH2A45}E&u=k literal 0 HcmV?d00001 diff --git a/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalTopLeft@2x.png b/AsyncDisplayKitTests/ReferenceImages_64/ASStackLayoutSpecSnapshotTests/testHorizontalAndVerticalAlignments_verticalTopLeft@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8277b97c003accf06b6cb5768b24c83f1d4f11a5 GIT binary patch literal 4289 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKL9Be?5hW%z|fD~hKkh>GZx^prwfgH{PkH}&M z1~F9-X7mhF`2ZA@$#n4#0n$uB3<4*BSOZD}+3fG5fV8KB1B;4opCS_r2S-Q44kjS2!pJB%VT(l@hk$~D0@EWdAU%PF z#l_*$BMG3X4GkO}+6oR04nhI~jGo=e9ixVghS_M+ph7hhsTqEG$93t1YZY-YkEoiz>-9X ziAl(E%1oeigs{Mbt=%`k Date: Sat, 28 Nov 2015 17:45:43 -0800 Subject: [PATCH 07/12] Placeholder implementation of -setInterfaceState:. --- AsyncDisplayKit/ASDisplayNode.h | 2 +- AsyncDisplayKit/ASDisplayNode.mm | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 62663267ec..51adccbc1c 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -46,7 +46,7 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) ASInterfaceStateMeasureLayout = 1 << 1, /** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */ ASInterfaceStateFetchData = 1 << 2, - /** The elemant is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ + /** The element is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ ASInterfaceStateDisplay = 1 << 3, /** The element is physically onscreen by at least 1 pixel. In practice, all other bit fields should also be set when this flag is set. */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 18a22dd560..744260b6b8 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1691,6 +1691,39 @@ - (ASInterfaceState)interfaceState - (void)setInterfaceState:(ASInterfaceState)interfaceState { if (interfaceState != _interfaceState) { + if ((interfaceState & ASInterfaceStateMeasureLayout) != (_interfaceState & ASInterfaceStateMeasureLayout)) { + // Trigger asynchronous measurement if it is not already cached or being calculated. + } + + // Entered or exited data loading state. + if ((interfaceState & ASInterfaceStateFetchData) != (_interfaceState & ASInterfaceStateFetchData)) { + if (interfaceState & ASInterfaceStateFetchData) { + [self fetchData]; + } else { + [self clearFetchedData]; + } + } + + // Entered or exited contents rendering state. + if ((interfaceState & ASInterfaceStateDisplay) != (_interfaceState & ASInterfaceStateDisplay)) { + if (interfaceState & ASInterfaceStateDisplay) { + // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. + [self setDisplaySuspended:NO]; + } else { + [self setDisplaySuspended:YES]; + [self clearContents]; + } + } + + // Entered or exited data loading state. + if ((interfaceState & ASInterfaceStateVisible) != (_interfaceState & ASInterfaceStateVisible)) { + if (interfaceState & ASInterfaceStateVisible) { + // Consider providing a -didBecomeVisible. + } else { + // Consider providing a -didBecomeInvisible. + } + } + _interfaceState = interfaceState; } } From ff0e94b4e16fc0c62f7b3a4a2e32986dade7dfe1 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Mon, 16 Nov 2015 11:36:10 -0800 Subject: [PATCH 08/12] Introduce ASInterfaceState on ASDisplayNode. Later, this will be added to ASViewController. Right now it is not hooked up to drive anything. It will become the codepath that drives calls like -fetchData and -clearContents. --- AsyncDisplayKit/ASDisplayNode.h | 22 +++++++++++++++++++ AsyncDisplayKit/ASDisplayNode.mm | 12 ++++++++++ .../Private/ASDisplayNodeInternal.h | 2 ++ 3 files changed, 36 insertions(+) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index cf9b7a54f3..62663267ec 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -32,6 +32,27 @@ typedef CALayer *(^ASDisplayNodeLayerBlock)(); */ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node); +/** + Interface state is available on ASDisplayNode and ASViewController, and + allows checking whether a node is in an interface situation where it is prudent to trigger certain + actions: measurement, data fetching, display, and visibility (the latter for animations or other onscreen-only effects). + */ + +typedef NS_OPTIONS(NSUInteger, ASInterfaceState) +{ + /** The element is not predicted to be onscreen soon and preloading should not be performed */ + ASInterfaceStateNone = 1 << 0, + /** The element may be added to a view soon that could become visible. Measure the layout, including size calculation. */ + ASInterfaceStateMeasureLayout = 1 << 1, + /** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */ + ASInterfaceStateFetchData = 1 << 2, + /** The elemant is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ + ASInterfaceStateDisplay = 1 << 3, + /** The element is physically onscreen by at least 1 pixel. + In practice, all other bit fields should also be set when this flag is set. */ + ASInterfaceStateVisible = 1 << 4, +}; + /** * An `ASDisplayNode` is an abstraction over `UIView` and `CALayer` that allows you to perform calculations about a view * hierarchy off the main thread, and could do rendering off the main thread as well. @@ -160,6 +181,7 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node); */ @property (nonatomic, readonly, retain) CALayer *layer; +@property (nonatomic, readwrite) ASInterfaceState interfaceState; /** @name Managing dimensions */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index e7c6e54c0b..394c050128 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1684,6 +1684,18 @@ - (void)recursivelyClearFetchedData [self clearFetchedData]; } +- (ASInterfaceState)interfaceState +{ + return _interfaceState; +} + +- (void)setInterfaceState:(ASInterfaceState)interfaceState +{ + if (interfaceState != _interfaceState) { + _interfaceState = interfaceState; + } +} + - (void)layout { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index cf0681b72b..4953a5b538 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -72,6 +72,8 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { NSMutableSet *_pendingDisplayNodes; _ASPendingState *_pendingViewState; + + ASInterfaceState _interfaceState; struct ASDisplayNodeFlags { // public properties From 943cae7eb9b79d0c89b274f0ed13201a9e1bd3d8 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 28 Nov 2015 17:45:43 -0800 Subject: [PATCH 09/12] Placeholder implementation of -setInterfaceState:. --- AsyncDisplayKit/ASDisplayNode.h | 2 +- AsyncDisplayKit/ASDisplayNode.mm | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 62663267ec..51adccbc1c 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -46,7 +46,7 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) ASInterfaceStateMeasureLayout = 1 << 1, /** The element is likely enough to come onscreen that disk and/or network data required for display should be fetched. */ ASInterfaceStateFetchData = 1 << 2, - /** The elemant is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ + /** The element is very likely to become visible, and concurrent rendering should be executed for any -setNeedsDisplay. */ ASInterfaceStateDisplay = 1 << 3, /** The element is physically onscreen by at least 1 pixel. In practice, all other bit fields should also be set when this flag is set. */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 394c050128..4095552b77 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1692,6 +1692,39 @@ - (ASInterfaceState)interfaceState - (void)setInterfaceState:(ASInterfaceState)interfaceState { if (interfaceState != _interfaceState) { + if ((interfaceState & ASInterfaceStateMeasureLayout) != (_interfaceState & ASInterfaceStateMeasureLayout)) { + // Trigger asynchronous measurement if it is not already cached or being calculated. + } + + // Entered or exited data loading state. + if ((interfaceState & ASInterfaceStateFetchData) != (_interfaceState & ASInterfaceStateFetchData)) { + if (interfaceState & ASInterfaceStateFetchData) { + [self fetchData]; + } else { + [self clearFetchedData]; + } + } + + // Entered or exited contents rendering state. + if ((interfaceState & ASInterfaceStateDisplay) != (_interfaceState & ASInterfaceStateDisplay)) { + if (interfaceState & ASInterfaceStateDisplay) { + // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. + [self setDisplaySuspended:NO]; + } else { + [self setDisplaySuspended:YES]; + [self clearContents]; + } + } + + // Entered or exited data loading state. + if ((interfaceState & ASInterfaceStateVisible) != (_interfaceState & ASInterfaceStateVisible)) { + if (interfaceState & ASInterfaceStateVisible) { + // Consider providing a -didBecomeVisible. + } else { + // Consider providing a -didBecomeInvisible. + } + } + _interfaceState = interfaceState; } } From 1ed22866b1df811e939a68144842660b6d6f1ffd Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sat, 28 Nov 2015 22:48:30 -0800 Subject: [PATCH 10/12] Working implementation of ASInterfaceState for Display and FetchData behaviors. --- AsyncDisplayKit/ASDisplayNode.h | 5 ++- AsyncDisplayKit/ASDisplayNode.mm | 40 +++++++++++++++++++ .../Details/ASRangeHandlerPreload.mm | 4 +- .../Details/ASRangeHandlerRender.mm | 7 ++-- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 51adccbc1c..45d23e07b8 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -181,7 +181,10 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) */ @property (nonatomic, readonly, retain) CALayer *layer; -@property (nonatomic, readwrite) ASInterfaceState interfaceState; +@property (nonatomic, readonly) ASInterfaceState interfaceState; + +- (void)enterInterfaceState:(ASInterfaceState)interfaceState; +- (void)exitInterfaceState:(ASInterfaceState)interfaceState; /** @name Managing dimensions */ diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 4095552b77..8c639de1ba 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1729,6 +1729,46 @@ - (void)setInterfaceState:(ASInterfaceState)interfaceState } } +- (void)enterInterfaceState:(ASInterfaceState)interfaceState +{ + recursivelyPerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.interfaceState |= interfaceState; + }); +} + +- (void)exitInterfaceState:(ASInterfaceState)interfaceState +{ + recursivelyPerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.interfaceState &= (!interfaceState); + }); +} + +void recursivelyPerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^performOnNode)(ASDisplayNode *node)) +{ + if (!node) { + ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer"); + ASDisplayNodeCAssertMainThread(); + node = [layer asyncdisplaykit_node]; + } + + if (node) { + performOnNode(node); + } + if (!layer && [node isNodeLoaded]) { + layer = node.layer; + } + + if (layer) { + for (CALayer *sublayer in [layer sublayers]) { + recursivelyPerformBlockOnEveryNode(sublayer, nil, performOnNode); + } + } else if (node) { + for (ASDisplayNode *subnode in [node subnodes]) { + recursivelyPerformBlockOnEveryNode(nil, subnode, performOnNode); + } + } +} + - (void)layout { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm index d30f6c1a40..fc61788fb0 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm @@ -16,13 +16,13 @@ @implementation ASRangeHandlerPreload - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges"); - [node recursivelyFetchData]; + [node enterInterfaceState:ASInterfaceStateFetchData]; } - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssert(rangeType == ASLayoutRangeTypePreload, @"Preload delegate should not handle other ranges"); - [node recursivelyClearFetchedData]; + [node exitInterfaceState:ASInterfaceStateFetchData]; } @end diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index e564564956..b2a751a8bd 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -57,7 +57,8 @@ - (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeTy [node.view removeFromSuperview]; } - [node recursivelySetDisplaySuspended:NO]; + // The node un-suspends display. + [node enterInterfaceState:ASInterfaceStateDisplay]; // Add the node's layer to an off-screen window to trigger display and mark its contents as non-volatile. // Use the layer directly to avoid the substantial overhead of UIView heirarchy manipulations. @@ -89,7 +90,8 @@ - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeTyp // preservation of this content could result in the app being killed, which is not likely preferable over briefly seeing placeholders in the event the user scrolls backwards. // Nonetheless, future changes to the implementation will likely eliminate this behavior to simplify debugging and extensibility of working range functionality. - [node recursivelySetDisplaySuspended:YES]; + // The node calls clearCurrentContents and suspends display + [node exitInterfaceState:ASInterfaceStateDisplay]; if (node.layer.superlayer != [[self workingWindow] layer]) { // In this case, the node has previously passed through the working range (or it is zero), and it has now fallen outside the working range. @@ -102,7 +104,6 @@ - (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeTyp // At this point, the node's layer may validly be present either in the workingWindow, or in the contentsView of a cell. [node.layer removeFromSuperlayer]; - [node recursivelyClearContents]; } @end From 936730d46b511cca3066d513d69e5a023c5628f8 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 29 Nov 2015 13:24:42 -0800 Subject: [PATCH 11/12] ASInterfaceStateVisible is now being driven by a new ASRangeHandlerVisible. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 28 +++++------ AsyncDisplayKit/ASDisplayNode.h | 12 +++-- AsyncDisplayKit/ASDisplayNode.mm | 46 +++++-------------- AsyncDisplayKit/ASDisplayNodeExtras.h | 8 ++++ AsyncDisplayKit/ASDisplayNodeExtras.mm | 30 +++++++++++- .../Details/ASAbstractLayoutController.mm | 14 ++++-- AsyncDisplayKit/Details/ASLayoutRangeType.h | 1 + AsyncDisplayKit/Details/ASRangeController.mm | 2 + .../Details/ASRangeHandlerPreload.mm | 3 +- .../Details/ASRangeHandlerVisible.h | 15 ++++++ .../Details/ASRangeHandlerVisible.mm | 25 ++++++++++ .../Private/ASDisplayNodeInternal.h | 4 ++ 12 files changed, 124 insertions(+), 64 deletions(-) create mode 100644 AsyncDisplayKit/Details/ASRangeHandlerVisible.h create mode 100644 AsyncDisplayKit/Details/ASRangeHandlerVisible.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 5781496c5c..4995f00798 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -407,6 +407,10 @@ D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DE6D9E321C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */; }; + DE6D9E331C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */; }; + DE6D9E341C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */; }; + DE6D9E351C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; @@ -675,6 +679,8 @@ 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 = ""; }; + DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerVisible.h; sourceTree = ""; }; + DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerVisible.mm; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -938,6 +944,8 @@ 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */, 292C599C1A956527007E5DD6 /* ASRangeHandler.h */, + DE6D9E301C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h */, + DE6D9E311C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm */, 292C599A1A956527007E5DD6 /* ASRangeHandlerPreload.h */, 292C599B1A956527007E5DD6 /* ASRangeHandlerPreload.mm */, 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */, @@ -1159,6 +1167,7 @@ 05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */, 430E7C8F1B4C23F100697A4C /* ASIndexPath.h in Headers */, ACF6ED221B17843500DA7C62 /* ASInsetLayoutSpec.h in Headers */, + DE6D9E321C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */, ACF6ED4B1B17847A00DA7C62 /* ASInternalHelpers.h in Headers */, ACF6ED241B17843500DA7C62 /* ASLayout.h in Headers */, 251B8EFB1BBB3D690087C538 /* ASDataController+Subclasses.h in Headers */, @@ -1254,6 +1263,7 @@ B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */, 34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */, + DE6D9E331C0AD9ED001A1DD3 /* ASRangeHandlerVisible.h in Headers */, B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */, @@ -1373,7 +1383,6 @@ 058D09B9195D04C000B7D73C /* Frameworks */, 058D09BA195D04C000B7D73C /* Resources */, 3B9D88CDF51B429C8409E4B6 /* Copy Pods Resources */, - 6CC5F540055A48FCA8C12BF5 /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -1503,21 +1512,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 6CC5F540055A48FCA8C12BF5 /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1599,6 +1593,7 @@ 055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */, 058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */, 058D0A1C195D050800B7D73C /* ASTextNodeCoreTextAdditions.m in Sources */, + DE6D9E341C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */, 058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */, 058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */, 058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */, @@ -1717,6 +1712,7 @@ 34EFC7741B701D0A00AD841F /* ASStaticLayoutSpec.mm in Sources */, B350620B1B010EFD0018CF92 /* ASTableView.mm in Sources */, B350620E1B010EFD0018CF92 /* ASTextNode.mm in Sources */, + DE6D9E351C0AD9ED001A1DD3 /* ASRangeHandlerVisible.mm in Sources */, B350622F1B010EFD0018CF92 /* ASTextNodeCoreTextAdditions.m in Sources */, B35062311B010EFD0018CF92 /* ASTextNodeRenderer.mm in Sources */, B35062331B010EFD0018CF92 /* ASTextNodeShadower.m in Sources */, diff --git a/AsyncDisplayKit/ASDisplayNode.h b/AsyncDisplayKit/ASDisplayNode.h index 45d23e07b8..80ee51aa6f 100644 --- a/AsyncDisplayKit/ASDisplayNode.h +++ b/AsyncDisplayKit/ASDisplayNode.h @@ -144,7 +144,6 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) /** @name Getting view and layer */ - /** * @abstract Returns a view. * @@ -181,14 +180,17 @@ typedef NS_OPTIONS(NSUInteger, ASInterfaceState) */ @property (nonatomic, readonly, retain) CALayer *layer; +/** + * @abstract Returns the Interface State of the node. + * + * @return The current ASInterfaceState of the node, indicating whether it is visible and other situational properties. + * + * @see ASInterfaceState + */ @property (nonatomic, readonly) ASInterfaceState interfaceState; -- (void)enterInterfaceState:(ASInterfaceState)interfaceState; -- (void)exitInterfaceState:(ASInterfaceState)interfaceState; - /** @name Managing dimensions */ - /** * @abstract Asks the node to measure and return the size that best fits its subnodes. * diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 8c639de1ba..298e683954 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -1650,6 +1650,7 @@ - (void)clearContents _placeholderImage = nil; } +// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or exitInterfaceState: - (void)recursivelyClearContents { for (ASDisplayNode *subnode in self.subnodes) { @@ -1663,6 +1664,7 @@ - (void)fetchData // subclass override } +// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or enterInterfaceState: - (void)recursivelyFetchData { for (ASDisplayNode *subnode in self.subnodes) { @@ -1676,6 +1678,7 @@ - (void)clearFetchedData // subclass override } +// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or exitInterfaceState: - (void)recursivelyClearFetchedData { for (ASDisplayNode *subnode in self.subnodes) { @@ -1731,44 +1734,18 @@ - (void)setInterfaceState:(ASInterfaceState)interfaceState - (void)enterInterfaceState:(ASInterfaceState)interfaceState { - recursivelyPerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { node.interfaceState |= interfaceState; }); } - (void)exitInterfaceState:(ASInterfaceState)interfaceState { - recursivelyPerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { - node.interfaceState &= (!interfaceState); + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.interfaceState &= (~interfaceState); }); } -void recursivelyPerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^performOnNode)(ASDisplayNode *node)) -{ - if (!node) { - ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer"); - ASDisplayNodeCAssertMainThread(); - node = [layer asyncdisplaykit_node]; - } - - if (node) { - performOnNode(node); - } - if (!layer && [node isNodeLoaded]) { - layer = node.layer; - } - - if (layer) { - for (CALayer *sublayer in [layer sublayers]) { - recursivelyPerformBlockOnEveryNode(sublayer, nil, performOnNode); - } - } else if (node) { - for (ASDisplayNode *subnode in [node subnodes]) { - recursivelyPerformBlockOnEveryNode(nil, subnode, performOnNode); - } - } -} - - (void)layout { ASDisplayNodeAssertMainThread(); @@ -1825,6 +1802,7 @@ - (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode - (void)setNeedsDisplayAtScale:(CGFloat)contentsScale { + ASDN::MutexLocker l(_propertyLock); if (contentsScale != self.contentsScaleForDisplay) { self.contentsScaleForDisplay = contentsScale; [self setNeedsDisplay]; @@ -1833,12 +1811,9 @@ - (void)setNeedsDisplayAtScale:(CGFloat)contentsScale - (void)recursivelySetNeedsDisplayAtScale:(CGFloat)contentsScale { - [self setNeedsDisplayAtScale:contentsScale]; - - ASDN::MutexLocker l(_propertyLock); - for (ASDisplayNode *child in _subnodes) { - [child recursivelySetNeedsDisplayAtScale:contentsScale]; - } + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + [node setNeedsDisplayAtScale:contentsScale]; + }); } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event @@ -1974,6 +1949,7 @@ - (void)recursivelySetDisplaySuspended:(BOOL)flag _recursivelySetDisplaySuspended(self, nil, flag); } +// TODO: Replace this with ASDisplayNodePerformBlockOnEveryNode or a variant with a condition / test block. static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, BOOL flag) { // If there is no layer, but node whose its view is loaded, then we can traverse down its layer hierarchy. Otherwise we must stick to the node hierarchy to avoid loading views prematurely. Note that for nodes that haven't loaded their views, they can't possibly have subviews/sublayers, so we don't need to traverse the layer hierarchy for them. diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index 5ce6ac2ba5..ca12de19a8 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -24,6 +24,14 @@ extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer); */ extern ASDisplayNode *ASViewToDisplayNode(UIView *view); +/** + This function will walk the layer heirarchy, spanning discontinuous sections of the node heirarchy (e.g. the layers + of UIKit intermediate views in UIViewControllers, UITableView, UICollectionView). + In the event that a node's backing layer is not created yet, the function will only walk the direct subnodes instead + of forcing the layer heirarchy to be created. + */ +void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node)); + /** Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block. */ diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 8d2e261570..721e536785 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -10,16 +10,42 @@ #import "ASDisplayNodeInternal.h" -ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) +inline ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) { return layer.asyncdisplaykit_node; } -ASDisplayNode *ASViewToDisplayNode(UIView *view) +inline ASDisplayNode *ASViewToDisplayNode(UIView *view) { return view.asyncdisplaykit_node; } +void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node)) +{ + if (!node) { + ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer"); + ASDisplayNodeCAssertMainThread(); + node = ASLayerToDisplayNode(layer); + } + + if (node) { + block(node); + } + if (!layer && [node isNodeLoaded]) { + layer = node.layer; + } + + if (layer) { + for (CALayer *sublayer in [layer sublayers]) { + ASDisplayNodePerformBlockOnEveryNode(sublayer, nil, block); + } + } else if (node) { + for (ASDisplayNode *subnode in [node subnodes]) { + ASDisplayNodePerformBlockOnEveryNode(nil, subnode, block); + } + } +} + id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) { CALayer *layer = node.layer; diff --git a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm index 30723e43b7..1aef1c1e0e 100644 --- a/AsyncDisplayKit/Details/ASAbstractLayoutController.mm +++ b/AsyncDisplayKit/Details/ASAbstractLayoutController.mm @@ -26,14 +26,19 @@ - (instancetype)init } _tuningParameters = std::vector(ASLayoutRangeTypeCount); + _tuningParameters[ASLayoutRangeTypeVisible] = { + .leadingBufferScreenfuls = 0, + .trailingBufferScreenfuls = 0 + }; + _tuningParameters[ASLayoutRangeTypeRender] = { + .leadingBufferScreenfuls = 1.5, + .trailingBufferScreenfuls = 0.75 + }; _tuningParameters[ASLayoutRangeTypePreload] = { .leadingBufferScreenfuls = 3, .trailingBufferScreenfuls = 2 }; - _tuningParameters[ASLayoutRangeTypeRender] = { - .leadingBufferScreenfuls = 2, - .trailingBufferScreenfuls = 1 - }; + return self; } @@ -49,6 +54,7 @@ - (ASRangeTuningParameters)tuningParametersForRangeType:(ASLayoutRangeType)range - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType { ASDisplayNodeAssert(rangeType < _tuningParameters.size(), @"Requesting a range that is OOB for the configured tuning parameters"); + ASDisplayNodeAssert(rangeType != ASLayoutRangeTypeVisible, @"Must not set Visible range tuning parameters (always 0, 0)"); _tuningParameters[rangeType] = tuningParameters; } diff --git a/AsyncDisplayKit/Details/ASLayoutRangeType.h b/AsyncDisplayKit/Details/ASLayoutRangeType.h index 128b88b9fe..a5f2075485 100644 --- a/AsyncDisplayKit/Details/ASLayoutRangeType.h +++ b/AsyncDisplayKit/Details/ASLayoutRangeType.h @@ -9,6 +9,7 @@ #import typedef NS_ENUM(NSInteger, ASLayoutRangeType) { + ASLayoutRangeTypeVisible, ASLayoutRangeTypeRender, ASLayoutRangeTypePreload, ASLayoutRangeTypeCount diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 1907baff75..ccbeff0c66 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -11,6 +11,7 @@ #import "ASAssert.h" #import "ASDisplayNodeExtras.h" #import "ASMultiDimensionalArrayUtils.h" +#import "ASRangeHandlerVisible.h" #import "ASRangeHandlerRender.h" #import "ASRangeHandlerPreload.h" #import "ASInternalHelpers.h" @@ -36,6 +37,7 @@ - (instancetype)init { _rangeIsValid = YES; _rangeTypeIndexPaths = [NSMutableDictionary dictionary]; _rangeTypeHandlers = @{ + @(ASLayoutRangeTypeVisible): [[ASRangeHandlerVisible alloc] init], @(ASLayoutRangeTypeRender): [[ASRangeHandlerRender alloc] init], @(ASLayoutRangeTypePreload): [[ASRangeHandlerPreload alloc] init], }; diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm index fc61788fb0..adbbf5ecfb 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm @@ -7,9 +7,8 @@ */ #import "ASRangeHandlerPreload.h" - #import "ASDisplayNode.h" -#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNodeInternal.h" @implementation ASRangeHandlerPreload diff --git a/AsyncDisplayKit/Details/ASRangeHandlerVisible.h b/AsyncDisplayKit/Details/ASRangeHandlerVisible.h new file mode 100644 index 0000000000..eab9f77f29 --- /dev/null +++ b/AsyncDisplayKit/Details/ASRangeHandlerVisible.h @@ -0,0 +1,15 @@ +/* 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 + +#import + +@interface ASRangeHandlerVisible : NSObject + +@end diff --git a/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm b/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm new file mode 100644 index 0000000000..d8daf51889 --- /dev/null +++ b/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm @@ -0,0 +1,25 @@ +/* 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 "ASRangeHandlerVisible.h" +#import "ASDisplayNode.h" +#import "ASDisplayNodeInternal.h" + +@implementation ASRangeHandlerVisible + +- (void)node:(ASDisplayNode *)node enteredRangeOfType:(ASLayoutRangeType)rangeType +{ + [node enterInterfaceState:ASInterfaceStateVisible]; +} + +- (void)node:(ASDisplayNode *)node exitedRangeOfType:(ASLayoutRangeType)rangeType +{ + [node exitInterfaceState:ASInterfaceStateVisible]; +} + +@end diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 4953a5b538..156e6f7730 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -118,6 +118,10 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Bitmask to check which methods an object overrides. @property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides; +// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. +- (void)enterInterfaceState:(ASInterfaceState)interfaceState; +- (void)exitInterfaceState:(ASInterfaceState)interfaceState; + // Swizzle to extend the builtin functionality with custom logic - (BOOL)__shouldLoadViewOrLayer; - (BOOL)__shouldSize; From 254f55b7583f393895cd39adb87af44fa4f65d44 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 29 Nov 2015 14:57:43 -0800 Subject: [PATCH 12/12] Add locking for ASInterfaceState. Misc. cleanup. --- AsyncDisplayKit/ASDisplayNode.mm | 66 +++---------------- AsyncDisplayKit/ASDisplayNodeExtras.h | 17 ++++- AsyncDisplayKit/ASDisplayNodeExtras.mm | 45 ++++++++++++- .../Private/ASDisplayNodeInternal.h | 1 - .../Sample.xcodeproj/project.pbxproj | 4 +- 5 files changed, 68 insertions(+), 65 deletions(-) diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 298e683954..9c52f08c21 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -379,11 +379,6 @@ - (BOOL)__shouldSize return YES; } -- (void)__exitedHierarchy -{ - -} - - (UIView *)_viewToLoad { UIView *view; @@ -787,55 +782,9 @@ - (CATransform3D)_transformToAncestor:(ASDisplayNode *)ancestor return transform; } -static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendent) -{ - ASDisplayNode *supernode = possibleDescendent; - while (supernode) { - if (supernode == possibleAncestor) { - return YES; - } - supernode = supernode.supernode; - } - - return NO; -} - -/** - * NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is - * disallowed in UIKit documentation and the behavior is left undefined. The output does not have a rigorously defined - * failure mode (i.e. returning CGPointZero or returning the point exactly as passed in). Rather than track the internal - * undefined and undocumented behavior of UIKit in ASDisplayNode, this operation is defined to be incorrect in all - * circumstances and must be fixed wherever encountered. - */ -static inline ASDisplayNode *_ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2) -{ - ASDisplayNode *possibleAncestor = node1; - while (possibleAncestor) { - if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) { - break; - } - possibleAncestor = possibleAncestor.supernode; - } - - ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2); - return possibleAncestor; -} - -static inline ASDisplayNode *_getRootNode(ASDisplayNode *node) -{ - // node <- supernode on each loop - // previous <- node on each loop where node is not nil - // previous is the final non-nil value of supernode, i.e. the root node - ASDisplayNode *previousNode = node; - while ((node = [node supernode])) { - previousNode = node; - } - return previousNode; -} - static inline CATransform3D _calculateTransformFromReferenceToTarget(ASDisplayNode *referenceNode, ASDisplayNode *targetNode) { - ASDisplayNode *ancestor = _ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode); + ASDisplayNode *ancestor = ASDisplayNodeFindClosestCommonAncestor(referenceNode, targetNode); // Transform into global (away from reference coordinate space) CATransform3D transformToGlobal = [referenceNode _transformToAncestor:ancestor]; @@ -850,7 +799,7 @@ - (CGPoint)convertPoint:(CGPoint)point fromNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : _getRootNode(self); + node = node ? node : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); @@ -865,7 +814,7 @@ - (CGPoint)convertPoint:(CGPoint)point toNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : _getRootNode(self); + node = node ? node : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); @@ -880,7 +829,7 @@ - (CGRect)convertRect:(CGRect)rect fromNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : _getRootNode(self); + node = node ? node : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(node, self); @@ -895,7 +844,7 @@ - (CGRect)convertRect:(CGRect)rect toNode:(ASDisplayNode *)node { ASDisplayNodeAssertThreadAffinity(self); // Get root node of the accessible node hierarchy, if node not specified - node = node ? node : _getRootNode(self); + node = node ? node : ASDisplayNodeUltimateParentOfNode(self); // Calculate transform to map points between coordinate spaces CATransform3D nodeTransform = _calculateTransformFromReferenceToTarget(self, node); @@ -1614,6 +1563,7 @@ - (void)invalidateCalculatedLayout - (void)__didLoad { + ASDN::MutexLocker l(_propertyLock); if (_nodeLoadedBlock) { _nodeLoadedBlock(self); _nodeLoadedBlock = nil; @@ -1638,8 +1588,6 @@ - (void)didExitHierarchy ASDisplayNodeAssertMainThread(); ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - - [self __exitedHierarchy]; } - (void)clearContents @@ -1689,11 +1637,13 @@ - (void)recursivelyClearFetchedData - (ASInterfaceState)interfaceState { + ASDN::MutexLocker l(_propertyLock); return _interfaceState; } - (void)setInterfaceState:(ASInterfaceState)interfaceState { + ASDN::MutexLocker l(_propertyLock); if (interfaceState != _interfaceState) { if ((interfaceState & ASInterfaceStateMeasureLayout) != (_interfaceState & ASInterfaceStateMeasureLayout)) { // Trigger asynchronous measurement if it is not already cached or being calculated. diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index ca12de19a8..4ccf0b48ca 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -24,13 +24,18 @@ extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer); */ extern ASDisplayNode *ASViewToDisplayNode(UIView *view); +/** + Given a node, returns the root of the node heirarchy (where supernode == nil) + */ +extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node); + /** This function will walk the layer heirarchy, spanning discontinuous sections of the node heirarchy (e.g. the layers of UIKit intermediate views in UIViewControllers, UITableView, UICollectionView). In the event that a node's backing layer is not created yet, the function will only walk the direct subnodes instead of forcing the layer heirarchy to be created. */ -void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node)); +extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node)); /** Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block. @@ -42,6 +47,16 @@ extern id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *no */ extern id ASDisplayNodeFindClass(ASDisplayNode *start, Class c); +/** + * Given two nodes, finds their most immediate common parent. Used for geometry conversion methods. + * NOTE: It is an error to try to convert between nodes which do not share a common ancestor. This behavior is + * disallowed in UIKit documentation and the behavior is left undefined. The output does not have a rigorously defined + * failure mode (i.e. returning CGPointZero or returning the point exactly as passed in). Rather than track the internal + * undefined and undocumented behavior of UIKit in ASDisplayNode, this operation is defined to be incorrect in all + * circumstances and must be fixed wherever encountered. + */ +extern ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2); + /** Given a display node, collects all descendents. This is a specialization of ASCollectContainer() that walks the Core Animation layer tree as opposed to the display node tree, thus supporting non-continues display node hierarchies. */ diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index 721e536785..681640ffa3 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -10,17 +10,17 @@ #import "ASDisplayNodeInternal.h" -inline ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) +extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) { return layer.asyncdisplaykit_node; } -inline ASDisplayNode *ASViewToDisplayNode(UIView *view) +extern ASDisplayNode *ASViewToDisplayNode(UIView *view) { return view.asyncdisplaykit_node; } -void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node)) +extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode *node, void(^block)(ASDisplayNode *node)) { if (!node) { ASDisplayNodeCAssertNotNil(layer, @"Cannot recursively perform with nil node and nil layer"); @@ -148,6 +148,45 @@ extern id ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c) }); } +static inline BOOL _ASDisplayNodeIsAncestorOfDisplayNode(ASDisplayNode *possibleAncestor, ASDisplayNode *possibleDescendent) +{ + ASDisplayNode *supernode = possibleDescendent; + while (supernode) { + if (supernode == possibleAncestor) { + return YES; + } + supernode = supernode.supernode; + } + + return NO; +} + +extern ASDisplayNode *ASDisplayNodeFindClosestCommonAncestor(ASDisplayNode *node1, ASDisplayNode *node2) +{ + ASDisplayNode *possibleAncestor = node1; + while (possibleAncestor) { + if (_ASDisplayNodeIsAncestorOfDisplayNode(possibleAncestor, node2)) { + break; + } + possibleAncestor = possibleAncestor.supernode; + } + + ASDisplayNodeCAssertNotNil(possibleAncestor, @"Could not find a common ancestor between node1: %@ and node2: %@", node1, node2); + return possibleAncestor; +} + +extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node) +{ + // node <- supernode on each loop + // previous <- node on each loop where node is not nil + // previous is the final non-nil value of supernode, i.e. the root node + ASDisplayNode *previousNode = node; + while ((node = [node supernode])) { + previousNode = node; + } + return previousNode; +} + #pragma mark - Placeholders UIColor *ASDisplayNodeDefaultPlaceholderColor() diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 156e6f7730..0d3ab7efe3 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -125,7 +125,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Swizzle to extend the builtin functionality with custom logic - (BOOL)__shouldLoadViewOrLayer; - (BOOL)__shouldSize; -- (void)__exitedHierarchy; // Core implementation of -measureWithSizeRange:. Must be called with _propertyLock held. - (ASLayout *)__measureWithSizeRange:(ASSizeRange)constrainedSize; diff --git a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj index 038f1b0bf6..2be08f1dcd 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj +++ b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj @@ -266,7 +266,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -301,7 +301,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES;