From 1204737330416f0b3e5c2f0f014770a376e49206 Mon Sep 17 00:00:00 2001 From: Ofer Morag Date: Sun, 20 Aug 2017 11:46:02 +0300 Subject: [PATCH 01/22] Update corner-rounding.md (#482) * Update corner-rounding.md If I understand right, both Precomposited corners techniques uses `[path clip]`, right? If so, I think it will be better to move it to the general description of the section, as currently it seems as only the second option uses `[path clip]`. * Update corner-rounding.md Improved positioning. * Update corner-rounding.md Syntax consistency: Changed `off-screen` to `offscreen`, as both syntaxes currently appears in the text. --- docs/_docs/corner-rounding.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_docs/corner-rounding.md b/docs/_docs/corner-rounding.md index d0fff023..a69f8fa6 100755 --- a/docs/_docs/corner-rounding.md +++ b/docs/_docs/corner-rounding.md @@ -17,7 +17,7 @@ When it comes to corner rounding, many developers stick with CALayer's `.cornerR ## CALayer's .cornerRadius is Expensive -Why is `.cornerRadius` so expensive? Use of CALayer's `.cornerRadius` property triggers off-screen rendering to perform the clipping operation on every frame - 60 FPS during scrolling - even if the content in that area isn't changing! This means that the GPU has to switch contexts on every frame, between compositing the overall frame + additional passes for each use of `.cornerRadius`. +Why is `.cornerRadius` so expensive? Use of CALayer's `.cornerRadius` property triggers offscreen rendering to perform the clipping operation on every frame - 60 FPS during scrolling - even if the content in that area isn't changing! This means that the GPU has to switch contexts on every frame, between compositing the overall frame + additional passes for each use of `.cornerRadius`. Importantly, these costs don't show up in the Time Profiler, because they affect work done by the CoreAnimation Render Server on your app's behalf. This intensive thrash annihilates performance for a lot of devices. On the iPhone 4, 4S, and 5 / 5C (along with comparable iPads / iPods), expect to see notably degraded performance. On the iPhone 5S and newer, even if you can't see the impact directly, it will reduce headroom so that it takes less to cause a frame drop. @@ -53,11 +53,11 @@ The final consideration is to determine if all four corners cover the same node ### Precomposited Corners -Precomposited corners refer to corners drawn using bezier paths to clip the content in a CGContext / UIGraphicsContext. In this scenario, the corners become part of the image itself — and are "baked in" to the single CALayer. There are two types of precomposited corners. +Precomposited corners refer to corners drawn using bezier paths to clip the content in a CGContext / UIGraphicsContext (`[path clip]`). In this scenario, the corners become part of the image itself — and are "baked in" to the single CALayer. There are two types of precomposited corners. The absolute best method is to use **precomposited opaque corners**. This is the most efficient method available, resulting in zero alpha blending (although this is much less critical than avoiding offscreen rendering). Unfortunately, this method is also the least flexible; the background behind the corners will need to be a solid color if the rounded image needs to move around on top of it. It's possible, but tricky to make precomposited corners with a textured or photo background - usually it's best to use precomposited alpha corners instead'.' -The second method involves using bezier paths with **precomposited alpha corners** (`[path clip]`). This method is pretty flexible and should be one of the most frequently used. It does incur the cost of alpha blending across the full size of the content, and including an alpha channel increases memory impact by 25% over opaque precompositing - but these costs are tiny on modern devices, and a different order of magnitude than `.cornerRadius` offscreen rendering. +The second method involves using bezier paths with **precomposited alpha corners**. This method is pretty flexible and should be one of the most frequently used. It does incur the cost of alpha blending across the full size of the content, and including an alpha channel increases memory impact by 25% over opaque precompositing - but these costs are tiny on modern devices, and a different order of magnitude than `.cornerRadius` offscreen rendering. A key limitation of precomposited corners is that the corners must only touch one node and not intersect with any subnodes. If either of these conditions exist, clip corners must be used. From 65fabf49d70c6afc9985cbb2d4eac4e37380c3e9 Mon Sep 17 00:00:00 2001 From: appleguy Date: Sun, 20 Aug 2017 03:17:46 -0700 Subject: [PATCH 02/22] [ASImageNode] Enable .clipsToBounds by default (fix .cornerRadius, GIFs overflow). (#466) * [ASImageNode] Enable .clipsToBounds by default (fix .cornerRadius, GIFs overflow). We've seen a number of bugs reported over time that .cornerRadius didn't work on ASNetworkImageNode. This wasn't much of a concern because cornerRadius is very inefficient anyway, and there are better ways to round corners, but it should certainly work. It turns out that clipsToBounds has been off for images, and this ultimately was behind another issue recently seen wehre decoded GIFs would spill outside the bounds area to overlap nearby content. Although there is some risk of behavior change from this, I think the risk is fairly small, and in most cases it will probably fix behaviors in a way that doesn't cause problems for the app. We should consider if this property should be on for all ASDisplayNodes, but for now it would be a great step to be confident it's on for all ASImageNodes. * Update changelog for ImageNode Clipping. --- CHANGELOG.md | 1 + Source/ASImageNode.mm | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a07d918..078fa9e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASImageNode] Enabled .clipsToBounds by default, fixing the use of .cornerRadius and clipping of GIFs. [Scott Goodson](https://github.com/appleguy) [#466](https://github.com/TextureGroup/Texture/pull/466) - Fix an issue in layout transition that causes it to unexpectedly use the old layout [Huy Nguyen](https://github.com/nguyenhuy) [#464](https://github.com/TextureGroup/Texture/pull/464) - Add -[ASDisplayNode detailedLayoutDescription] property to aid debugging. [Adlai Holler](https://github.com/Adlai-Holler) [#476](https://github.com/TextureGroup/Texture/pull/476) - Fix an issue that causes calculatedLayoutDidChange being called needlessly. [Huy Nguyen](https://github.com/nguyenhuy) [#490](https://github.com/TextureGroup/Texture/pull/490) diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index d36509d5..dce952d2 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -175,7 +175,8 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry); self.contentsScale = ASScreenScale(); self.contentMode = UIViewContentModeScaleAspectFill; self.opaque = NO; - + self.clipsToBounds = YES; + // If no backgroundColor is set to the image node and it's a subview of UITableViewCell, UITableView is setting // the opaque value of all subviews to YES if highlighting / selection is happening and does not set it back to the // initial value. With setting a explicit backgroundColor we can prevent that change. From 5cf05f3c1777739b8a8117b3c176a79a713576bc Mon Sep 17 00:00:00 2001 From: appleguy Date: Sun, 20 Aug 2017 13:17:05 -0700 Subject: [PATCH 03/22] [Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. (#468) After consulting Apple documentation and working with some a11y experts, we've found that aggregating objects that have a11y labels but are not themselves interactable is significantly preferred for these users. It makes it much quicker to navigate scrolling content if VoiceOver only stops to select entire cells, and then allows drilling down into the cell to select individual components. This implementation achieves that behavior. We should consider enabling isAccessibilityContainer by default on ASCellNode. This would be an improvement for 95% of a11y use cases. Aggregation can be enabled or disabled on any node. --- CHANGELOG.md | 1 + Source/ASDisplayNode+Beta.h | 13 +++ Source/ASDisplayNode+Yoga.mm | 6 ++ Source/ASDisplayNode.mm | 13 +++ Source/Details/_ASDisplayViewAccessiblity.mm | 89 +++++++++++++++++++- Source/Private/ASDisplayNodeInternal.h | 1 + 6 files changed, 120 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 078fa9e1..e8302e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [#468][Scott Goodson](https://github.com/appleguy) - [ASImageNode] Enabled .clipsToBounds by default, fixing the use of .cornerRadius and clipping of GIFs. [Scott Goodson](https://github.com/appleguy) [#466](https://github.com/TextureGroup/Texture/pull/466) - Fix an issue in layout transition that causes it to unexpectedly use the old layout [Huy Nguyen](https://github.com/nguyenhuy) [#464](https://github.com/TextureGroup/Texture/pull/464) - Add -[ASDisplayNode detailedLayoutDescription] property to aid debugging. [Adlai Holler](https://github.com/Adlai-Holler) [#476](https://github.com/TextureGroup/Texture/pull/476) diff --git a/Source/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h index 8235c24c..8e413a5a 100644 --- a/Source/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -112,6 +112,19 @@ typedef struct { @property (nonatomic, strong, readonly) ASEventLog *eventLog; #endif +/** + * @abstract Whether this node acts as an accessibility container. If set to YES, then this node's accessibility label will represent + * an aggregation of all child nodes' accessibility labels. Nodes in this node's subtree that are also accessibility containers will + * not be included in this aggregation, and will be exposed as separate accessibility elements to UIKit. + */ +@property (nonatomic, assign) BOOL isAccessibilityContainer; + +/** + * @abstract Invoked when a user performs a custom action on an accessible node. Nodes that are children of accessibility containers, have + * an accessibity label and have an interactive UIAccessibilityTrait will automatically receive custom-action handling. + */ +- (void)performAccessibilityCustomAction:(UIAccessibilityCustomAction *)action; + /** * @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/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index cc41593a..281bf1e7 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -19,6 +19,7 @@ #if YOGA /* YOGA */ +#import #import #import #import @@ -235,6 +236,11 @@ yogaFloatForCGFloat(rootConstrainedSize.max.height), YGDirectionInherit); + // Reset accessible elements, since layout may have changed. + ASPerformBlockOnMainThread(^{ + [(_ASDisplayView *)self.view setAccessibleElements:nil]; + }); + ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { [node setupYogaCalculatedLayout]; node.yogaLayoutInProgress = NO; diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 2b58f943..e1ce6f8b 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3171,6 +3171,19 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { return measurements; } +#pragma mark - Accessibility + +- (void)setIsAccessibilityContainer:(BOOL)isAccessibilityContainer +{ + ASDN::MutexLocker l(__instanceLock__); + _isAccessibilityContainer = isAccessibilityContainer; +} + +- (BOOL)isAccessibilityContainer +{ + ASDN::MutexLocker l(__instanceLock__); + return _isAccessibilityContainer; +} #pragma mark - Debugging (Private) diff --git a/Source/Details/_ASDisplayViewAccessiblity.mm b/Source/Details/_ASDisplayViewAccessiblity.mm index e2f93740..80ae2a92 100644 --- a/Source/Details/_ASDisplayViewAccessiblity.mm +++ b/Source/Details/_ASDisplayViewAccessiblity.mm @@ -23,9 +23,21 @@ #import #import +#import + +NS_INLINE UIAccessibilityTraits InteractiveAccessibilityTraitsMask() { + return UIAccessibilityTraitLink | UIAccessibilityTraitKeyboardKey | UIAccessibilityTraitButton; +} + #pragma mark - UIAccessibilityElement -typedef NSComparisonResult (^SortAccessibilityElementsComparator)(UIAccessibilityElement *, UIAccessibilityElement *); +@protocol ASAccessibilityElementPositioning + +@property (nonatomic, readonly) CGRect accessibilityFrame; + +@end + +typedef NSComparisonResult (^SortAccessibilityElementsComparator)(id, id); /// Sort accessiblity elements first by y and than by x origin. static void SortAccessibilityElements(NSMutableArray *elements) @@ -35,7 +47,7 @@ static void SortAccessibilityElements(NSMutableArray *elements) static SortAccessibilityElementsComparator comparator = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - comparator = ^NSComparisonResult(UIAccessibilityElement *a, UIAccessibilityElement *b) { + comparator = ^NSComparisonResult(id a, id b) { CGPoint originA = a.accessibilityFrame.origin; CGPoint originB = b.accessibilityFrame.origin; if (originA.y == originB.y) { @@ -50,7 +62,7 @@ static void SortAccessibilityElements(NSMutableArray *elements) [elements sortUsingComparator:comparator]; } -@interface ASAccessibilityElement : UIAccessibilityElement +@interface ASAccessibilityElement : UIAccessibilityElement @property (nonatomic, strong) ASDisplayNode *node; @property (nonatomic, strong) ASDisplayNode *containerNode; @@ -85,6 +97,25 @@ static void SortAccessibilityElements(NSMutableArray *elements) #pragma mark - _ASDisplayView / UIAccessibilityContainer +@interface ASAccessibilityCustomAction : UIAccessibilityCustomAction + +@property (nonatomic, strong) UIView *container; +@property (nonatomic, strong) ASDisplayNode *node; +@property (nonatomic, strong) ASDisplayNode *containerNode; + +@end + +@implementation ASAccessibilityCustomAction + +- (CGRect)accessibilityFrame +{ + CGRect accessibilityFrame = [self.containerNode convertRect:self.node.bounds fromNode:self.node]; + accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(accessibilityFrame, self.container); + return accessibilityFrame; +} + +@end + /// 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) { @@ -100,12 +131,64 @@ static void CollectUIAccessibilityElementsForNode(ASDisplayNode *node, ASDisplay }); } +static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, _ASDisplayView *view, NSMutableArray *elements) { + UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:container containerNode:container]; + + NSMutableArray *labeledNodes = [NSMutableArray array]; + NSMutableArray *actions = [NSMutableArray array]; + std::queue queue; + queue.push(container); + + ASDisplayNode *node; + while (!queue.empty()) { + node = queue.front(); + queue.pop(); + + if (node != container && node.isAccessibilityContainer) { + CollectAccessibilityElementsForContainer(node, view, elements); + continue; + } + + if (node.accessibilityLabel.length > 0) { + if (node.accessibilityTraits & InteractiveAccessibilityTraitsMask()) { + ASAccessibilityCustomAction *action = [[ASAccessibilityCustomAction alloc] initWithName:node.accessibilityLabel target:node selector:@selector(performAccessibilityCustomAction:)]; + action.node = node; + action.containerNode = node.supernode; + action.container = node.supernode.view; + [actions addObject:action]; + } else { + // Even though not surfaced to UIKit, create a non-interactive element for purposes of building sorted aggregated label. + ASAccessibilityElement *nonInteractiveElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:node containerNode:container]; + [labeledNodes addObject:nonInteractiveElement]; + } + } + + for (ASDisplayNode *subnode in node.subnodes) { + queue.push(subnode); + } + } + + SortAccessibilityElements(labeledNodes); + NSArray *labels = [labeledNodes valueForKey:@"accessibilityLabel"]; + accessiblityElement.accessibilityLabel = [labels componentsJoinedByString:@", "]; + + SortAccessibilityElements(actions); + accessiblityElement.accessibilityCustomActions = actions; + + [elements addObject:accessiblityElement]; +} + /// 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"); ASDisplayNode *node = view.asyncdisplaykit_node; + + if (node.isAccessibilityContainer) { + CollectAccessibilityElementsForContainer(node, view, elements); + return; + } // Handle rasterize case if (node.rasterizesSubtree) { diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 7555e093..36683954 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -195,6 +195,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo NSArray *_accessibilityHeaderElements; CGPoint _accessibilityActivationPoint; UIBezierPath *_accessibilityPath; + BOOL _isAccessibilityContainer; // performance measurement ASDisplayNodePerformanceMeasurementOptions _measurementOptions; From 359b5f0b5bece3f41fcc6e1a1da1f1cbf510f626 Mon Sep 17 00:00:00 2001 From: Phil Larson Date: Mon, 21 Aug 2017 04:49:11 -0700 Subject: [PATCH 04/22] ASImageNode+AnimatedImage playbackReadyCallback retain cycle (#520) * ASImageNode+AnimatedImage playbackReadyCallback causes strong retain cycle * Add CHANGELOG entry for #520 --- CHANGELOG.md | 1 + Source/ASImageNode+AnimatedImage.mm | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8302e82..45656791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [Breaking] Add content offset bridging property to ASTableNode and ASCollectionNode. Deprecate related methods in ASTableView and ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#460](https://github.com/TextureGroup/Texture/pull/460) - Remove re-entrant access to self.view when applying initial pending state. [Adlai Holler](https://github.com/Adlai-Holler) [#510](https://github.com/TextureGroup/Texture/pull/510) - Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513) +- Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm index 0ff21c4b..0d12351d 100644 --- a/Source/ASImageNode+AnimatedImage.mm +++ b/Source/ASImageNode+AnimatedImage.mm @@ -70,7 +70,7 @@ NSString *const ASAnimatedImageDefaultRunLoopMode = NSRunLoopCommonModes; } else { animatedImage.playbackReadyCallback = ^{ // In this case the lock is already gone we have to call the unlocked version therefore - [self setShouldAnimate:YES]; + [weakSelf setShouldAnimate:YES]; }; } } From 884a4f56f1792e0f90ffddba25f2a2594e7675f1 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Tue, 22 Aug 2017 22:14:01 +0100 Subject: [PATCH 05/22] [Table and collection views] Consider content inset when calculating (default) element size range (#525) * Table and collection views to consider their content inset when calculating element size range * Update CHANGELOG * Address comments * -[ASPagerNode currentPageIndex] to use pageSize instead of bounds * Update documentation in ASPagerNode * Minor change --- CHANGELOG.md | 1 + Source/ASCollectionView.mm | 4 ---- Source/ASPagerNode.h | 5 +++++ Source/ASPagerNode.m | 15 ++++++++++++--- Source/ASTableView.mm | 3 ++- Source/Details/ASCollectionViewLayoutInspector.m | 3 +++ 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45656791..26d77718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Remove re-entrant access to self.view when applying initial pending state. [Adlai Holler](https://github.com/Adlai-Holler) [#510](https://github.com/TextureGroup/Texture/pull/510) - Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513) - Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520) +- Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 83bcc244..f7ce6859 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1536,10 +1536,6 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section return [self.layoutInspector scrollableDirections]; } -- (ASScrollDirection)flowLayoutScrollableDirections:(UICollectionViewFlowLayout *)flowLayout { - return (flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal) ? ASScrollDirectionHorizontalDirections : ASScrollDirectionVerticalDirections; -} - - (void)layoutSubviews { if (_cellsForLayoutUpdates.count > 0) { diff --git a/Source/ASPagerNode.h b/Source/ASPagerNode.h index f5c01641..51e57898 100644 --- a/Source/ASPagerNode.h +++ b/Source/ASPagerNode.h @@ -75,6 +75,9 @@ NS_ASSUME_NONNULL_BEGIN @end +/** + * A horizontal, paging collection node. + */ @interface ASPagerNode : ASCollectionNode /** @@ -84,6 +87,8 @@ NS_ASSUME_NONNULL_BEGIN /** * Initializer with custom-configured flow layout properties. + * + * NOTE: The flow layout must have a horizontal scroll direction. */ - (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; diff --git a/Source/ASPagerNode.m b/Source/ASPagerNode.m index 02666993..86062f66 100644 --- a/Source/ASPagerNode.m +++ b/Source/ASPagerNode.m @@ -66,6 +66,7 @@ - (instancetype)initWithCollectionViewLayout:(ASPagerFlowLayout *)flowLayout; { ASDisplayNodeAssert([flowLayout isKindOfClass:[ASPagerFlowLayout class]], @"ASPagerNode requires a flow layout."); + ASDisplayNodeAssertTrue(flowLayout.scrollDirection == UICollectionViewScrollDirectionHorizontal); self = [super initWithCollectionViewLayout:flowLayout]; return self; } @@ -112,7 +113,15 @@ - (NSInteger)currentPageIndex { - return (self.view.contentOffset.x / CGRectGetWidth(self.view.bounds)); + return (self.view.contentOffset.x / [self pageSize].width); +} + +- (CGSize)pageSize +{ + UIEdgeInsets contentInset = self.view.contentInset; + CGSize pageSize = self.bounds.size; + pageSize.height -= (contentInset.top + contentInset.bottom); + return pageSize; } #pragma mark - Helpers @@ -142,7 +151,7 @@ - (CGSize)sizeForElements:(ASElementMap *)elements { ASDisplayNodeAssertMainThread(); - return self.bounds.size; + return [self pageSize]; } #pragma mark - ASCollectionDataSource @@ -179,7 +188,7 @@ } #pragma clang diagnostic pop - return ASSizeRangeMake(self.bounds.size); + return ASSizeRangeMake([self pageSize]); } #pragma mark - Data Source Proxy diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index d0e529c8..38bfe683 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -751,7 +751,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { // Remeasure all rows if our row width has changed. _remeasuringCellNodes = YES; - CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth]; + UIEdgeInsets contentInset = self.contentInset; + CGFloat constrainedWidth = self.bounds.size.width - [self sectionIndexWidth] - contentInset.left - contentInset.right; if (constrainedWidth > 0 && _nodesConstrainedWidth != constrainedWidth) { _nodesConstrainedWidth = constrainedWidth; diff --git a/Source/Details/ASCollectionViewLayoutInspector.m b/Source/Details/ASCollectionViewLayoutInspector.m index b77f2217..26100cec 100644 --- a/Source/Details/ASCollectionViewLayoutInspector.m +++ b/Source/Details/ASCollectionViewLayoutInspector.m @@ -27,9 +27,12 @@ // of the collection view ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionView) { CGSize maxSize = collectionView.bounds.size; + UIEdgeInsets contentInset = collectionView.contentInset; if (ASScrollDirectionContainsHorizontalDirection(collectionView.scrollableDirections)) { maxSize.width = CGFLOAT_MAX; + maxSize.height -= (contentInset.top + contentInset.bottom); } else { + maxSize.width -= (contentInset.left + contentInset.right); maxSize.height = CGFLOAT_MAX; } return ASSizeRangeMake(CGSizeZero, maxSize); From 16ce3c9a33e83b50a01323e93963a1a584939b33 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 22 Aug 2017 14:48:42 -0700 Subject: [PATCH 06/22] Add a function to disable all logging at runtime (#528) * Implement a runtime disable for all logging * Update the changelog * Inline the function * Flip the scrip --- CHANGELOG.md | 1 + Source/Base/ASLog.h | 19 +++++++++++++++---- Source/Base/ASLog.m | 26 ++++++++++++++++++++------ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26d77718..4b08e8ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Remove re-entrant access to self.view when applying initial pending state. [Adlai Holler](https://github.com/Adlai-Holler) [#510](https://github.com/TextureGroup/Texture/pull/510) - Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513) - Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520) +- Change the API for disabling logging from a compiler flag to a runtime C function ASDisableLogging(). [Adlai Holler](https://github.com/Adlai-Holler) [#528](https://github.com/TextureGroup/Texture/pull/528) - Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525) ##2.4 diff --git a/Source/Base/ASLog.h b/Source/Base/ASLog.h index 412b9d90..e4f54fd8 100644 --- a/Source/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -21,16 +21,27 @@ #import #import -#ifndef ASEnableLogs - #define ASEnableLogs 1 -#endif - #ifndef ASEnableVerboseLogging #define ASEnableVerboseLogging 0 #endif ASDISPLAYNODE_EXTERN_C_BEGIN +/** + * Disable all logging. + * + * You should only use this function if the default log level is + * annoying during development. By default, logging is run at + * the appropriate system log level (see the os_log_* functions), + * so you do not need to worry generally about the performance + * implications of log messages. + * + * For example, virtually all log messages generated by Texture + * are at the `debug` log level, which the system + * disables in production. + */ +void ASDisableLogging(); + /// Log for general node events e.g. interfaceState, didLoad. #define ASNodeLogEnabled 1 os_log_t ASNodeLog(); diff --git a/Source/Base/ASLog.m b/Source/Base/ASLog.m index 42148de9..8e65c4b5 100644 --- a/Source/Base/ASLog.m +++ b/Source/Base/ASLog.m @@ -11,27 +11,41 @@ // #import +#import + +static atomic_bool __ASLogEnabled = ATOMIC_VAR_INIT(YES); + +void ASDisableLogging() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + atomic_store(&__ASLogEnabled, NO); + }); +} + +ASDISPLAYNODE_INLINE BOOL ASLoggingIsEnabled() { + return atomic_load(&__ASLogEnabled); +} os_log_t ASNodeLog() { - return ASCreateOnce((ASEnableLogs && ASNodeLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Node") : OS_LOG_DISABLED); + return (ASNodeLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Node")) : OS_LOG_DISABLED; } os_log_t ASLayoutLog() { - return ASCreateOnce((ASEnableLogs && ASLayoutLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Layout") : OS_LOG_DISABLED); + return (ASLayoutLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Layout")) : OS_LOG_DISABLED; } os_log_t ASCollectionLog() { - return ASCreateOnce((ASEnableLogs && ASCollectionLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Collection") : OS_LOG_DISABLED); + return (ASCollectionLogEnabled && ASLoggingIsEnabled()) ?ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Collection")) : OS_LOG_DISABLED; } os_log_t ASDisplayLog() { - return ASCreateOnce((ASEnableLogs && ASDisplayLogEnabled) ? as_log_create("org.TextureGroup.Texture", "Display") : OS_LOG_DISABLED); + return (ASDisplayLogEnabled && ASLoggingIsEnabled()) ?ASCreateOnce(as_log_create("org.TextureGroup.Texture", "Display")) : OS_LOG_DISABLED; } os_log_t ASImageLoadingLog() { - return ASCreateOnce((ASEnableLogs && ASImageLoadingLogEnabled) ? as_log_create("org.TextureGroup.Texture", "ImageLoading") : OS_LOG_DISABLED); + return (ASImageLoadingLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "ImageLoading")) : OS_LOG_DISABLED; } os_log_t ASMainThreadDeallocationLog() { - return ASCreateOnce((ASEnableLogs && ASMainThreadDeallocationLogEnabled) ? as_log_create("org.TextureGroup.Texture", "MainDealloc") : OS_LOG_DISABLED); + return (ASMainThreadDeallocationLogEnabled && ASLoggingIsEnabled()) ? ASCreateOnce(as_log_create("org.TextureGroup.Texture", "MainDealloc")) : OS_LOG_DISABLED; } From ccc57860322f791dd1410641a15a889a639815d9 Mon Sep 17 00:00:00 2001 From: appleguy Date: Wed, 23 Aug 2017 03:16:21 -0700 Subject: [PATCH 07/22] [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. (#522) * [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. Over time, there have actually been a lot of legitimate uses for an API like this. In fact, I'm not quite sure what has held us back from adding one! I believe that at least some portion of -wait calls (even if less than 50%) could be replaced with -onDidFinishProcessingUpdates:, which could potentially improve the performance of applications using -wait by a significant amount. Please take a close look at implementation correctness. Although I'm in a bit of a rush, I'm aiming to make this properly documented and added a basic test -- but it could certainly use some more detailed testing as a followup. * [ASCollectionNode] Improvements to the implementation of -isProcessingUpdates and -onDidFinishProcessingUpdates: * Add lock to ASMainSerialQueue count method. * [ASTableNode] Implement -isProcessingUpdates and -onDidFinishProcessingUpdates:. Rename -waitUntil to consistent naming. --- CHANGELOG.md | 1 + Source/ASCollectionNode.h | 37 +++++++++++++++++++-- Source/ASCollectionNode.mm | 23 +++++++++++-- Source/ASCollectionView.h | 6 ++-- Source/ASCollectionView.mm | 16 ++++++++-- Source/ASTableNode.h | 39 +++++++++++++++++++++-- Source/ASTableNode.mm | 21 +++++++++++- Source/ASTableView.h | 6 ++-- Source/ASTableView.mm | 16 ++++++++-- Source/Details/ASDataController.h | 7 +++- Source/Details/ASDataController.mm | 33 +++++++++++++++++-- Source/Details/ASMainSerialQueue.h | 1 + Source/Details/ASMainSerialQueue.mm | 6 ++++ Tests/ASCollectionModernDataSourceTests.m | 2 +- Tests/ASCollectionViewTests.mm | 31 +++++++++++------- Tests/ASTableViewTests.mm | 16 +++++----- 16 files changed, 220 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b08e8ea..3ba86ee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. [#522](https://github.com/TextureGroup/Texture/pull/522) [Scott Goodson](https://github.com/appleguy) - [Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [#468][Scott Goodson](https://github.com/appleguy) - [ASImageNode] Enabled .clipsToBounds by default, fixing the use of .cornerRadius and clipping of GIFs. [Scott Goodson](https://github.com/appleguy) [#466](https://github.com/TextureGroup/Texture/pull/466) - Fix an issue in layout transition that causes it to unexpectedly use the old layout [Huy Nguyen](https://github.com/nguyenhuy) [#464](https://github.com/TextureGroup/Texture/pull/464) diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 9ba6fdce..352867e9 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -252,10 +252,39 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +/** + * Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:. + * This is typically the concurrent allocation (calling nodeBlocks) and layout of newly inserted + * ASCellNodes. If YES is returned, then calling -waitUntilAllUpdatesAreProcessed may take tens of + * milliseconds to return as it blocks on these concurrent operations. + * + * Returns NO if ASCollectionNode is fully synchronized with the underlying UICollectionView. This + * means that until the next performBatchUpdates: is called, it is safe to compare UIKit values + * (such as from UICollectionViewLayout) with your app's data source. + * + * This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed. + */ +@property (nonatomic, readonly) BOOL isProcessingUpdates; + +/** + * Schedules a block to be performed (on the main thread) after processing of performBatchUpdates: + * is finished (completely synchronized to UIKit). The blocks will be run at the moment that + * -isProcessingUpdates changes from YES to NO; + * + * When isProcessingUpdates == NO, the block is run block immediately (before the method returns). + * + * Blocks scheduled by this mechanism are NOT guaranteed to run in the order they are scheduled. + * They may also be delayed if performBatchUpdates continues to be called; the blocks will wait until + * all running updates are finished. + * + * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. + */ +- (void)onDidFinishProcessingUpdates:(nullable void (^)())didFinishProcessingUpdates; + /** * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. */ -- (void)waitUntilAllUpdatesAreCommitted; +- (void)waitUntilAllUpdatesAreProcessed; /** * Inserts one or more sections. @@ -501,9 +530,11 @@ NS_ASSUME_NONNULL_BEGIN * @warning This method is substantially more expensive than UICollectionView's version. * * @deprecated This method is deprecated in 2.0. Use @c reloadDataWithCompletion: and - * then @c waitUntilAllUpdatesAreCommitted instead. + * then @c waitUntilAllUpdatesAreProcessed instead. */ -- (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use -reloadData / -reloadDataWithCompletion: followed by -waitUntilAllUpdatesAreCommitted instead."); +- (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use -reloadData / -reloadDataWithCompletion: followed by -waitUntilAllUpdatesAreProcessed instead."); + +- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); @end diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 994e2618..5bb12bef 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -705,7 +705,21 @@ [self performBatchAnimated:UIView.areAnimationsEnabled updates:updates completion:completion]; } -- (void)waitUntilAllUpdatesAreCommitted +- (BOOL)isProcessingUpdates +{ + return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); +} + +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +{ + if (!self.nodeLoaded) { + completion(); + } else { + [self.view onDidFinishProcessingUpdates:completion]; + } +} + +- (void)waitUntilAllUpdatesAreProcessed { ASDisplayNodeAssertMainThread(); if (self.nodeLoaded) { @@ -713,6 +727,11 @@ } } +- (void)waitUntilAllUpdatesAreCommitted +{ + [self waitUntilAllUpdatesAreProcessed]; +} + - (void)reloadDataWithCompletion:(void (^)())completion { ASDisplayNodeAssertMainThread(); @@ -738,7 +757,7 @@ { ASDisplayNodeAssertMainThread(); [self reloadData]; - [self waitUntilAllUpdatesAreCommitted]; + [self waitUntilAllUpdatesAreProcessed]; } - (void)relayoutItems diff --git a/Source/ASCollectionView.h b/Source/ASCollectionView.h index f4dd8913..2a1eb185 100644 --- a/Source/ASCollectionView.h +++ b/Source/ASCollectionView.h @@ -296,9 +296,11 @@ NS_ASSUME_NONNULL_BEGIN - (void)relayoutItems ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); /** - * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. + * See ASCollectionNode.h for full documentation of these methods. */ -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +@property (nonatomic, readonly) BOOL isProcessingUpdates; +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion; +- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead."); /** * Registers the given kind of supplementary node for use in creating node-backed supplementary views. diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index f7ce6859..906577fd 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -359,6 +359,16 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; [_dataController relayoutAllNodes]; } +- (BOOL)isProcessingUpdates +{ + return [_dataController isProcessingUpdates]; +} + +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +{ + [_dataController onDidFinishProcessingUpdates:completion]; +} + - (void)waitUntilAllUpdatesAreCommitted { ASDisplayNodeAssertMainThread(); @@ -367,8 +377,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier"; // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); return; } - - [_dataController waitUntilAllUpdatesAreCommitted]; + + [_dataController waitUntilAllUpdatesAreProcessed]; } - (void)setDataSource:(id)dataSource @@ -2193,7 +2203,7 @@ minimumLineSpacingForSectionAtIndex:(NSInteger)section if (changedInNonScrollingDirection) { [_dataController relayoutAllNodes]; - [_dataController waitUntilAllUpdatesAreCommitted]; + [_dataController waitUntilAllUpdatesAreProcessed]; // We need to ensure the size requery is done before we update our layout. [self.collectionViewLayout invalidateLayout]; } diff --git a/Source/ASTableNode.h b/Source/ASTableNode.h index 20d59d2e..08b26a6f 100644 --- a/Source/ASTableNode.h +++ b/Source/ASTableNode.h @@ -207,9 +207,38 @@ NS_ASSUME_NONNULL_BEGIN - (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; /** - * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. + * Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:. + * This is typically the concurrent allocation (calling nodeBlocks) and layout of newly inserted + * ASCellNodes. If YES is returned, then calling -waitUntilAllUpdatesAreProcessed may take tens of + * milliseconds to return as it blocks on these concurrent operations. + * + * Returns NO if ASCollectionNode is fully synchronized with the underlying UICollectionView. This + * means that until the next performBatchUpdates: is called, it is safe to compare UIKit values + * (such as from UICollectionViewLayout) with your app's data source. + * + * This method will always return NO if called immediately after -waitUntilAllUpdatesAreProcessed. */ -- (void)waitUntilAllUpdatesAreCommitted; +@property (nonatomic, readonly) BOOL isProcessingUpdates; + +/** + * Schedules a block to be performed (on the main thread) after processing of performBatchUpdates: + * is finished (completely synchronized to UIKit). The blocks will be run at the moment that + * -isProcessingUpdates changes from YES to NO; + * + * When isProcessingUpdates == NO, the block is run block immediately (before the method returns). + * + * Blocks scheduled by this mechanism are NOT guaranteed to run in the order they are scheduled. + * They may also be delayed if performBatchUpdates continues to be called; the blocks will wait until + * all running updates are finished. + * + * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. + */ +- (void)onDidFinishProcessingUpdates:(nullable void (^)())didFinishProcessingUpdates; + +/** + * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. + */ +- (void)waitUntilAllUpdatesAreProcessed; /** * Inserts one or more sections, with an option to animate the insertion. @@ -699,4 +728,10 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASTableNode (Deprecated) + +- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); + +@end + NS_ASSUME_NONNULL_END diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index 4cd7fd92..b4a3e06d 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -733,7 +733,21 @@ ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) } } -- (void)waitUntilAllUpdatesAreCommitted +- (BOOL)isProcessingUpdates +{ + return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); +} + +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +{ + if (!self.nodeLoaded) { + completion(); + } else { + [self.view onDidFinishProcessingUpdates:completion]; + } +} + +- (void)waitUntilAllUpdatesAreProcessed { ASDisplayNodeAssertMainThread(); if (self.nodeLoaded) { @@ -741,6 +755,11 @@ ASLayoutElementCollectionTableSetTraitCollection(_environmentStateLock) } } +- (void)waitUntilAllUpdatesAreCommitted +{ + [self waitUntilAllUpdatesAreProcessed]; +} + #pragma mark - Debugging (Private) - (NSMutableArray *)propertiesForDebugDescription diff --git a/Source/ASTableView.h b/Source/ASTableView.h index 5877ab4d..0a3d07ec 100644 --- a/Source/ASTableView.h +++ b/Source/ASTableView.h @@ -219,9 +219,11 @@ NS_ASSUME_NONNULL_BEGIN - (void)endUpdatesAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL completed))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's -performBatchUpdates:completion: instead."); /** - * Blocks execution of the main thread until all section and row updates are committed. This method must be called from the main thread. + * See ASTableNode.h for full documentation of these methods. */ -- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); +@property (nonatomic, readonly) BOOL isProcessingUpdates; +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion; +- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASTableNode waitUntilAllUpdatesAreProcessed] instead."); - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 38bfe683..8f41cffa 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -548,7 +548,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; { ASDisplayNodeAssertMainThread(); [self reloadData]; - [_dataController waitUntilAllUpdatesAreCommitted]; + [_dataController waitUntilAllUpdatesAreProcessed]; } - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated @@ -735,6 +735,16 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; } } +- (BOOL)isProcessingUpdates +{ + return [_dataController isProcessingUpdates]; +} + +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +{ + [_dataController onDidFinishProcessingUpdates:completion]; +} + - (void)waitUntilAllUpdatesAreCommitted { ASDisplayNodeAssertMainThread(); @@ -743,8 +753,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; // ASDisplayNodeFailAssert(@"Should not call %@ during batch update", NSStringFromSelector(_cmd)); return; } - - [_dataController waitUntilAllUpdatesAreCommitted]; + + [_dataController waitUntilAllUpdatesAreProcessed]; } - (void)layoutSubviews diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index 82791a68..b2d2f3ab 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -254,7 +254,12 @@ extern NSString * const ASCollectionInvalidUpdateException; */ - (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableArray * _Nonnull)nodesSizesChanged; -- (void)waitUntilAllUpdatesAreCommitted; +/** + * See ASCollectionNode.h for full documentation of these methods. + */ +@property (nonatomic, readonly) BOOL isProcessingUpdates; +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion; +- (void)waitUntilAllUpdatesAreProcessed; /** * Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 102fca12..88959950 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -430,13 +430,42 @@ typedef dispatch_block_t ASDataControllerCompletionBlock; #pragma mark - Batching (External API) -- (void)waitUntilAllUpdatesAreCommitted +- (void)waitUntilAllUpdatesAreProcessed { // Schedule block in main serial queue to wait until all operations are finished that are // where scheduled while waiting for the _editingTransactionQueue to finish [self _scheduleBlockOnMainSerialQueue:^{ }]; } +- (BOOL)isProcessingUpdates +{ + ASDisplayNodeAssertMainThread(); + if (_mainSerialQueue.numberOfScheduledBlocks > 0) { + return YES; + } else if (dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_NOW) != 0) { + // After waiting for zero duration, a nonzero value is returned if blocks are still running. + return YES; + } + // Both the _mainSerialQueue and _editingTransactionQueue are drained; we are fully quiesced. + return NO; +} + +- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +{ + ASDisplayNodeAssertMainThread(); + if ([self isProcessingUpdates] == NO) { + ASPerformBlockOnMainThread(completion); + } else { + dispatch_async(_editingTransactionQueue, ^{ + // Retry the block. If we're done processing updates, it'll run immediately, otherwise + // wait again for updates to quiesce completely. + [_mainSerialQueue performBlockOnMainThread:^{ + [self onDidFinishProcessingUpdates:completion]; + }]; + }); + } +} + - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); @@ -563,7 +592,7 @@ typedef dispatch_block_t ASDataControllerCompletionBlock; }); if (_usesSynchronousDataLoading) { - [self waitUntilAllUpdatesAreCommitted]; + [self waitUntilAllUpdatesAreProcessed]; } } diff --git a/Source/Details/ASMainSerialQueue.h b/Source/Details/ASMainSerialQueue.h index ef939eff..e94451d6 100644 --- a/Source/Details/ASMainSerialQueue.h +++ b/Source/Details/ASMainSerialQueue.h @@ -21,6 +21,7 @@ AS_SUBCLASSING_RESTRICTED @interface ASMainSerialQueue : NSObject +@property (nonatomic, readonly) NSUInteger numberOfScheduledBlocks; - (void)performBlockOnMainThread:(dispatch_block_t)block; @end diff --git a/Source/Details/ASMainSerialQueue.mm b/Source/Details/ASMainSerialQueue.mm index e79481fa..4a3d929c 100644 --- a/Source/Details/ASMainSerialQueue.mm +++ b/Source/Details/ASMainSerialQueue.mm @@ -40,6 +40,12 @@ return self; } +- (NSUInteger)numberOfScheduledBlocks +{ + ASDN::MutexLocker l(_serialQueueLock); + return _blocks.count; +} + - (void)performBlockOnMainThread:(dispatch_block_t)block { ASDN::MutexLocker l(_serialQueueLock); diff --git a/Tests/ASCollectionModernDataSourceTests.m b/Tests/ASCollectionModernDataSourceTests.m index a6daa978..ff084a7b 100644 --- a/Tests/ASCollectionModernDataSourceTests.m +++ b/Tests/ASCollectionModernDataSourceTests.m @@ -71,7 +71,7 @@ - (void)tearDown { - [collectionNode waitUntilAllUpdatesAreCommitted]; + [collectionNode waitUntilAllUpdatesAreProcessed]; [super tearDown]; } diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 00cf58d2..f3567b43 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -260,7 +260,7 @@ [window makeKeyAndVisible]; [testController.collectionNode reloadData]; - [testController.collectionNode waitUntilAllUpdatesAreCommitted]; + [testController.collectionNode waitUntilAllUpdatesAreProcessed]; [testController.collectionView layoutIfNeeded]; NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; @@ -397,7 +397,7 @@ window.rootViewController = testController;\ \ [cn reloadData];\ - [cn waitUntilAllUpdatesAreCommitted]; \ + [cn waitUntilAllUpdatesAreProcessed]; \ [testController.collectionView layoutIfNeeded]; - (void)testThatSubmittingAValidInsertDoesNotThrowAnException @@ -620,7 +620,7 @@ [window makeKeyAndVisible]; for (NSInteger i = 0; i < 2; i++) { - // NOTE: waitUntilAllUpdatesAreCommitted or reloadDataImmediately is not sufficient here!! + // NOTE: waitUntilAllUpdatesAreProcessed or reloadDataImmediately is not sufficient here!! XCTestExpectation *done = [self expectationWithDescription:[NSString stringWithFormat:@"Reload #%td complete", i]]; [cn reloadDataWithCompletion:^{ [done fulfill]; @@ -755,7 +755,7 @@ del.sectionGeneration++; [cn reloadData]; - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; NSInteger sectionCount = del->_itemCounts.size(); for (NSInteger section = 0; section < sectionCount; section++) { @@ -857,7 +857,7 @@ [window layoutIfNeeded]; ASCollectionNode *cn = testController.collectionNode; - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; [cn.view layoutIfNeeded]; ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; XCTAssertTrue(node.visible); @@ -880,7 +880,7 @@ [window layoutIfNeeded]; ASCollectionNode *cn = testController.collectionNode; - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; XCTAssertGreaterThan(cn.bounds.size.height, cn.view.contentSize.height, @"Expected initial data not to fill collection view area."); __block NSUInteger batchFetchCount = 0; @@ -926,7 +926,7 @@ [window layoutIfNeeded]; ASCollectionNode *cn = testController.collectionNode; - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; __block NSUInteger batchFetchCount = 0; XCTestExpectation *e = [self expectationWithDescription:@"Batch fetching completed"]; @@ -1020,7 +1020,7 @@ [view layoutIfNeeded]; // Wait for ASDK reload to finish - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; // Force UIKit to read updated data & range controller to update and account for it [cn.view layoutIfNeeded]; [self waitForExpectationsWithTimeout:60 handler:nil]; @@ -1050,8 +1050,17 @@ // Trigger the initial reload to start [window layoutIfNeeded]; + // Test the APIs that monitor ASCollectionNode update handling + XCTAssertTrue(cn.isProcessingUpdates, @"ASCollectionNode should still be processing updates after initial layoutIfNeeded call (reloadData)"); + [cn onDidFinishProcessingUpdates:^{ + XCTAssertTrue(!cn.isProcessingUpdates, @"ASCollectionNode should no longer be processing updates inside -onDidFinishProcessingUpdates: block"); + }]; + // Wait for ASDK reload to finish - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; + + XCTAssertTrue(!cn.isProcessingUpdates, @"ASCollectionNode should no longer be processing updates after -wait call"); + // Force UIKit to read updated data & range controller to update and account for it [cn.view layoutIfNeeded]; @@ -1093,7 +1102,7 @@ traitCollection.containerSize = screenBounds.size; cn.primitiveTraitCollection = traitCollection; - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; [cn.view layoutIfNeeded]; // Assert that the new trait collection is picked up by all cell nodes, including ones that were not allocated but are forced to allocate now @@ -1124,7 +1133,7 @@ [window makeKeyAndVisible]; [window layoutIfNeeded]; - [cn waitUntilAllUpdatesAreCommitted]; + [cn waitUntilAllUpdatesAreProcessed]; for (NSInteger i = 0; i < itemCount; i++) { ASTextCellNodeWithSetSelectedCounter *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; XCTAssert(node.automaticallyManagesSubnodes, @"Expected test cell node to use automatic subnode management. Can modify the test with a different class if needed."); diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index e7388ccf..2bf79364 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -610,7 +610,7 @@ [UITableView as_recordEditingCallsIntoArray:selectors]; XCTAssertGreaterThan(node.numberOfSections, 0); - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; XCTAssertGreaterThan(node.view.numberOfSections, 0); // The first reloadData call helps prevent UITableView from calling it multiple times while ASDataController is working. @@ -635,13 +635,13 @@ // Load initial data. XCTAssertGreaterThan(node.numberOfSections, 0); - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; XCTAssertGreaterThan(node.view.numberOfSections, 0); // Reload data. [UITableView as_recordEditingCallsIntoArray:selectors]; [node reloadData]; - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; // Assert that the beginning of the call pattern is correct. // There is currently noise that comes after that we will allow for this test. @@ -668,7 +668,7 @@ // Trigger data load BEFORE first layout pass, to ensure constrained size is correct. XCTAssertGreaterThan(node.numberOfSections, 0); - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; ASSizeRange expectedSizeRange = ASSizeRangeMake(CGSizeMake(cellWidth, 0)); expectedSizeRange.max.height = CGFLOAT_MAX; @@ -703,7 +703,7 @@ // So we need to force a new layout pass so that the table will pick up a new constrained size and apply to its node. [node setNeedsLayout]; [node.view layoutIfNeeded]; - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; UITableViewCell *cell = [node.view cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; XCTAssertNotNil(cell); @@ -758,7 +758,7 @@ [window makeKeyAndVisible]; [window layoutIfNeeded]; - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; XCTAssertEqual(node.view.numberOfSections, NumberOfSections); ASXCTAssertEqualRects(CGRectMake(0, 32, 375, 44), [node rectForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]], @"This text requires very specific geometry. The rect for the first row should match up."); @@ -812,7 +812,7 @@ node.dataSource = ds; [node.view layoutIfNeeded]; - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; CGFloat rowHeight = [node.view rectForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].size.height; // Scroll to row (0,1) + 10pt node.contentOffset = CGPointMake(0, rowHeight + 10); @@ -825,7 +825,7 @@ [node deleteRowsAtIndexPaths:@[ [NSIndexPath indexPathForItem:0 inSection:i]] withRowAnimation:UITableViewRowAnimationAutomatic]; } } completion:nil]; - [node waitUntilAllUpdatesAreCommitted]; + [node waitUntilAllUpdatesAreProcessed]; // Now that row (0,0) is deleted, we should have slid up to be at just 10 // i.e. we should have subtracted the deleted row height from our content offset. From 19a9d29aa873edcf5624bed7fa9ee86662da0bfd Mon Sep 17 00:00:00 2001 From: Yan S Date: Mon, 28 Aug 2017 11:49:41 -0700 Subject: [PATCH 08/22] SEP-491 prerequisite: add textViewShouldBeginEditing: to ASEditableTextNodeDelegate (#535) * SEP-491 prerequisite: add textViewShouldBeginEditing: to ASEditableTextNodeDelegate * - added entry to CHANGELOG.md, addressed nit --- CHANGELOG.md | 1 + Source/ASEditableTextNode.h | 7 +++++++ Source/ASEditableTextNode.mm | 14 ++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba86ee3..7deeff47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520) - Change the API for disabling logging from a compiler flag to a runtime C function ASDisableLogging(). [Adlai Holler](https://github.com/Adlai-Holler) [#528](https://github.com/TextureGroup/Texture/pull/528) - Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525) +- [ASEditableTextNode] added -editableTextNodeShouldBeginEditing to ASEditableTextNodeDelegate to mirror the corresponding method from UITextViewDelegate. [Yan S.](https://github.com/yans) [#535](https://github.com/TextureGroup/Texture/pull/535) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASEditableTextNode.h b/Source/ASEditableTextNode.h index db310f06..31dbfe7d 100644 --- a/Source/ASEditableTextNode.h +++ b/Source/ASEditableTextNode.h @@ -158,6 +158,13 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASEditableTextNodeDelegate @optional +/** + @abstract Asks the delegate if editing should begin for the text node. + @param editableTextNode An editable text node. + @discussion YES if editing should begin; NO if editing should not begin -- the default returns YES. + */ +- (BOOL)editableTextNodeShouldBeginEditing:(ASEditableTextNode *)editableTextNode; + /** @abstract Indicates to the delegate that the text node began editing. @param editableTextNode An editable text node. diff --git a/Source/ASEditableTextNode.mm b/Source/ASEditableTextNode.mm index fd35ea5e..d68182ed 100644 --- a/Source/ASEditableTextNode.mm +++ b/Source/ASEditableTextNode.mm @@ -699,6 +699,12 @@ } #pragma mark - UITextView Delegate +- (BOOL)textViewShouldBeginEditing:(UITextView *)textView +{ + // Delegateify. + return [self _delegateShouldBeginEditing]; +} + - (void)textViewDidBeginEditing:(UITextView *)textView { // Delegateify. @@ -793,6 +799,14 @@ } #pragma mark - +- (BOOL)_delegateShouldBeginEditing +{ + if ([_delegate respondsToSelector:@selector(editableTextNodeShouldBeginEditing:)]) { + return [_delegate editableTextNodeShouldBeginEditing:self]; + } + return YES; +} + - (void)_delegateDidBeginEditing { if ([_delegate respondsToSelector:@selector(editableTextNodeDidBeginEditing:)]) From 4ba6f451f64c95568f435557d2898820a3e07ec7 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 7 Sep 2017 19:25:42 +0100 Subject: [PATCH 09/22] [Cleanup] Remove deprecated APIs (#529) * Remove preferredFrameSize * Remove -measure: * Remove -measureWithSizeRange: * Remove ASLayoutable * Remove .name * Remove deprecated style forwardings That includes following properties that are declared on ASDisplayNode and ASLayoutSpec: spacingBefore, spacingAfter, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange and layoutPosition. * Remove usesImplicitHierarchyManagement * Remove deprecated range update callbacks: -visibilityDidChange: -visibleStateDidChange: -displayStateDidChange: -loadStateDidChange: * Remove -clearFetchedData * Remove -cancelLayoutTransitionsInProgress * Remve ASDisplayNode+Deprecated.h * Remove ASLayoutRangeTypeRender and ASLayoutRangeTypeFetchData * Remove -[ASTableView clearContents] * Remove reloadDataImmediately * Remove ASStaticLayoutSpec * Remove ASDimensionDeprecated * Remove optional -pagerNode:constrainedSizeForNodeAtIndex: delegate method in ASPagerDelegate * Remove suppressesInvalidCollectionUpdateExceptions * Remove -[ASCollectionViewLayoutInspector initWithCollectionView] * Remove ASVideoPlayerNode.loadAssetWhenNodeBecomesVisible * Update CHANGELOG * Update license of ASLayoutSpecTests.m * Update examples/PagerNode * Remove ASEnvironmentTraitCollection * Remove -ASViewController.nodeConstrainedSize * More on removing ASLayoutable --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 -- CHANGELOG.md | 1 + Source/ASCollectionNode.h | 10 -- Source/ASCollectionNode.mm | 7 - Source/ASCollectionView.h | 8 - Source/ASDisplayNode+Beta.h | 13 -- Source/ASDisplayNode+Deprecated.h | 142 ------------------ Source/ASDisplayNode+Layout.mm | 13 +- Source/ASDisplayNode+Subclasses.h | 8 +- Source/ASDisplayNode.h | 2 +- Source/ASDisplayNode.mm | 134 +---------------- Source/ASPagerNode.h | 12 -- Source/ASPagerNode.m | 18 --- Source/ASTableView.h | 14 -- Source/ASTableView.mm | 7 - Source/ASVideoPlayerNode.h | 8 - Source/ASVideoPlayerNode.mm | 22 --- Source/ASViewController.h | 13 -- Source/ASViewController.mm | 6 - Source/AsyncDisplayKit.h | 2 - .../Details/ASCollectionViewLayoutInspector.h | 3 - .../Details/ASCollectionViewLayoutInspector.m | 7 - Source/Details/ASLayoutRangeType.h | 3 - Source/Details/ASTraitCollection.h | 21 --- Source/Layout/ASAbsoluteLayoutElement.h | 6 - Source/Layout/ASAbsoluteLayoutSpec.h | 9 -- Source/Layout/ASAbsoluteLayoutSpec.mm | 15 -- Source/Layout/ASDimensionDeprecated.h | 102 ------------- Source/Layout/ASDimensionDeprecated.mm | 102 ------------- Source/Layout/ASLayout.h | 17 --- Source/Layout/ASLayout.mm | 24 --- Source/Layout/ASLayoutElement.h | 16 -- Source/Layout/ASLayoutElement.mm | 18 --- Source/Layout/ASLayoutElementPrivate.h | 136 ----------------- Source/Layout/ASLayoutSpec.h | 6 - Source/Layout/ASLayoutSpec.mm | 20 --- Source/Private/ASDisplayNodeInternal.h | 2 - Source/Private/ASTableView+Undeprecated.h | 8 - .../Layout/ASStackUnpositionedLayout.mm | 2 +- .../Private/_ASCollectionGalleryLayoutItem.mm | 1 - Source/Private/_ASHierarchyChangeSet.mm | 14 +- Tests/ASCollectionViewTests.mm | 2 +- Tests/ASDisplayNodeLayoutTests.mm | 2 +- Tests/ASDisplayNodeTests.mm | 22 --- Tests/ASLayoutSpecTests.m | 15 +- Tests/ASTableViewTests.mm | 3 +- Tests/ASTableViewThrashTests.m | 3 +- examples/PagerNode/Sample/PageNode.m | 26 ++-- 48 files changed, 42 insertions(+), 1015 deletions(-) delete mode 100644 Source/ASDisplayNode+Deprecated.h delete mode 100644 Source/Layout/ASDimensionDeprecated.h delete mode 100644 Source/Layout/ASDimensionDeprecated.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index c02b9fd5..41389f72 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -106,7 +106,6 @@ 636EA1A41C7FF4EC00EE152F /* NSArray+Diffing.m in Sources */ = {isa = PBXBuildFile; fileRef = DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */; }; 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */ = {isa = PBXBuildFile; fileRef = AEB7B0191C5962EA00662EF4 /* ASDefaultPlayButton.m */; }; 680346941CE4052A0009FEB4 /* ASNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; 68355B341CB579B9001D4E68 /* ASImageNode+AnimatedImage.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */; }; 68355B3E1CB57A60001D4E68 /* ASPINRemoteImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */; }; 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */ = {isa = PBXBuildFile; fileRef = 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */; }; @@ -129,8 +128,6 @@ 6907C25A1DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */; }; 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */; }; 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C35631E055C7B00069B91 /* ASDimensionInternal.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */; }; - 690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */; settings = {ATTRIBUTES = (Public, ); }; }; 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; 690ED5961E36D118000627C0 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */; settings = {ATTRIBUTES = (Private, ); }; }; 690ED5981E36D118000627C0 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */; }; @@ -627,7 +624,6 @@ 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableLayoutController.m; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; - 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Deprecated.h"; sourceTree = ""; }; 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = ""; }; 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPINRemoteImageDownloader.m; sourceTree = ""; }; 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageContainerProtocolCategories.h; sourceTree = ""; }; @@ -652,8 +648,6 @@ 6907C2571DC4ECFE00374C66 /* ASObjectDescriptionHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASObjectDescriptionHelpers.m; sourceTree = ""; }; 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimensionInternal.mm; sourceTree = ""; }; 690C35631E055C7B00069B91 /* ASDimensionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDimensionInternal.h; sourceTree = ""; }; - 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimensionDeprecated.mm; sourceTree = ""; }; - 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDimensionDeprecated.h; sourceTree = ""; }; 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementStylePrivate.h; sourceTree = ""; }; 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+tvOS.h"; sourceTree = ""; }; 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASControlNode+tvOS.m"; sourceTree = ""; }; @@ -1081,7 +1075,6 @@ 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */, 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */, - 683489271D70DE3400327501 /* ASDisplayNode+Deprecated.h */, 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */, CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */, @@ -1524,8 +1517,6 @@ ACF6ED041B17843500DA7C62 /* ASCenterLayoutSpec.mm */, ACF6ED071B17843500DA7C62 /* ASDimension.h */, ACF6ED081B17843500DA7C62 /* ASDimension.mm */, - 690C356A1E05680300069B91 /* ASDimensionDeprecated.h */, - 690C35651E0567C600069B91 /* ASDimensionDeprecated.mm */, 690C35631E055C7B00069B91 /* ASDimensionInternal.h */, 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */, ACF6ED091B17843500DA7C62 /* ASInsetLayoutSpec.h */, @@ -1731,8 +1722,6 @@ 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */, 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */, 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */, - 690C356B1E05680300069B91 /* ASDimensionDeprecated.h in Headers */, - 683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */, 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */, 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */, 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */, @@ -2204,7 +2193,6 @@ CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, - 690C35671E0567C600069B91 /* ASDimensionDeprecated.mm in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, CCA282B91E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.m in Sources */, 636EA1A51C7FF4EF00EE152F /* ASDefaultPlayButton.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 7deeff47..d3d6a3d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Change the API for disabling logging from a compiler flag to a runtime C function ASDisableLogging(). [Adlai Holler](https://github.com/Adlai-Holler) [#528](https://github.com/TextureGroup/Texture/pull/528) - Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525) - [ASEditableTextNode] added -editableTextNodeShouldBeginEditing to ASEditableTextNodeDelegate to mirror the corresponding method from UITextViewDelegate. [Yan S.](https://github.com/yans) [#535](https://github.com/TextureGroup/Texture/pull/535) +- [Breaking] Remove APIs that have been deprecated since 2.0 and/or for at least 6 months [Huy Nguyen](https://github.com/nguyenhuy) [#529](https://github.com/TextureGroup/Texture/pull/529) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 352867e9..68e5d752 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -524,16 +524,6 @@ NS_ASSUME_NONNULL_BEGIN @interface ASCollectionNode (Deprecated) -/** - * Reload everything from scratch, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UICollectionView's version. - * - * @deprecated This method is deprecated in 2.0. Use @c reloadDataWithCompletion: and - * then @c waitUntilAllUpdatesAreProcessed instead. - */ -- (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use -reloadData / -reloadDataWithCompletion: followed by -waitUntilAllUpdatesAreProcessed instead."); - - (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("This method has been renamed to -waitUntilAllUpdatesAreProcessed."); @end diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 5bb12bef..e5cde4c8 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -753,13 +753,6 @@ [self reloadDataWithCompletion:nil]; } -- (void)reloadDataImmediately -{ - ASDisplayNodeAssertMainThread(); - [self reloadData]; - [self waitUntilAllUpdatesAreProcessed]; -} - - (void)relayoutItems { ASDisplayNodeAssertMainThread(); diff --git a/Source/ASCollectionView.h b/Source/ASCollectionView.h index 2a1eb185..2c1f8798 100644 --- a/Source/ASCollectionView.h +++ b/Source/ASCollectionView.h @@ -280,14 +280,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)reloadData AS_UNAVAILABLE("Use ASCollectionNode method instead."); -/** - * Reload everything from scratch entirely on the main thread, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UICollectionView's version and will block the main thread - * while all the cells load. - */ -- (void)reloadDataImmediately AS_UNAVAILABLE("Use ASCollectionNode method instead."); - /** * Triggers a relayout of all nodes. * diff --git a/Source/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h index 8e413a5a..e8b08a9a 100644 --- a/Source/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -61,19 +61,6 @@ typedef struct { @interface ASDisplayNode (Beta) -/** - * ASTableView and ASCollectionView now throw exceptions on invalid updates - * like their UIKit counterparts. If YES, these classes will log messages - * on invalid updates rather than throwing exceptions. - * - * Note that even if AsyncDisplayKit's exception is suppressed, the app may still crash - * as it proceeds with an invalid update. - * - * This property defaults to NO. It will be removed in a future release. - */ -+ (BOOL)suppressesInvalidCollectionUpdateExceptions AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Collection update exceptions are thrown if assertions are enabled."); -+ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses ASDISPLAYNODE_DEPRECATED_MSG("Collection update exceptions are thrown if assertions are enabled."); - /** * @abstract Recursively ensures node and all subnodes are displayed. * @see Full documentation in ASDisplayNode+FrameworkPrivate.h diff --git a/Source/ASDisplayNode+Deprecated.h b/Source/ASDisplayNode+Deprecated.h deleted file mode 100644 index 696169fa..00000000 --- a/Source/ASDisplayNode+Deprecated.h +++ /dev/null @@ -1,142 +0,0 @@ -// -// ASDisplayNode+Deprecated.h -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once - -#import - -@interface ASDisplayNode (Deprecated) - -/** - * @abstract The name of this node, which will be displayed in `description`. The default value is nil. - * - * @deprecated Deprecated in version 2.0: Use .debugName instead. This value will display in - * results of the -asciiArtString method (@see ASLayoutElementAsciiArtProtocol). - */ -@property (nullable, nonatomic, copy) NSString *name ASDISPLAYNODE_DEPRECATED_MSG("Use .debugName instead."); - -/** - * @abstract Provides a default intrinsic content size for calculateSizeThatFits:. This is useful when laying out - * a node that either has no intrinsic content size or should be laid out at a different size than its intrinsic content - * size. For example, this property could be set on an ASImageNode to display at a size different from the underlying - * image size. - * - * @return Try to create a CGSize for preferredFrameSize of this node from the width and height property of this node. It will return CGSizeZero if width and height dimensions are not of type ASDimensionUnitPoints. - * - * @deprecated Deprecated in version 2.0: Just calls through to set the height and width property of the node. Convert to use sizing properties instead: height, minHeight, maxHeight, width, minWidth, maxWidth. - */ -@property (nonatomic, assign, readwrite) CGSize preferredFrameSize ASDISPLAYNODE_DEPRECATED_MSG("Use .style.preferredSize instead OR set individual values with .style.height and .style.width."); - -/** - * @abstract Asks the node to measure and return the size that best fits its subnodes. - * - * @param constrainedSize The maximum size the receiver should fit in. - * - * @return A new size that fits the receiver's subviews. - * - * @discussion Though this method does not set the bounds of the view, it does have side effects--caching both the - * constraint and the result. - * - * @warning Subclasses must not override this; it calls -measureWithSizeRange: with zero min size. - * -measureWithSizeRange: caches results from -calculateLayoutThatFits:. Calling this method may - * be expensive if result is not cached. - * - * @see measureWithSizeRange: - * @see [ASDisplayNode(Subclassing) calculateLayoutThatFits:] - * - * @deprecated Deprecated in version 2.0: Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout - */ -- (CGSize)measure:(CGSize)constrainedSize ASDISPLAYNODE_DEPRECATED_MSG("Use layoutThatFits: with a constrained size of (CGSizeZero, constrainedSize) and call size on the returned ASLayout."); - -ASLayoutElementStyleForwardingDeclaration - -/** - * @abstract Called whenever the visiblity of the node changed. - * - * @discussion Subclasses may use this to monitor when they become visible. - * - * @deprecated @see didEnterVisibleState @see didExitVisibleState - */ -- (void)visibilityDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead."); - -/** - * @abstract Called whenever the visiblity of the node changed. - * - * @discussion Subclasses may use this to monitor when they become visible. - * - * @deprecated @see didEnterVisibleState @see didExitVisibleState - */ -- (void)visibleStateDidChange:(BOOL)isVisible ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterVisibleState / -didExitVisibleState instead."); - -/** - * @abstract Called whenever the the node has entered or exited the display state. - * - * @discussion Subclasses may use this to monitor when a node should be rendering its content. - * - * @note This method can be called from any thread and should therefore be thread safe. - * - * @deprecated @see didEnterDisplayState @see didExitDisplayState - */ -- (void)displayStateDidChange:(BOOL)inDisplayState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterDisplayState / -didExitDisplayState instead."); - -/** - * @abstract Called whenever the the node has entered or left the load state. - * - * @discussion Subclasses may use this to monitor data for a node should be loaded, either from a local or remote source. - * - * @note This method can be called from any thread and should therefore be thread safe. - * - * @deprecated @see didEnterPreloadState @see didExitPreloadState - */ -- (void)loadStateDidChange:(BOOL)inLoadState ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState / -didExitPreloadState instead."); - -/** - * @abstract Cancels all performing layout transitions. Can be called on any thread. - * - * @deprecated Deprecated in version 2.0: Use cancelLayoutTransition - */ -- (void)cancelLayoutTransitionsInProgress ASDISPLAYNODE_DEPRECATED_MSG("Use -cancelLayoutTransition instead."); - -/** - * @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_MSG("Set .automaticallyManagesSubnodes instead."); - -/** - * @abstract Indicates that the node should fetch any external data, such as images. - * - * @discussion Subclasses may override this method to be notified when they should begin to preload. Fetching - * should be done asynchronously. The node is also responsible for managing the memory of any data. - * The data may be remote and accessed via the network, but could also be a local database query. - */ -- (void)fetchData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didEnterPreloadState instead."); - -/** - * Provides an opportunity to clear any fetched data (e.g. remote / network or database-queried) on the current node. - * - * @discussion This will not clear data recursively for all subnodes. Either call -recursivelyClearPreloadedData or - * selectively clear fetched data. - */ -- (void)clearFetchedData ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use -didExitPreloadState instead."); - -@end diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index 5674997d..6727cd23 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -57,15 +57,6 @@ #pragma mark Measurement Pass - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // For now we just call the deprecated measureWithSizeRange: method to not break old API - return [self measureWithSizeRange:constrainedSize]; -#pragma clang diagnostic pop -} - -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize { return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max]; } @@ -127,8 +118,6 @@ ASLayoutElementStyleExtensibilityForwarding return [ASTraitCollection traitCollectionWithASPrimitiveTraitCollection:self.primitiveTraitCollection]; } -ASPrimitiveTraitCollectionDeprecatedImplementation - #pragma mark - ASLayoutElementAsciiArtProtocol - (NSString *)asciiArtString @@ -938,7 +927,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation // Grab lock after calling out to subclass ASDN::MutexLocker l(__instanceLock__); - // We generate placeholders at measureWithSizeRange: time so that a node is guaranteed to have a placeholder ready to go. + // We generate placeholders at -layoutThatFits: time so that a node is guaranteed to have a placeholder ready to go. // This is also because measurement is usually asynchronous, but placeholders need to be set up synchronously. // First measurement is guaranteed to be before the node is onscreen, so we can create the image async. but still have it appear sync. if (_placeholderEnabled && !_placeholderImage && [self _locked_displaysAsynchronously]) { diff --git a/Source/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h index b506124d..9cc98aab 100644 --- a/Source/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -116,12 +116,12 @@ NS_ASSUME_NONNULL_BEGIN * @discussion For node subclasses that implement manual layout (e.g., they have a custom -layout method), * calculatedLayout may be accessed on subnodes to retrieved cached information about their size. * This allows -layout to be very fast, saving time on the main thread. - * Note: .calculatedLayout will only be set for nodes that have had -measure: called on them. - * For manual layout, make sure you call -measure: in your implementation of -calculateSizeThatFits:. + * Note: .calculatedLayout will only be set for nodes that have had -layoutThatFits: called on them. + * For manual layout, make sure you call -layoutThatFits: in your implementation of -calculateSizeThatFits:. * * For node subclasses that use automatic layout (e.g., they implement -layoutSpecThatFits:), * it is typically not necessary to use .calculatedLayout at any point. For these nodes, - * the ASLayoutSpec implementation will automatically call -measureWithSizeRange: on all of the subnodes, + * the ASLayoutSpec implementation will automatically call -layoutThatFits: on all of the subnodes, * and the ASDisplayNode base class implementation of -layout will automatically make use of .calculatedLayout on the subnodes. * * @return Layout that wraps calculated size returned by -calculateSizeThatFits: (in manual layout mode), @@ -183,7 +183,7 @@ NS_ASSUME_NONNULL_BEGIN * or -calculateSizeThatFits:, whichever method is overriden. Subclasses rarely need to override this method, * override -layoutSpecThatFits: or -calculateSizeThatFits: instead. * - * @note This method should not be called directly outside of ASDisplayNode; use -measure: or -calculatedLayout instead. + * @note This method should not be called directly outside of ASDisplayNode; use -layoutThatFits: or -calculatedLayout instead. */ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize; diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 24347d09..c221e3c6 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -785,7 +785,7 @@ extern NSInteger const ASDefaultDrawingPriority; * @abstract Return the calculated size. * * @discussion Ideal for use by subclasses in -layout, having already prompted their subnodes to calculate their size by - * calling -measure: on them in -calculateLayoutThatFits. + * calling -layoutThatFits: on them in -calculateLayoutThatFits. * * @return Size already calculated by -calculateLayoutThatFits:. * diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index e1ce6f8b..f91b3384 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -20,7 +20,6 @@ #import #import #import -#import #import #import #import @@ -77,19 +76,8 @@ NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; @synthesize threadSafeBounds = _threadSafeBounds; -static BOOL suppressesInvalidCollectionUpdateExceptions = NO; static std::atomic_bool storesUnflattenedLayouts = ATOMIC_VAR_INIT(NO); -+ (BOOL)suppressesInvalidCollectionUpdateExceptions -{ - return suppressesInvalidCollectionUpdateExceptions; -} - -+ (void)setSuppressesInvalidCollectionUpdateExceptions:(BOOL)suppresses -{ - suppressesInvalidCollectionUpdateExceptions = suppresses; -} - BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector) { return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector); @@ -182,12 +170,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (ASDisplayNodeSubclassOverridesSelector(c, @selector(calculateSizeThatFits:))) { overrides |= ASDisplayNodeMethodOverrideCalcSizeThatFits; } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(fetchData))) { - overrides |= ASDisplayNodeMethodOverrideFetchData; - } - if (ASDisplayNodeSubclassOverridesSelector(c, @selector(clearFetchedData))) { - overrides |= ASDisplayNodeMethodOverrideClearFetchedData; - } return overrides; } @@ -202,8 +184,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedSize)), @"Subclass %@ must not override calculatedSize method.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(calculatedLayout)), @"Subclass %@ must not override calculatedLayout method.", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measure:)), @"Subclass %@ must not override measure: method", classString); - ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange: method. Instead override calculateLayoutThatFits:", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:)), @"Subclass %@ must not override layoutThatFits: method. Instead override calculateLayoutThatFits:.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(layoutThatFits:parentSize:)), @"Subclass %@ must not override layoutThatFits:parentSize method. Instead override calculateLayoutThatFits:.", classString); ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self, @selector(recursivelyClearContents)), @"Subclass %@ must not override recursivelyClearContents method.", classString); @@ -906,7 +886,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) if (CGRectEqualToRect(bounds, CGRectZero)) { // Performing layout on a zero-bounds view often results in frame calculations // with negative sizes after applying margins, which will cause - // measureWithSizeRange: on subnodes to assert. + // layoutThatFits: on subnodes to assert. as_log_debug(OS_LOG_DISABLED, "Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self); return; } @@ -2935,13 +2915,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); [_interfaceStateDelegate didEnterPreloadState]; - - if (_methodOverrides & ASDisplayNodeMethodOverrideFetchData) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self fetchData]; -#pragma clang diagnostic pop - } } - (void)didExitPreloadState @@ -2949,13 +2922,6 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); [_interfaceStateDelegate didExitPreloadState]; - - if (_methodOverrides & ASDisplayNodeMethodOverrideClearFetchedData) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [self clearFetchedData]; -#pragma clang diagnostic pop - } } - (void)clearContents @@ -3504,101 +3470,3 @@ static const char *ASDisplayNodeAssociatedNodeKey = "ASAssociatedNode"; } @end - -#pragma mark - ASDisplayNode (Deprecated) - -@implementation ASDisplayNode (Deprecated) - -- (NSString *)name -{ - return self.debugName; -} - -- (void)setName:(NSString *)name -{ - self.debugName = name; -} - -- (void)setPreferredFrameSize:(CGSize)preferredFrameSize -{ - // Deprecated preferredFrameSize just calls through to set width and height - self.style.preferredSize = preferredFrameSize; - [self setNeedsLayout]; -} - -- (CGSize)preferredFrameSize -{ - ASLayoutSize size = self.style.preferredLayoutSize; - BOOL isPoints = (size.width.unit == ASDimensionUnitPoints && size.height.unit == ASDimensionUnitPoints); - return isPoints ? CGSizeMake(size.width.value, size.height.value) : CGSizeZero; -} - -- (BOOL)usesImplicitHierarchyManagement -{ - return self.automaticallyManagesSubnodes; -} - -- (void)setUsesImplicitHierarchyManagement:(BOOL)enabled -{ - self.automaticallyManagesSubnodes = enabled; -} - -- (CGSize)measure:(CGSize)constrainedSize -{ - return [self layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)].size; -} - -ASLayoutElementStyleForwarding - -- (void)visibilityDidChange:(BOOL)isVisible -{ - if (isVisible) { - [self didEnterVisibleState]; - } else { - [self didExitVisibleState]; - } -} - -- (void)visibleStateDidChange:(BOOL)isVisible -{ - if (isVisible) { - [self didEnterVisibleState]; - } else { - [self didExitVisibleState]; - } -} - -- (void)displayStateDidChange:(BOOL)inDisplayState -{ - if (inDisplayState) { - [self didEnterVisibleState]; - } else { - [self didExitVisibleState]; - } -} - -- (void)loadStateDidChange:(BOOL)inLoadState -{ - if (inLoadState) { - [self didEnterPreloadState]; - } else { - [self didExitPreloadState]; - } -} - -- (void)fetchData -{ - // subclass override -} - -- (void)clearFetchedData -{ - // subclass override -} - -- (void)cancelLayoutTransitionsInProgress -{ - [self cancelLayoutTransition]; -} - -@end diff --git a/Source/ASPagerNode.h b/Source/ASPagerNode.h index 51e57898..2b489659 100644 --- a/Source/ASPagerNode.h +++ b/Source/ASPagerNode.h @@ -61,18 +61,6 @@ NS_ASSUME_NONNULL_BEGIN @end @protocol ASPagerDelegate - -@optional - -/** - * Provides the constrained size range for measuring the node at the index. - * - * @param pagerNode The sender. - * @param index The index of the node. - * @return A constrained size range for layout the node at this index. - */ -- (ASSizeRange)pagerNode:(ASPagerNode *)pagerNode constrainedSizeForNodeAtIndex:(NSInteger)index ASDISPLAYNODE_DEPRECATED_MSG("Pages in a pager node should be the exact size of the collection node (default behavior)."); - @end /** diff --git a/Source/ASPagerNode.m b/Source/ASPagerNode.m index 86062f66..1ef893d9 100644 --- a/Source/ASPagerNode.m +++ b/Source/ASPagerNode.m @@ -39,9 +39,6 @@ } _pagerDataSourceFlags; __weak id _pagerDelegate; - struct { - unsigned constrainedSizeForNode:1; - } _pagerDelegateFlags; ASPagerNodeProxy *_proxyDelegate; } @@ -181,13 +178,6 @@ - (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (_pagerDelegateFlags.constrainedSizeForNode) { - return [_pagerDelegate pagerNode:self constrainedSizeForNodeAtIndex:indexPath.item]; - } -#pragma clang diagnostic pop - return ASSizeRangeMake([self pageSize]); } @@ -220,15 +210,7 @@ { if (delegate != _pagerDelegate) { _pagerDelegate = delegate; - - if (delegate == nil) { - memset(&_pagerDelegateFlags, 0, sizeof(_pagerDelegateFlags)); - } else { - _pagerDelegateFlags.constrainedSizeForNode = [_pagerDelegate respondsToSelector:@selector(pagerNode:constrainedSizeForNodeAtIndex:)]; - } - _proxyDelegate = delegate ? [[ASPagerNodeProxy alloc] initWithTarget:delegate interceptor:self] : nil; - super.delegate = (id )_proxyDelegate; } } diff --git a/Source/ASTableView.h b/Source/ASTableView.h index 0a3d07ec..04e1dbd1 100644 --- a/Source/ASTableView.h +++ b/Source/ASTableView.h @@ -184,14 +184,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)reloadData ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); -/** - * Reload everything from scratch entirely on the main thread, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version and will block the main thread while - * all the cells load. - */ -- (void)reloadDataImmediately ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode's reloadDataWithCompletion: followed by ASTableNode's -waitUntilAllUpdatesAreCommitted instead."); - /** * Triggers a relayout of all nodes. * @@ -241,12 +233,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); -/// Deprecated in 2.0. You should not call this method. -- (void)clearContents ASDISPLAYNODE_DEPRECATED_MSG("You should not call this method directly. Intead, rely on the Interstate State callback methods."); - -/// Deprecated in 2.0. You should not call this method. -- (void)clearFetchedData ASDISPLAYNODE_DEPRECATED_MSG("You should not call this method directly. Intead, rely on the Interstate State callback methods."); - - (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); @end diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 8f41cffa..4606726f 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -544,13 +544,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; [self reloadDataWithCompletion:nil]; } -- (void)reloadDataImmediately -{ - ASDisplayNodeAssertMainThread(); - [self reloadData]; - [_dataController waitUntilAllUpdatesAreProcessed]; -} - - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated { if ([self validateIndexPath:indexPath]) { diff --git a/Source/ASVideoPlayerNode.h b/Source/ASVideoPlayerNode.h index e68b15c5..f878c152 100644 --- a/Source/ASVideoPlayerNode.h +++ b/Source/ASVideoPlayerNode.h @@ -44,8 +44,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL controlsDisabled; -@property (nonatomic, assign, readonly) BOOL loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state. This flag does nothing."); - #pragma mark - ASVideoNode property proxy /** * When shouldAutoplay is set to true, a video node will play when it has both loaded and entered the "visible" interfaceState. @@ -79,12 +77,6 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithAsset:(AVAsset *)asset; - (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix; -#pragma mark Lifecycle Deprecated -- (instancetype)initWithUrl:(NSURL *)url ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); -- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); -- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible ASDISPLAYNODE_DEPRECATED_MSG("Asset is always loaded when this node enters preload state, therefore loadAssetWhenNodeBecomesVisible is deprecated and not used anymore."); - #pragma mark - Public API - (void)seekToTime:(CGFloat)percentComplete; - (void)play; diff --git a/Source/ASVideoPlayerNode.mm b/Source/ASVideoPlayerNode.mm index ca05c058..e6f766b4 100644 --- a/Source/ASVideoPlayerNode.mm +++ b/Source/ASVideoPlayerNode.mm @@ -154,28 +154,6 @@ static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; [self addSubnode:_videoNode]; } -#pragma mark Deprecated - -- (instancetype)initWithUrl:(NSURL *)url -{ - return [self initWithURL:url]; -} - -- (instancetype)initWithUrl:(NSURL *)url loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible -{ - return [self initWithURL:url]; -} - -- (instancetype)initWithAsset:(AVAsset *)asset loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible -{ - return [self initWithAsset:asset]; -} - -- (instancetype)initWithAsset:(AVAsset *)asset videoComposition:(AVVideoComposition *)videoComposition audioMix:(AVAudioMix *)audioMix loadAssetWhenNodeBecomesVisible:(BOOL)loadAssetWhenNodeBecomesVisible -{ - return [self initWithAsset:asset videoComposition:videoComposition audioMix:audioMix]; -} - #pragma mark - Setter / Getter - (void)setAssetURL:(NSURL *)assetURL diff --git a/Source/ASViewController.h b/Source/ASViewController.h index c2e71a27..bdd343b3 100644 --- a/Source/ASViewController.h +++ b/Source/ASViewController.h @@ -92,17 +92,4 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASViewController (Deprecated) - -/** - * The constrained size used to measure the backing node. - * - * @discussion Defaults to providing a size range that uses the view controller view's bounds as - * both the min and max definitions. Override this method to provide a custom size range to the - * backing node. - */ -- (ASSizeRange)nodeConstrainedSize AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED_MSG("Set the size directly to the view's frame"); - -@end - NS_ASSUME_NONNULL_END diff --git a/Source/ASViewController.mm b/Source/ASViewController.mm index d4dab1a6..2826fae9 100644 --- a/Source/ASViewController.mm +++ b/Source/ASViewController.mm @@ -146,12 +146,9 @@ [self propagateNewTraitCollection:traitCollection]; }]; } else { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // Call layoutThatFits: to let the node prepare for a layout that will happen shortly in the layout pass of the view. // If the node's constrained size didn't change between the last layout pass it's a no-op [_node layoutThatFits:[self nodeConstrainedSize]]; -#pragma clang diagnostic pop } } @@ -296,13 +293,10 @@ ASVisibilityDepthImplementation; ASTraitCollectionPropagateDown(child, traitCollection); } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // Once we've propagated all the traits, layout this node. // Remeasure the node with the latest constrained size – old constrained size may be incorrect. as_activity_scope_verbose(as_activity_create("Layout ASViewController node with new traits", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); [_node layoutThatFits:[self nodeConstrainedSize]]; -#pragma clang diagnostic pop } } diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 73a5288b..b1903945 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -75,7 +75,6 @@ #import #import #import -#import #import #import #import @@ -125,7 +124,6 @@ #import #import -#import #import #import diff --git a/Source/Details/ASCollectionViewLayoutInspector.h b/Source/Details/ASCollectionViewLayoutInspector.h index 3f6302fe..ba2aadf2 100644 --- a/Source/Details/ASCollectionViewLayoutInspector.h +++ b/Source/Details/ASCollectionViewLayoutInspector.h @@ -86,9 +86,6 @@ extern ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *colle * @warning This class is not meant to be subclassed and will be restricted in the future. */ @interface ASCollectionViewLayoutInspector : NSObject - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView ASDISPLAYNODE_DEPRECATED_MSG("Use -init instead."); - @end NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASCollectionViewLayoutInspector.m b/Source/Details/ASCollectionViewLayoutInspector.m index 26100cec..9b8a302d 100644 --- a/Source/Details/ASCollectionViewLayoutInspector.m +++ b/Source/Details/ASCollectionViewLayoutInspector.m @@ -47,13 +47,6 @@ ASSizeRange NodeConstrainedSizeForScrollDirection(ASCollectionView *collectionVi } _delegateFlags; } -#pragma mark Lifecycle - -- (instancetype)initWithCollectionView:(ASCollectionView *)collectionView -{ - return [self init]; -} - #pragma mark ASCollectionViewLayoutInspecting - (void)didChangeCollectionViewDelegate:(id)delegate diff --git a/Source/Details/ASLayoutRangeType.h b/Source/Details/ASLayoutRangeType.h index 45e16354..2f2be520 100644 --- a/Source/Details/ASLayoutRangeType.h +++ b/Source/Details/ASLayoutRangeType.h @@ -74,6 +74,3 @@ typedef NS_ENUM(NSInteger, ASLayoutRangeType) { }; static NSInteger const ASLayoutRangeTypeCount = 2; - -#define ASLayoutRangeTypeRender ASLayoutRangeTypeDisplay -#define ASLayoutRangeTypeFetchData ASLayoutRangeTypePreload diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 760919c0..fdff5c0b 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -66,10 +66,6 @@ extern NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollecti */ extern void ASTraitCollectionPropagateDown(id element, ASPrimitiveTraitCollection traitCollection); -/// For backward compatibility reasons we redefine the old layout element trait collection struct name -#define ASEnvironmentTraitCollection ASPrimitiveTraitCollection -#define ASEnvironmentTraitCollectionMakeDefault ASPrimitiveTraitCollectionMakeDefault - ASDISPLAYNODE_EXTERN_C_END /** @@ -92,13 +88,6 @@ ASDISPLAYNODE_EXTERN_C_END */ - (ASTraitCollection *)asyncTraitCollection; -/** - * Deprecated and should be replaced by the methods from above - */ -- (ASEnvironmentTraitCollection)environmentTraitCollection; -- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traitCollection; - - @end #define ASPrimitiveTraitCollectionDefaults \ @@ -111,16 +100,6 @@ ASDISPLAYNODE_EXTERN_C_END _primitiveTraitCollection = traitCollection;\ }\ -#define ASPrimitiveTraitCollectionDeprecatedImplementation \ -- (ASEnvironmentTraitCollection)environmentTraitCollection\ -{\ - return self.primitiveTraitCollection;\ -}\ -- (void)setEnvironmentTraitCollection:(ASEnvironmentTraitCollection)traitCollection\ -{\ - [self setPrimitiveTraitCollection:traitCollection];\ -}\ - #define ASLayoutElementCollectionTableSetTraitCollection(lock) \ - (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection\ {\ diff --git a/Source/Layout/ASAbsoluteLayoutElement.h b/Source/Layout/ASAbsoluteLayoutElement.h index 39ce04f1..384830f5 100644 --- a/Source/Layout/ASAbsoluteLayoutElement.h +++ b/Source/Layout/ASAbsoluteLayoutElement.h @@ -16,7 +16,6 @@ // #import -#import NS_ASSUME_NONNULL_BEGIN @@ -30,11 +29,6 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) CGPoint layoutPosition; - -#pragma mark Deprecated - -@property (nonatomic, assign) ASRelativeSizeRange sizeRange ASDISPLAYNODE_DEPRECATED; - @end NS_ASSUME_NONNULL_END diff --git a/Source/Layout/ASAbsoluteLayoutSpec.h b/Source/Layout/ASAbsoluteLayoutSpec.h index 7d218dce..0c250ec6 100644 --- a/Source/Layout/ASAbsoluteLayoutSpec.h +++ b/Source/Layout/ASAbsoluteLayoutSpec.h @@ -50,13 +50,4 @@ NS_ASSUME_NONNULL_BEGIN @end - -#pragma mark - Deprecated - -@interface ASStaticLayoutSpec : ASAbsoluteLayoutSpec - -+ (instancetype)staticLayoutSpecWithChildren:(NSArray> *)children AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED; - -@end - NS_ASSUME_NONNULL_END diff --git a/Source/Layout/ASAbsoluteLayoutSpec.mm b/Source/Layout/ASAbsoluteLayoutSpec.mm index c8d136a5..96ab7a70 100644 --- a/Source/Layout/ASAbsoluteLayoutSpec.mm +++ b/Source/Layout/ASAbsoluteLayoutSpec.mm @@ -107,18 +107,3 @@ @end -#pragma mark - ASStaticLayoutSpec - -@implementation ASStaticLayoutSpec : ASAbsoluteLayoutSpec - -+ (instancetype)staticLayoutSpecWithChildren:(NSArray> *)children -{ - return [self absoluteLayoutSpecWithSizing:ASAbsoluteLayoutSpecSizingSizeToFit children:children]; -} - -- (instancetype)initWithChildren:(NSArray *)children -{ - return [super initWithSizing:ASAbsoluteLayoutSpecSizingSizeToFit children:children]; -} - -@end diff --git a/Source/Layout/ASDimensionDeprecated.h b/Source/Layout/ASDimensionDeprecated.h deleted file mode 100644 index 94483edc..00000000 --- a/Source/Layout/ASDimensionDeprecated.h +++ /dev/null @@ -1,102 +0,0 @@ -// -// ASDimensionDeprecated.h -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#pragma once -#import -#import - -ASDISPLAYNODE_EXTERN_C_BEGIN -NS_ASSUME_NONNULL_BEGIN - -/** - * A dimension relative to constraints to be provided in the future. - * A ASDimension can be one of three types: - * - * "Auto" - This indicated "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. - * - * "Points" - Just a number. It will always resolve to exactly this amount. - * - * "Percent" - Multiplied to a provided parent amount to resolve a final amount. - */ -typedef NS_ENUM(NSInteger, ASRelativeDimensionType) { - /** This indicates "I have no opinion" and may be resolved in whatever way makes most sense given the circumstances. */ - ASRelativeDimensionTypeAuto, - /** Just a number. It will always resolve to exactly this amount. This is the default type. */ - ASRelativeDimensionTypePoints, - /** Multiplied to a provided parent amount to resolve a final amount. */ - ASRelativeDimensionTypeFraction, -}; - -#define ASRelativeDimension ASDimension -#define ASRelativeSize ASLayoutSize -#define ASRelativeDimensionMakeWithPoints ASDimensionMakeWithPoints -#define ASRelativeDimensionMakeWithFraction ASDimensionMakeWithFraction - -/** - * Function is deprecated. Use ASSizeRangeMake instead. - */ -extern AS_WARN_UNUSED_RESULT ASSizeRange ASSizeRangeMakeExactSize(CGSize size) ASDISPLAYNODE_DEPRECATED_MSG("Use ASSizeRangeMake instead."); - -/** - Expresses an inclusive range of relative sizes. Used to provide additional constraint to layout. - Used by ASStaticLayoutSpec. - */ -typedef struct { - ASLayoutSize min; - ASLayoutSize max; -} ASRelativeSizeRange; - -extern ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained; - -#pragma mark - ASRelativeDimension - -extern ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) ASDISPLAYNODE_DEPRECATED; - -#pragma mark - ASRelativeSize - -extern ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) ASDISPLAYNODE_DEPRECATED; - -/** Convenience constructor to provide size in points. */ -extern ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) ASDISPLAYNODE_DEPRECATED; - -/** Convenience constructor to provide size as a fraction. */ -extern ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; - -extern BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) ASDISPLAYNODE_DEPRECATED; - -extern NSString *NSStringFromASRelativeSize(ASLayoutSize size) ASDISPLAYNODE_DEPRECATED; - -#pragma mark - ASRelativeSizeRange - -extern ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) ASDISPLAYNODE_DEPRECATED; - -#pragma mark Convenience constructors to provide an exact size (min == max). -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) ASDISPLAYNODE_DEPRECATED; - -extern ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) ASDISPLAYNODE_DEPRECATED; - -extern BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) ASDISPLAYNODE_DEPRECATED; - -/** Provided a parent size, compute final dimensions for this RelativeSizeRange to arrive at a SizeRange. */ -extern ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, CGSize parentSize) ASDISPLAYNODE_DEPRECATED; - -NS_ASSUME_NONNULL_END -ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Layout/ASDimensionDeprecated.mm b/Source/Layout/ASDimensionDeprecated.mm deleted file mode 100644 index bca453d0..00000000 --- a/Source/Layout/ASDimensionDeprecated.mm +++ /dev/null @@ -1,102 +0,0 @@ -// -// ASDimensionDeprecated.mm -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#import - -ASDimension ASRelativeDimensionMake(ASRelativeDimensionType type, CGFloat value) -{ - if (type == ASRelativeDimensionTypePoints) { - return ASDimensionMakeWithPoints(value); - } else if (type == ASRelativeDimensionTypeFraction) { - return ASDimensionMakeWithFraction(value); - } - - ASDisplayNodeCAssert(NO, @"ASRelativeDimensionMake does not support the given ASRelativeDimensionType"); - return ASDimensionMakeWithPoints(0); -} - -ASSizeRange ASSizeRangeMakeExactSize(CGSize size) -{ - return ASSizeRangeMake(size); -} - -ASRelativeSizeRange const ASRelativeSizeRangeUnconstrained = {}; - -#pragma mark - ASRelativeSize - -ASLayoutSize ASRelativeSizeMake(ASRelativeDimension width, ASRelativeDimension height) -{ - return ASLayoutSizeMake(width, height); -} - -ASLayoutSize ASRelativeSizeMakeWithCGSize(CGSize size) -{ - return ASRelativeSizeMake(ASRelativeDimensionMakeWithPoints(size.width), - ASRelativeDimensionMakeWithPoints(size.height)); -} - -ASLayoutSize ASRelativeSizeMakeWithFraction(CGFloat fraction) -{ - return ASRelativeSizeMake(ASRelativeDimensionMakeWithFraction(fraction), - ASRelativeDimensionMakeWithFraction(fraction)); -} - -BOOL ASRelativeSizeEqualToRelativeSize(ASLayoutSize lhs, ASLayoutSize rhs) -{ - return ASDimensionEqualToDimension(lhs.width, rhs.width) - && ASDimensionEqualToDimension(lhs.height, rhs.height); -} - - -#pragma mark - ASRelativeSizeRange - -ASRelativeSizeRange ASRelativeSizeRangeMake(ASLayoutSize min, ASLayoutSize max) -{ - ASRelativeSizeRange sizeRange; sizeRange.min = min; sizeRange.max = max; return sizeRange; -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeSize(ASLayoutSize exact) -{ - return ASRelativeSizeRangeMake(exact, exact); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactCGSize(CGSize exact) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithCGSize(exact)); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactFraction(CGFloat fraction) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMakeWithFraction(fraction)); -} - -ASRelativeSizeRange ASRelativeSizeRangeMakeWithExactRelativeDimensions(ASRelativeDimension exactWidth, ASRelativeDimension exactHeight) -{ - return ASRelativeSizeRangeMakeWithExactRelativeSize(ASRelativeSizeMake(exactWidth, exactHeight)); -} - -BOOL ASRelativeSizeRangeEqualToRelativeSizeRange(ASRelativeSizeRange lhs, ASRelativeSizeRange rhs) -{ - return ASRelativeSizeEqualToRelativeSize(lhs.min, rhs.min) && ASRelativeSizeEqualToRelativeSize(lhs.max, rhs.max); -} - -ASSizeRange ASRelativeSizeRangeResolve(ASRelativeSizeRange relativeSizeRange, - CGSize parentSize) -{ - return ASSizeRangeMake(ASLayoutSizeResolveSize(relativeSizeRange.min, parentSize, parentSize), - ASLayoutSizeResolveSize(relativeSizeRange.max, parentSize, parentSize)); -} diff --git a/Source/Layout/ASLayout.h b/Source/Layout/ASLayout.h index a0aff5b7..fa137198 100644 --- a/Source/Layout/ASLayout.h +++ b/Source/Layout/ASLayout.h @@ -147,23 +147,6 @@ ASDISPLAYNODE_EXTERN_C_END @end -#pragma mark - Deprecated - -@interface ASLayout (Deprecated) - -- (id )layoutableObject ASDISPLAYNODE_DEPRECATED; - -+ (instancetype)layoutWithLayoutableObject:(id)layoutElement - constrainedSizeRange:(ASSizeRange)constrainedSizeRange - size:(CGSize)size ASDISPLAYNODE_DEPRECATED; - -+ (instancetype)layoutWithLayoutableObject:(id)layoutElement - constrainedSizeRange:(ASSizeRange)constrainedSizeRange - size:(CGSize)size - sublayouts:(nullable NSArray *)sublayouts AS_WARN_UNUSED_RESULT ASDISPLAYNODE_DEPRECATED; - -@end - #pragma mark - Debugging @interface ASLayout (Debugging) diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index 22904b8b..03112010 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -353,30 +353,6 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( @end -@implementation ASLayout (Deprecation) - -- (id )layoutableObject -{ - return self.layoutElement; -} - -+ (instancetype)layoutWithLayoutableObject:(id)layoutElement - constrainedSizeRange:(ASSizeRange)constrainedSizeRange - size:(CGSize)size -{ - return [self layoutWithLayoutElement:layoutElement size:size]; -} - -+ (instancetype)layoutWithLayoutableObject:(id)layoutElement - constrainedSizeRange:(ASSizeRange)constrainedSizeRange - size:(CGSize)size - sublayouts:(nullable NSArray *)sublayouts -{ - return [self layoutWithLayoutElement:layoutElement size:size sublayouts:sublayouts]; -} - -@end - ASLayout *ASCalculateLayout(id layoutElement, const ASSizeRange sizeRange, const CGSize parentSize) { ASDisplayNodeCAssertNotNil(layoutElement, @"Not valid layoutElement passed in."); diff --git a/Source/Layout/ASLayoutElement.h b/Source/Layout/ASLayoutElement.h index 2ebd55a4..ca56c672 100644 --- a/Source/Layout/ASLayoutElement.h +++ b/Source/Layout/ASLayoutElement.h @@ -147,22 +147,6 @@ typedef NS_ENUM(NSUInteger, ASLayoutElementType) { - (BOOL)implementsLayoutMethod; -#pragma mark - Deprecated - -#define ASLayoutable ASLayoutElement - -/** - * @abstract Calculate a layout based on given size range. - * - * @param constrainedSize The minimum and maximum sizes the receiver should fit in. - * - * @return An ASLayout instance defining the layout of the receiver and its children. - * - * @deprecated Deprecated in version 2.0: Use layoutThatFits: or layoutThatFits:parentSize: if used in - * ASLayoutSpec subclasses - */ -- (nonnull ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize ASDISPLAYNODE_DEPRECATED_MSG("Use layoutThatFits: instead."); - @end #pragma mark - ASLayoutElementStyle diff --git a/Source/Layout/ASLayoutElement.mm b/Source/Layout/ASLayoutElement.mm index 8d647c09..9fc52b3b 100644 --- a/Source/Layout/ASLayoutElement.mm +++ b/Source/Layout/ASLayoutElement.mm @@ -804,22 +804,4 @@ do {\ #endif /* YOGA */ -#pragma mark Deprecated - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -- (ASRelativeSizeRange)sizeRange -{ - return ASRelativeSizeRangeMake(self.minLayoutSize, self.maxLayoutSize); -} - -- (void)setSizeRange:(ASRelativeSizeRange)sizeRange -{ - self.minLayoutSize = sizeRange.min; - self.maxLayoutSize = sizeRange.max; -} - -#pragma clang diagnostic pop - @end diff --git a/Source/Layout/ASLayoutElementPrivate.h b/Source/Layout/ASLayoutElementPrivate.h index 68c46de6..9bc3101b 100644 --- a/Source/Layout/ASLayoutElementPrivate.h +++ b/Source/Layout/ASLayoutElementPrivate.h @@ -51,11 +51,6 @@ NS_ASSUME_NONNULL_END return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max];\ }\ \ -- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize\ -{\ - return [self layoutThatFits:constrainedSize parentSize:constrainedSize.max];\ -}\ -\ - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize\ {\ return [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize];\ @@ -117,134 +112,3 @@ typedef struct ASLayoutElementStyleExtensions { return [self.style layoutOptionExtensionEdgeInsetsAtIndex:idx];\ }\ -#pragma mark ASLayoutElementStyleForwardingDeclaration (Deprecated) - -#define ASLayoutElementStyleForwardingDeclaration \ -@property (nonatomic, readwrite) CGFloat spacingBefore ASDISPLAYNODE_DEPRECATED_MSG("Use style.spacingBefore"); \ -@property (nonatomic, readwrite) CGFloat spacingAfter ASDISPLAYNODE_DEPRECATED_MSG("Use style.spacingAfter"); \ -@property (nonatomic, readwrite) CGFloat flexGrow ASDISPLAYNODE_DEPRECATED_MSG("Use style.flexGrow"); \ -@property (nonatomic, readwrite) CGFloat flexShrink ASDISPLAYNODE_DEPRECATED_MSG("Use style.flexShrink"); \ -@property (nonatomic, readwrite) ASDimension flexBasis ASDISPLAYNODE_DEPRECATED_MSG("Use style.flexBasis"); \ -@property (nonatomic, readwrite) ASStackLayoutAlignSelf alignSelf ASDISPLAYNODE_DEPRECATED_MSG("Use style.alignSelf"); \ -@property (nonatomic, readwrite) CGFloat ascender ASDISPLAYNODE_DEPRECATED_MSG("Use style.ascender"); \ -@property (nonatomic, readwrite) CGFloat descender ASDISPLAYNODE_DEPRECATED_MSG("Use style.descender"); \ -@property (nonatomic, assign) ASRelativeSizeRange sizeRange ASDISPLAYNODE_DEPRECATED_MSG("Don't use sizeRange anymore instead set style.width or style.height"); \ -@property (nonatomic, assign) CGPoint layoutPosition ASDISPLAYNODE_DEPRECATED_MSG("Use style.layoutPosition"); \ - - -#pragma mark - ASLayoutElementStyleForwarding (Deprecated) - -// For the time beeing we are forwading all style related properties on ASDisplayNode and ASLayoutSpec. This define -// help us to not have duplicate code while moving from 1.x to 2.0s -#define ASLayoutElementStyleForwarding \ -\ -@dynamic spacingBefore, spacingAfter, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition;\ -\ -_Pragma("mark - ASStackLayoutElement")\ -\ -- (void)setSpacingBefore:(CGFloat)spacingBefore\ -{\ - self.style.spacingBefore = spacingBefore;\ -}\ -\ -- (CGFloat)spacingBefore\ -{\ - return self.style.spacingBefore;\ -}\ -\ -- (void)setSpacingAfter:(CGFloat)spacingAfter\ -{\ - self.style.spacingAfter = spacingAfter;\ -}\ -\ -- (CGFloat)spacingAfter\ -{\ - return self.style.spacingAfter;\ -}\ -\ -- (void)setFlexGrow:(CGFloat)flexGrow\ -{\ - self.style.flexGrow = flexGrow;\ -}\ -\ -- (CGFloat)flexGrow\ -{\ - return self.style.flexGrow;\ -}\ -\ -- (void)setFlexShrink:(CGFloat)flexShrink\ -{\ - self.style.flexShrink = flexShrink;\ -}\ -\ -- (CGFloat)flexShrink\ -{\ - return self.style.flexShrink;\ -}\ -\ -- (void)setFlexBasis:(ASDimension)flexBasis\ -{\ - self.style.flexBasis = flexBasis;\ -}\ -\ -- (ASDimension)flexBasis\ -{\ - return self.style.flexBasis;\ -}\ -\ -- (void)setAlignSelf:(ASStackLayoutAlignSelf)alignSelf\ -{\ - self.style.alignSelf = alignSelf;\ -}\ -\ -- (ASStackLayoutAlignSelf)alignSelf\ -{\ - return self.style.alignSelf;\ -}\ -\ -- (void)setAscender:(CGFloat)ascender\ -{\ - self.style.ascender = ascender;\ -}\ -\ -- (CGFloat)ascender\ -{\ - return self.style.ascender;\ -}\ -\ -- (void)setDescender:(CGFloat)descender\ -{\ - self.style.descender = descender;\ -}\ -\ -- (CGFloat)descender\ -{\ - return self.style.descender;\ -}\ -\ -_Pragma("mark - ASAbsoluteLayoutElement")\ -\ -- (void)setLayoutPosition:(CGPoint)layoutPosition\ -{\ - self.style.layoutPosition = layoutPosition;\ -}\ -\ -- (CGPoint)layoutPosition\ -{\ - return self.style.layoutPosition;\ -}\ -\ -_Pragma("clang diagnostic push")\ -_Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")\ -\ -- (void)setSizeRange:(ASRelativeSizeRange)sizeRange\ -{\ - self.style.sizeRange = sizeRange;\ -}\ -\ -- (ASRelativeSizeRange)sizeRange\ -{\ - return self.style.sizeRange;\ -}\ -\ -_Pragma("clang diagnostic pop")\ diff --git a/Source/Layout/ASLayoutSpec.h b/Source/Layout/ASLayoutSpec.h index e4d9d42d..ac02bacd 100644 --- a/Source/Layout/ASLayoutSpec.h +++ b/Source/Layout/ASLayoutSpec.h @@ -104,10 +104,4 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASLayoutSpec (Deprecated) - -ASLayoutElementStyleForwardingDeclaration - -@end - NS_ASSUME_NONNULL_END diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index 6600f508..76901d1a 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -35,17 +35,6 @@ @dynamic layoutElementType; @synthesize debugName = _debugName; -#pragma mark - Class - -+ (void)initialize -{ - [super initialize]; - if (self != [ASLayoutSpec class]) { - ASDisplayNodeAssert(!ASSubclassOverridesSelector([ASLayoutSpec class], self, @selector(measureWithSizeRange:)), @"Subclass %@ must not override measureWithSizeRange: method. Instead override calculateLayoutThatFits:", NSStringFromClass(self)); - } -} - - #pragma mark - Lifecycle - (instancetype)init @@ -167,7 +156,6 @@ ASLayoutElementLayoutCalculationDefaults } ASPrimitiveTraitCollectionDefaults -ASPrimitiveTraitCollectionDeprecatedImplementation #pragma mark - ASLayoutElementStyleExtensibility @@ -353,11 +341,3 @@ ASLayoutElementStyleExtensibilityForwarding } @end - -#pragma mark - ASLayoutSpec (Deprecated) - -@implementation ASLayoutSpec (Deprecated) - -ASLayoutElementStyleForwarding - -@end diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 36683954..0d1982a2 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -52,8 +52,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4, ASDisplayNodeMethodOverrideCalcLayoutThatFits = 1 << 5, ASDisplayNodeMethodOverrideCalcSizeThatFits = 1 << 6, - ASDisplayNodeMethodOverrideFetchData = 1 << 7, - ASDisplayNodeMethodOverrideClearFetchedData = 1 << 8 }; typedef NS_OPTIONS(uint_least32_t, ASDisplayNodeAtomicFlags) diff --git a/Source/Private/ASTableView+Undeprecated.h b/Source/Private/ASTableView+Undeprecated.h index 70098940..bef11339 100644 --- a/Source/Private/ASTableView+Undeprecated.h +++ b/Source/Private/ASTableView+Undeprecated.h @@ -154,14 +154,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)reloadData; -/** - * Reload everything from scratch entirely on the main thread, destroying the working range and all cached nodes. - * - * @warning This method is substantially more expensive than UITableView's version and will block the main thread while - * all the cells load. - */ -- (void)reloadDataImmediately; - /** * Triggers a relayout of all nodes. * diff --git a/Source/Private/Layout/ASStackUnpositionedLayout.mm b/Source/Private/Layout/ASStackUnpositionedLayout.mm index 54a17bb0..1dff9334 100644 --- a/Source/Private/Layout/ASStackUnpositionedLayout.mm +++ b/Source/Private/Layout/ASStackUnpositionedLayout.mm @@ -71,7 +71,7 @@ static ASLayout *crossChildLayout(const ASStackLayoutSpecChild &child, crossMax); const ASSizeRange childSizeRange = directionSizeRange(style.direction, stackMin, stackMax, childCrossMin, childCrossMax); ASLayout *layout = [child.element layoutThatFits:childSizeRange parentSize:parentSize]; - ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from measureWithSizeRange: must not be nil: %@", child.element); + ASDisplayNodeCAssertNotNil(layout, @"ASLayout returned from -layoutThatFits:parentSize: must not be nil: %@", child.element); return layout ? : [ASLayout layoutWithLayoutElement:child.element size:{0, 0}]; } diff --git a/Source/Private/_ASCollectionGalleryLayoutItem.mm b/Source/Private/_ASCollectionGalleryLayoutItem.mm index 688ef44d..32479d1c 100644 --- a/Source/Private/_ASCollectionGalleryLayoutItem.mm +++ b/Source/Private/_ASCollectionGalleryLayoutItem.mm @@ -38,7 +38,6 @@ ASLayoutElementStyleExtensibilityForwarding ASPrimitiveTraitCollectionDefaults -ASPrimitiveTraitCollectionDeprecatedImplementation - (ASTraitCollection *)asyncTraitCollection { diff --git a/Source/Private/_ASHierarchyChangeSet.mm b/Source/Private/_ASHierarchyChangeSet.mm index e2e70c70..77ad5d95 100644 --- a/Source/Private/_ASHierarchyChangeSet.mm +++ b/Source/Private/_ASHierarchyChangeSet.mm @@ -25,19 +25,11 @@ #import #import -// If assertions are enabled and they haven't forced us to suppress the exception, -// then throw, otherwise log. +// If assertions are enabled, throw. Otherwise log. #if ASDISPLAYNODE_ASSERTIONS_ENABLED #define ASFailUpdateValidation(...)\ - _Pragma("clang diagnostic push")\ - _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"")\ - if ([ASDisplayNode suppressesInvalidCollectionUpdateExceptions]) {\ - NSLog(__VA_ARGS__);\ - } else {\ - NSLog(__VA_ARGS__);\ - [NSException raise:ASCollectionInvalidUpdateException format:__VA_ARGS__];\ - }\ - _Pragma("clang diagnostic pop") + NSLog(__VA_ARGS__);\ + [NSException raise:ASCollectionInvalidUpdateException format:__VA_ARGS__]; #else #define ASFailUpdateValidation(...) NSLog(__VA_ARGS__); #endif diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index f3567b43..b223e246 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -620,7 +620,7 @@ [window makeKeyAndVisible]; for (NSInteger i = 0; i < 2; i++) { - // NOTE: waitUntilAllUpdatesAreProcessed or reloadDataImmediately is not sufficient here!! + // NOTE: reloadData and waitUntilAllUpdatesAreProcessed are not sufficient here!! XCTestExpectation *done = [self expectationWithDescription:[NSString stringWithFormat:@"Reload #%td complete", i]]; [cn reloadDataWithCompletion:^{ [done fulfill]; diff --git a/Tests/ASDisplayNodeLayoutTests.mm b/Tests/ASDisplayNodeLayoutTests.mm index a998e982..00512e80 100644 --- a/Tests/ASDisplayNodeLayoutTests.mm +++ b/Tests/ASDisplayNodeLayoutTests.mm @@ -44,7 +44,7 @@ ASXCTAssertEqualSizes(displayNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0"); ASXCTAssertEqualSizes(buttonNode.calculatedSize, CGSizeZero, @"Calculated size before measurement and layout should be 0"); - // Trigger view creation and layout pass without a manual measure: call before so the automatic measurement + // Trigger view creation and layout pass without a manual -layoutThatFits: call before so the automatic measurement // pass will trigger in the layout pass [displayNode.view layoutIfNeeded]; diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index 99882df0..f1fb33d1 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -24,7 +24,6 @@ #import #import #import -#import #import #import "ASDisplayNodeTestsHelper.h" #import @@ -2128,27 +2127,6 @@ static bool stringContainsPointer(NSString *description, id p) { XCTAssertEqualObjects(calls, expected); } -- (void)testPreferredFrameSizeDeprecated -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - - ASDisplayNode *node = [ASDisplayNode new]; - - // Default auto preferred frame size will be CGSizeZero - XCTAssert(CGSizeEqualToSize(node.preferredFrameSize, CGSizeZero)); - - // Set a specific preferredFrameSize - node.preferredFrameSize = CGSizeMake(100, 100); - ASXCTAssertEqualSizes(node.preferredFrameSize, CGSizeMake(100, 100)); - - // CGSizeZero should be returned if width or height is not of unit type points - node.style.width = ASDimensionMakeWithFraction(0.5); - ASXCTAssertEqualSizes(node.preferredFrameSize, CGSizeZero); - -#pragma clang diagnostic pop -} - - (void)testSettingPropertiesViaStyllableProtocol { ASDisplayNode *node = [[ASDisplayNode alloc] init]; diff --git a/Tests/ASLayoutSpecTests.m b/Tests/ASLayoutSpecTests.m index 63009246..5cfc3a66 100644 --- a/Tests/ASLayoutSpecTests.m +++ b/Tests/ASLayoutSpecTests.m @@ -2,8 +2,17 @@ // ASLayoutSpecTests.m // Texture // -// Created by Michael Schneider on 1/27/17. -// Copyright © 2017 Facebook. All rights reserved. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -39,7 +48,7 @@ ASDK_STYLE_PROP_OBJ(NSString *, extendedName, setExtendedName); @end /* - * As the ASLayoutableStyle conforms to the ASDKExtendedLayoutable protocol now, ASDKExtendedLayoutable properties + * As the ASLayoutElementStyle conforms to the ASDKExtendedLayoutElement protocol now, ASDKExtendedLayoutElement properties * can be accessed in ASDKExtendedLayoutSpec */ @interface ASDKExtendedLayoutSpec : ASLayoutSpec diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index 2bf79364..f26d96b1 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -253,7 +253,8 @@ tableView.asyncDelegate = delegate; tableView.asyncDataSource = dataSource; - [tableView reloadDataImmediately]; + [tableView reloadData]; + [tableView waitUntilAllUpdatesAreCommitted]; [tableView setNeedsLayout]; [tableView layoutIfNeeded]; diff --git a/Tests/ASTableViewThrashTests.m b/Tests/ASTableViewThrashTests.m index e547fe39..fe2e2efd 100644 --- a/Tests/ASTableViewThrashTests.m +++ b/Tests/ASTableViewThrashTests.m @@ -226,7 +226,8 @@ static atomic_uint ASThrashTestSectionNextID = 1; #else _tableView.asyncDelegate = self; _tableView.asyncDataSource = self; - [_tableView reloadDataImmediately]; + [_tableView reloadData]; + [_tableView waitUntilAllUpdatesAreCommitted]; #endif [_tableView layoutIfNeeded]; } diff --git a/examples/PagerNode/Sample/PageNode.m b/examples/PagerNode/Sample/PageNode.m index bda108a1..0efd8312 100644 --- a/examples/PagerNode/Sample/PageNode.m +++ b/examples/PagerNode/Sample/PageNode.m @@ -1,20 +1,18 @@ // // PageNode.m -// Sample -// -// Created by McCallum, Levi on 12/7/15. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses 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. +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "PageNode.h" @@ -26,10 +24,10 @@ return constrainedSize; } -- (void)fetchData +- (void)didEnterPreloadState { - [super fetchData]; - NSLog(@"Fetching data for node: %@", self); + [super didEnterPreloadState]; + NSLog(@"didEnterPreloadState for node: %@", self); } @end From 0bd18c852233d7b2b0b433e031aea1beb10da86b Mon Sep 17 00:00:00 2001 From: Eric Scheers Date: Thu, 7 Sep 2017 22:05:30 +0200 Subject: [PATCH 10/22] [ASDisplayNode] Notify rasterized subnodes that render pass has completed (#532) * Notify rasterized subsides that render pass has completed * Traverse entire subnode tree notifying all subnodes * Add entry in changelog * Retrieve rasterizesSubtree flag while holding instance lock * Balance display delegate calls for rasterized subnodes --- CHANGELOG.md | 1 + Source/Private/ASDisplayNode+AsyncDisplay.mm | 15 ++++++ Tests/ASDisplayNodeTests.mm | 49 ++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3d6a3d3..f98a440d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525) - [ASEditableTextNode] added -editableTextNodeShouldBeginEditing to ASEditableTextNodeDelegate to mirror the corresponding method from UITextViewDelegate. [Yan S.](https://github.com/yans) [#535](https://github.com/TextureGroup/Texture/pull/535) - [Breaking] Remove APIs that have been deprecated since 2.0 and/or for at least 6 months [Huy Nguyen](https://github.com/nguyenhuy) [#529](https://github.com/TextureGroup/Texture/pull/529) +- [ASDisplayNode] Ensure `-displayWillStartAsynchronously:` and `-displayDidFinish` are invoked on rasterized subnodes. [Eric Scheers](https://github.com/smeis) [#532](https://github.com/TextureGroup/Texture/pull/532) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 00bb0ca6..999c2552 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -23,6 +23,8 @@ #import #import #import +#import + @interface ASDisplayNode () <_ASDisplayLayerDelegate> @end @@ -302,6 +304,7 @@ } CALayer *layer = _layer; + BOOL rasterizesSubtree = _flags.rasterizesSubtree; __instanceLock__.unlock(); @@ -347,11 +350,23 @@ layer.contents = (id)image.CGImage; } [self didDisplayAsyncLayer:self.asyncLayer]; + + if (rasterizesSubtree) { + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { + [node didDisplayAsyncLayer:node.asyncLayer]; + }); + } } }; // Call willDisplay immediately in either case [self willDisplayAsyncLayer:self.asyncLayer asynchronously:asynchronously]; + + if (rasterizesSubtree) { + ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) { + [node willDisplayAsyncLayer:node.asyncLayer asynchronously:asynchronously]; + }); + } if (asynchronously) { // Async rendering operations are contained by a transaction, which allows them to proceed and concurrently diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index f1fb33d1..fac3f509 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -110,6 +110,10 @@ for (ASDisplayNode *n in @[ nodes ]) {\ @property (nonatomic) BOOL hasPreloaded; @property (nonatomic) BOOL preloadStateChangedToYES; @property (nonatomic) BOOL preloadStateChangedToNO; + +@property (nonatomic, assign) NSUInteger displayWillStartCount; +@property (nonatomic, assign) NSUInteger didDisplayCount; + @end @interface ASTestResponderNode : ASTestDisplayNode @@ -154,6 +158,18 @@ for (ASDisplayNode *n in @[ nodes ]) {\ } } +- (void)displayDidFinish +{ + [super displayDidFinish]; + _didDisplayCount++; +} + +- (void)displayWillStartAsynchronously:(BOOL)asynchronously +{ + [super displayWillStartAsynchronously:asynchronously]; + _displayWillStartCount++; +} + @end @interface UIDisplayNodeTestView : UIView @@ -2018,6 +2034,39 @@ static bool stringContainsPointer(NSString *description, id p) { XCTAssertThrows([rasterizedSupernode addSubnode:subnode]); } +- (void)testThatSubnodesGetDisplayUpdatesIfRasterized +{ + ASTestDisplayNode *supernode = [[ASTestDisplayNode alloc] init]; + supernode.frame = CGRectMake(0.0, 0.0, 100.0, 100.0); + [supernode enableSubtreeRasterization]; + + ASTestDisplayNode *subnode = [[ASTestDisplayNode alloc] init]; + ASTestDisplayNode *subSubnode = [[ASTestDisplayNode alloc] init]; + + ASSetDebugNames(supernode, subnode); + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + [subnode addSubnode:subSubnode]; + [supernode addSubnode:subnode]; + [window addSubnode:supernode]; + [window makeKeyAndVisible]; + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subnode.didDisplayCount == 1); + })); + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subSubnode.didDisplayCount == 1); + })); + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subnode.displayWillStartCount == 1); + })); + + XCTAssertTrue(ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ + return (subSubnode.displayWillStartCount == 1); + })); +} + // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2011 - (void)testThatLayerBackedSubnodesAreMarkedInvisibleBeforeDeallocWhenSupernodesViewIsRemovedFromHierarchyWhileBeingRetained { From 53e99cc76273fe952ed790fc38c4026c7ea3d715 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 8 Sep 2017 07:17:35 -0700 Subject: [PATCH 11/22] Make ASWeakMapEntry Value Atomic (#555) * Make ASWeakMapEntry value atomic * Increment changelog * Go a little nuts * Update CHANGELOG.md --- CHANGELOG.md | 1 + Source/Private/ASWeakMap.h | 4 ++-- Source/Private/ASWeakMap.m | 16 ++++++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f98a440d..3f38cdc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - [ASEditableTextNode] added -editableTextNodeShouldBeginEditing to ASEditableTextNodeDelegate to mirror the corresponding method from UITextViewDelegate. [Yan S.](https://github.com/yans) [#535](https://github.com/TextureGroup/Texture/pull/535) - [Breaking] Remove APIs that have been deprecated since 2.0 and/or for at least 6 months [Huy Nguyen](https://github.com/nguyenhuy) [#529](https://github.com/TextureGroup/Texture/pull/529) - [ASDisplayNode] Ensure `-displayWillStartAsynchronously:` and `-displayDidFinish` are invoked on rasterized subnodes. [Eric Scheers](https://github.com/smeis) [#532](https://github.com/TextureGroup/Texture/pull/532) +- Fixed a memory corruption issue in the ASImageNode display system. [Adlai Holler](https://github.com/Adlai-Holler) [#555](https://github.com/TextureGroup/Texture/pull/555) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/Private/ASWeakMap.h b/Source/Private/ASWeakMap.h index fb53de15..18bf0718 100644 --- a/Source/Private/ASWeakMap.h +++ b/Source/Private/ASWeakMap.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASWeakMapEntry : NSObject -@property (nonatomic, retain, readonly) Value value; +@property (atomic, strong, readonly) Value value; @end @@ -49,7 +49,7 @@ AS_SUBCLASSING_RESTRICTED * The underlying storage is a hash table and the Key type should implement `hash` and `isEqual:`. */ AS_SUBCLASSING_RESTRICTED -@interface ASWeakMap<__covariant Key : NSObject *, Value> : NSObject +@interface ASWeakMap<__covariant Key, Value> : NSObject /** * Read from the cache. The Value object is accessible from the returned ASWeakMapEntry. diff --git a/Source/Private/ASWeakMap.m b/Source/Private/ASWeakMap.m index 2d9eb552..5d5b3dc1 100644 --- a/Source/Private/ASWeakMap.m +++ b/Source/Private/ASWeakMap.m @@ -18,12 +18,13 @@ #import @interface ASWeakMapEntry () -@property (nonatomic, strong) NSObject *key; +@property (nonatomic, strong, readonly) id key; +@property (atomic, strong) id value; @end @implementation ASWeakMapEntry -- (instancetype)initWithKey:(NSObject *)key value:(NSObject *)value +- (instancetype)initWithKey:(id)key value:(id)value { self = [super init]; if (self) { @@ -33,16 +34,11 @@ return self; } -- (void)setValue:(NSObject *)value -{ - _value = value; -} - @end @interface ASWeakMap () -@property (nonatomic, strong) NSMapTable *hashTable; +@property (nonatomic, strong, readonly) NSMapTable *hashTable; @end /** @@ -69,12 +65,12 @@ return self; } -- (ASWeakMapEntry *)entryForKey:(NSObject *)key +- (ASWeakMapEntry *)entryForKey:(id)key { return [self.hashTable objectForKey:key]; } -- (ASWeakMapEntry *)setObject:(NSObject *)value forKey:(NSObject *)key +- (ASWeakMapEntry *)setObject:(id)value forKey:(id)key { ASWeakMapEntry *entry = [self.hashTable objectForKey:key]; if (entry != nil) { From d4b1f625aaa3bf85989f30aee809f116a92db57e Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Fri, 8 Sep 2017 17:05:06 +0100 Subject: [PATCH 12/22] [Gallery layout] Include the caller in properties providing methods (#533) * Include the caller in ASCollectionGalleryLayoutPropertiesProviding's methods * Update CHANGELOG.md --- CHANGELOG.md | 1 + Source/ASPagerNode.m | 2 +- .../Details/ASCollectionGalleryLayoutDelegate.h | 17 +++++++++++++---- .../ASCollectionGalleryLayoutDelegate.mm | 14 +++++++------- .../ASCollectionView/Sample/ViewController.m | 2 +- 5 files changed, 23 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f38cdc8..1553fc74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [Breaking] Remove APIs that have been deprecated since 2.0 and/or for at least 6 months [Huy Nguyen](https://github.com/nguyenhuy) [#529](https://github.com/TextureGroup/Texture/pull/529) - [ASDisplayNode] Ensure `-displayWillStartAsynchronously:` and `-displayDidFinish` are invoked on rasterized subnodes. [Eric Scheers](https://github.com/smeis) [#532](https://github.com/TextureGroup/Texture/pull/532) - Fixed a memory corruption issue in the ASImageNode display system. [Adlai Holler](https://github.com/Adlai-Holler) [#555](https://github.com/TextureGroup/Texture/pull/555) +- [Breaking] Rename ASCollectionGalleryLayoutSizeProviding to ASCollectionGalleryLayoutPropertiesProviding. Besides a fixed item size, it now can provide interitem and line spacings, as well as section inset [Huy Nguyen](https://github.com/nguyenhuy) [#496](https://github.com/TextureGroup/Texture/pull/496) [#533](https://github.com/TextureGroup/Texture/pull/533) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASPagerNode.m b/Source/ASPagerNode.m index 1ef893d9..4d8195a1 100644 --- a/Source/ASPagerNode.m +++ b/Source/ASPagerNode.m @@ -145,7 +145,7 @@ #pragma mark - ASCollectionGalleryLayoutPropertiesProviding -- (CGSize)sizeForElements:(ASElementMap *)elements +- (CGSize)galleryLayoutDelegate:(nonnull ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(nonnull ASElementMap *)elements { ASDisplayNodeAssertMainThread(); return [self pageSize]; diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.h b/Source/Details/ASCollectionGalleryLayoutDelegate.h index dfd0f5c8..99958d1c 100644 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.h +++ b/Source/Details/ASCollectionGalleryLayoutDelegate.h @@ -14,6 +14,7 @@ #import @class ASElementMap; +@class ASCollectionGalleryLayoutDelegate; NS_ASSUME_NONNULL_BEGIN @@ -24,11 +25,13 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion This method will only be called on main thread. * + * @param delegate The calling object. + * * @param elements All elements to be sized. * * @return The elements' size */ -- (CGSize)sizeForElements:(ASElementMap *)elements; +- (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements; @optional @@ -42,11 +45,13 @@ NS_ASSUME_NONNULL_BEGIN * It is not applied between the first line and the header, or between the last line and the footer. * This is the same behavior as UICollectionViewFlowLayout's minimumLineSpacing. * + * @param delegate The calling object. + * * @param elements All elements in the layout. * * @return The interitem spacing */ -- (CGFloat)minimumLineSpacingForElements:(ASElementMap *)elements; +- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumLineSpacingForElements:(ASElementMap *)elements; /** * Returns the minumum spacing to use between items in the same row or column, depending on the scroll directions. @@ -58,22 +63,26 @@ NS_ASSUME_NONNULL_BEGIN * It is considered while fitting items into lines, but the actual final spacing between some items might be larger. * This is the same behavior as UICollectionViewFlowLayout's minimumInteritemSpacing. * + * @param delegate The calling object. + * * @param elements All elements in the layout. * * @return The interitem spacing */ -- (CGFloat)minimumInteritemSpacingForElements:(ASElementMap *)elements; +- (CGFloat)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate minimumInteritemSpacingForElements:(ASElementMap *)elements; /** * Returns the margins of each section. * * @discussion This method will only be called on main thread. * + * @param delegate The calling object. + * * @param elements All elements in the layout. * * @return The margins used to layout content in a section */ -- (UIEdgeInsets)sectionInsetForElements:(ASElementMap *)elements; +- (UIEdgeInsets)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sectionInsetForElements:(ASElementMap *)elements; @end diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/Source/Details/ASCollectionGalleryLayoutDelegate.mm index a01fdf7f..2734e964 100644 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ b/Source/Details/ASCollectionGalleryLayoutDelegate.mm @@ -66,9 +66,9 @@ _propertiesProviderFlags = {}; } else { _propertiesProvider = propertiesProvider; - _propertiesProviderFlags.minimumLineSpacingForElements = [_propertiesProvider respondsToSelector:@selector(minimumLineSpacingForElements:)]; - _propertiesProviderFlags.minimumInteritemSpacingForElements = [_propertiesProvider respondsToSelector:@selector(minimumInteritemSpacingForElements:)]; - _propertiesProviderFlags.sectionInsetForElements = [_propertiesProvider respondsToSelector:@selector(sectionInsetForElements:)]; + _propertiesProviderFlags.minimumLineSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumLineSpacingForElements:)]; + _propertiesProviderFlags.minimumInteritemSpacingForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:minimumInteritemSpacingForElements:)]; + _propertiesProviderFlags.sectionInsetForElements = [_propertiesProvider respondsToSelector:@selector(galleryLayoutDelegate:sectionInsetForElements:)]; } } @@ -80,10 +80,10 @@ return nil; } - CGSize itemSize = [propertiesProvider sizeForElements:elements]; - UIEdgeInsets sectionInset = _propertiesProviderFlags.sectionInsetForElements ? [propertiesProvider sectionInsetForElements:elements] : UIEdgeInsetsZero; - CGFloat lineSpacing = _propertiesProviderFlags.minimumLineSpacingForElements ? [propertiesProvider minimumLineSpacingForElements:elements] : 0.0; - CGFloat interitemSpacing = _propertiesProviderFlags.minimumInteritemSpacingForElements ? [propertiesProvider minimumInteritemSpacingForElements:elements] : 0.0; + CGSize itemSize = [propertiesProvider galleryLayoutDelegate:self sizeForElements:elements]; + UIEdgeInsets sectionInset = _propertiesProviderFlags.sectionInsetForElements ? [propertiesProvider galleryLayoutDelegate:self sectionInsetForElements:elements] : UIEdgeInsetsZero; + CGFloat lineSpacing = _propertiesProviderFlags.minimumLineSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumLineSpacingForElements:elements] : 0.0; + CGFloat interitemSpacing = _propertiesProviderFlags.minimumInteritemSpacingForElements ? [propertiesProvider galleryLayoutDelegate:self minimumInteritemSpacingForElements:elements] : 0.0; return [[_ASCollectionGalleryLayoutInfo alloc] initWithItemSize:itemSize minimumLineSpacing:lineSpacing minimumInteritemSpacing:interitemSpacing diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index fa093f80..3755b01d 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -112,7 +112,7 @@ #pragma mark - ASCollectionGalleryLayoutPropertiesProviding -- (CGSize)sizeForElements:(ASElementMap *)elements +- (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements { ASDisplayNodeAssertMainThread(); return CGSizeMake(180, 90); From 786963c6a98514da9d227f2b8fed219e469d65d0 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Fri, 8 Sep 2017 18:04:43 +0100 Subject: [PATCH 13/22] [ASDisplayNode] Deprecate -displayWillStart in favor of -displayWillStartAsynchronously: (#536) * Deprecate -[ASDisplayNode displayWillStart] in favor of -displayWillStartAsynchronously: * Minor change * Fix CHANGELOG * Update CHANGELOG.md * Update CHANGELOG.md --- CHANGELOG.md | 3 ++- Source/ASDisplayNode+Subclasses.h | 13 ++++++++++++- Source/ASDisplayNode.mm | 5 ++++- Source/ASMultiplexImageNode.mm | 6 +++--- Source/ASNetworkImageNode.mm | 2 +- Source/Private/ASDisplayNode+AsyncDisplay.mm | 2 +- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1553fc74..0b69fcd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - Add -[ASDisplayNode detailedLayoutDescription] property to aid debugging. [Adlai Holler](https://github.com/Adlai-Holler) [#476](https://github.com/TextureGroup/Texture/pull/476) - Fix an issue that causes calculatedLayoutDidChange being called needlessly. [Huy Nguyen](https://github.com/nguyenhuy) [#490](https://github.com/TextureGroup/Texture/pull/490) - Negate iOS 11 automatic estimated table row heights. [Christian Selig](https://github.com/christianselig) [#485](https://github.com/TextureGroup/Texture/pull/485) -- [Breaking] Add content offset bridging property to ASTableNode and ASCollectionNode. Deprecate related methods in ASTableView and ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#460](https://github.com/TextureGroup/Texture/pull/460) +- Add content offset bridging property to ASTableNode and ASCollectionNode. Deprecate related methods in ASTableView and ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#460](https://github.com/TextureGroup/Texture/pull/460) - Remove re-entrant access to self.view when applying initial pending state. [Adlai Holler](https://github.com/Adlai-Holler) [#510](https://github.com/TextureGroup/Texture/pull/510) - Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513) - Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520) @@ -19,6 +19,7 @@ - [ASDisplayNode] Ensure `-displayWillStartAsynchronously:` and `-displayDidFinish` are invoked on rasterized subnodes. [Eric Scheers](https://github.com/smeis) [#532](https://github.com/TextureGroup/Texture/pull/532) - Fixed a memory corruption issue in the ASImageNode display system. [Adlai Holler](https://github.com/Adlai-Holler) [#555](https://github.com/TextureGroup/Texture/pull/555) - [Breaking] Rename ASCollectionGalleryLayoutSizeProviding to ASCollectionGalleryLayoutPropertiesProviding. Besides a fixed item size, it now can provide interitem and line spacings, as well as section inset [Huy Nguyen](https://github.com/nguyenhuy) [#496](https://github.com/TextureGroup/Texture/pull/496) [#533](https://github.com/TextureGroup/Texture/pull/533) +- Deprecate `-[ASDisplayNode displayWillStart]` in favor of `-displayWillStartAsynchronously:` [Huy Nguyen](https://github.com/nguyenhuy) [536](https://github.com/TextureGroup/Texture/pull/536) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h index 9cc98aab..622acc29 100644 --- a/Source/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -315,6 +315,18 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable id)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer; +/** + * @abstract Indicates that the receiver is about to display. + * + * @discussion Deprecated in 2.5. + * + * @discussion Subclasses may override this method to be notified when display (asynchronous or synchronous) is + * about to begin. + * + * @note Called on the main thread only + */ +- (void)displayWillStart ASDISPLAYNODE_REQUIRES_SUPER ASDISPLAYNODE_DEPRECATED_MSG("Use displayWillStartAsynchronously: instead."); + /** * @abstract Indicates that the receiver is about to display. * @@ -323,7 +335,6 @@ NS_ASSUME_NONNULL_BEGIN * * @note Called on the main thread only */ -- (void)displayWillStart ASDISPLAYNODE_REQUIRES_SUPER; - (void)displayWillStartAsynchronously:(BOOL)asynchronously ASDISPLAYNODE_REQUIRES_SUPER; /** diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index f91b3384..e1f1d143 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -1533,7 +1533,11 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, - (void)willDisplayAsyncLayer:(_ASDisplayLayer *)layer asynchronously:(BOOL)asynchronously { // Subclass hook. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [self displayWillStart]; +#pragma clang diagnostic pop + [self displayWillStartAsynchronously:asynchronously]; } @@ -1546,7 +1550,6 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer, - (void)displayWillStart {} - (void)displayWillStartAsynchronously:(BOOL)asynchronously { - [self displayWillStart]; // Subclass override ASDisplayNodeAssertMainThread(); ASDisplayNodeLogEvent(self, @"displayWillStart"); diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index 97652ec5..81f3f43c 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -261,11 +261,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent return (self.image == nil && self.animatedImage == nil && self.imageIdentifiers.count > 0); } -/* displayWillStart in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary +/* displayWillStartAsynchronously in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary in ASNetworkImageNode as well. */ -- (void)displayWillStart +- (void)displayWillStartAsynchronously:(BOOL)asynchronously { - [super displayWillStart]; + [super displayWillStartAsynchronously:asynchronously]; [self didEnterPreloadState]; diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index c68cb792..25f31ae0 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -313,7 +313,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; return (self.image == nil && self.animatedImage == nil && _URL != nil); } -/* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary +/* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary in ASMultiplexImageNode as well. */ - (void)displayWillStartAsynchronously:(BOOL)asynchronously { diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 999c2552..e22f4614 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -335,7 +335,7 @@ return; } - ASDisplayNodeAssert(_layer, @"Expect _layer to be not nil"); + ASDisplayNodeAssert(layer, @"Expect _layer to be not nil"); // This block is called back on the main thread after rendering at the completion of the current async transaction, or immediately if !asynchronously asyncdisplaykit_async_transaction_operation_completion_block_t completionBlock = ^(id value, BOOL canceled){ From 3c77d4a5da44c46c7b80b2a627c95389b7d6352d Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 11 Sep 2017 11:12:45 -0700 Subject: [PATCH 14/22] Adds support for specifying a quality indexed array of URLs (#557) * Add support for downloading a set of URLs on ASNetworkImageNode * Should be building now; * Remove old unused code * Add a changelog message * Bump PINRemoteImage * Huy's comments --- CHANGELOG.md | 1 + Cartfile | 2 +- Source/ASNetworkImageNode.h | 9 + Source/ASNetworkImageNode.mm | 182 +++++++++++++------- Source/Details/ASImageProtocols.h | 30 +++- Source/Details/ASPINRemoteImageDownloader.m | 101 +++++++---- Texture.podspec | 2 +- 7 files changed, 228 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b69fcd8..1f086bd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Fixed a memory corruption issue in the ASImageNode display system. [Adlai Holler](https://github.com/Adlai-Holler) [#555](https://github.com/TextureGroup/Texture/pull/555) - [Breaking] Rename ASCollectionGalleryLayoutSizeProviding to ASCollectionGalleryLayoutPropertiesProviding. Besides a fixed item size, it now can provide interitem and line spacings, as well as section inset [Huy Nguyen](https://github.com/nguyenhuy) [#496](https://github.com/TextureGroup/Texture/pull/496) [#533](https://github.com/TextureGroup/Texture/pull/533) - Deprecate `-[ASDisplayNode displayWillStart]` in favor of `-displayWillStartAsynchronously:` [Huy Nguyen](https://github.com/nguyenhuy) [536](https://github.com/TextureGroup/Texture/pull/536) +- Add support for URLs on ASNetworkImageNode. [Garrett Moon](https://github.com/garrettmoon) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Cartfile b/Cartfile index 93ba6743..bdcfff60 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ -github "pinterest/PINRemoteImage" "3.0.0-beta.11" +github "pinterest/PINRemoteImage" "3.0.0-beta.12" github "pinterest/PINCache" "3.0.1-beta.5" diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index a438b422..dc911bed 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -82,6 +82,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, nonatomic, strong, readwrite) NSURL *URL; +/** + * An array of URLs of increasing cost to download. + * + * @discussion By setting an array of URLs, the image property of this node will be managed internally. This means previously + * directly set images to the image property will be cleared out and replaced by the placeholder () image + * while loading and the final image after the new image data was downloaded and processed. + */ +@property (nullable, nonatomic, strong, readwrite) NSArray *URLs; + /** * Download and display a new image. * diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 25f31ae0..0e9c1fb1 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -39,7 +39,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; // Only access any of these with __instanceLock__. __weak id _delegate; - NSURL *_URL; + NSArray *_URLs; UIImage *_defaultImage; NSUUID *_cacheUUID; @@ -68,6 +68,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; unsigned int downloaderImplementsSetPriority:1; unsigned int downloaderImplementsAnimatedImage:1; unsigned int downloaderImplementsCancelWithResume:1; + unsigned int downloaderImplementsDownloadURLs:1; } _downloaderFlags; // Immutable and set on init only. We don't need to lock in this case. @@ -75,6 +76,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; struct { unsigned int cacheSupportsClearing:1; unsigned int cacheSupportsSynchronousFetch:1; + unsigned int cacheSupportsCachedURLs:1; } _cacheFlags; } @@ -96,9 +98,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; _downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)]; + _downloaderFlags.downloaderImplementsDownloadURLs = [downloader respondsToSelector:@selector(downloadImageWithURLs:callbackQueue:downloadProgress:completion:)]; _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; + _cacheFlags.cacheSupportsCachedURLs = [cache respondsToSelector:@selector(cachedImageWithURLs:callbackQueue:completion:)]; _shouldCacheImage = YES; _shouldRenderProgressImages = YES; @@ -136,8 +140,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; BOOL shouldCancelAndClear = imageWasSetExternally && (imageWasSetExternally != _imageWasSetExternally); _imageWasSetExternally = imageWasSetExternally; if (shouldCancelAndClear) { - ASDisplayNodeAssertNil(_URL, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first."); - _URL = nil; + ASDisplayNodeAssert(_URLs == nil || _URLs.count == 0, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first."); + _URLs = nil; [self _locked_cancelDownloadAndClearImageWithResumePossibility:NO]; } @@ -158,15 +162,38 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)setURL:(NSURL *)URL { - [self setURL:URL resetToDefault:YES]; + if (URL) { + [self setURLs:@[URL]]; + } else { + [self setURLs:nil]; + } } - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset +{ + if (URL) { + [self setURLs:@[URL] resetToDefault:reset]; + } else { + [self setURLs:nil resetToDefault:reset]; + } +} + +- (NSURL *)URL +{ + return [self.URLs lastObject]; +} + +- (void)setURLs:(NSArray *)URLs +{ + [self setURLs:URLs resetToDefault:YES]; +} + +- (void)setURLs:(NSArray *)URLs resetToDefault:(BOOL)reset { { ASDN::MutexLocker l(__instanceLock__); - if (ASObjectIsEqual(URL, _URL)) { + if (ASObjectIsEqual(URLs, _URLs)) { return; } @@ -175,25 +202,25 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; _imageWasSetExternally = NO; [self _locked_cancelImageDownloadWithResumePossibility:NO]; - + _imageLoaded = NO; - _URL = URL; + _URLs = URLs; - BOOL hasURL = (_URL == nil); + BOOL hasURL = (_URLs.count == 0); if (reset || hasURL) { [self _locked_setCurrentImageQuality:(hasURL ? 0.0 : 1.0)]; [self _locked__setImage:_defaultImage]; } } - + [self setNeedsPreload]; } -- (NSURL *)URL +- (NSArray *)URLs { ASDN::MutexLocker l(__instanceLock__); - return _URL; + return _URLs; } - (void)setDefaultImage:(UIImage *)defaultImage @@ -212,7 +239,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; _defaultImage = defaultImage; if (!_imageLoaded) { - [self _locked_setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; + [self _locked_setCurrentImageQuality:((_URLs.count == 0) ? 0.0 : 1.0)]; [self _locked__setImage:defaultImage]; } @@ -310,7 +337,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (BOOL)placeholderShouldPersist { ASDN::MutexLocker l(__instanceLock__); - return (self.image == nil && self.animatedImage == nil && _URL != nil); + return (self.image == nil && self.animatedImage == nil && _URLs.count != 0); } /* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary @@ -322,13 +349,16 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { ASDN::MutexLocker l(__instanceLock__); - if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { - UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; - if (result) { - [self _locked_setCurrentImageQuality:1.0]; - [self _locked__setImage:result]; - - _imageLoaded = YES; + if (_imageLoaded == NO && _URLs.count > 0 && _downloadIdentifier == nil) { + for (NSURL *url in [_URLs reverseObjectEnumerator]) { + UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; + if (result) { + [self _locked_setCurrentImageQuality:1.0]; + [self _locked__setImage:result]; + + _imageLoaded = YES; + break; + } } } } @@ -510,9 +540,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; _imageLoaded = NO; if (_cacheFlags.cacheSupportsClearing) { - if (_URL != nil) { - as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URL); - [_cache clearFetchedImageFromCacheWithURL:_URL]; + if (_URLs.count != 0) { + as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URLs); + for (NSURL *url in _URLs) { + [_cache clearFetchedImageFromCacheWithURL:url]; + } } } } @@ -546,7 +578,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished { ASPerformBlockOnBackgroundThread(^{ - NSURL *url; + NSArray *urls; id downloadIdentifier; BOOL cancelAndReattempt = NO; @@ -555,23 +587,34 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; // it and try again. { ASDN::MutexLocker l(__instanceLock__); - url = _URL; + urls = _URLs; } - - downloadIdentifier = [_downloader downloadImageWithURL:url - callbackQueue:dispatch_get_main_queue() - downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier); - } - }]; + if (_downloaderFlags.downloaderImplementsDownloadURLs) { + downloadIdentifier = [_downloader downloadImageWithURLs:urls + callbackQueue:dispatch_get_main_queue() + downloadProgress:NULL + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { + if (finished != NULL) { + finished(imageContainer, error, downloadIdentifier); + } + }]; + } else { + downloadIdentifier = [_downloader downloadImageWithURL:[urls lastObject] + callbackQueue:dispatch_get_main_queue() + downloadProgress:NULL + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { + if (finished != NULL) { + finished(imageContainer, error, downloadIdentifier); + } + }]; + } + as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); { ASDN::MutexLocker l(__instanceLock__); - if (ASObjectIsEqual(_URL, url)) { + if (ASObjectIsEqual(_URLs, urls)) { // The download we kicked off is correct, no need to do any more work. _downloadIdentifier = downloadIdentifier; } else { @@ -600,34 +643,36 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; __weak id delegate = _delegate; BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData; BOOL isImageLoaded = _imageLoaded; - NSURL *URL = _URL; + NSArray *URLs = _URLs; id currentDownloadIdentifier = _downloadIdentifier; __instanceLock__.unlock(); - if (!isImageLoaded && URL != nil && currentDownloadIdentifier == nil) { + if (!isImageLoaded && URLs.count > 0 && currentDownloadIdentifier == nil) { if (delegateDidStartFetchingData) { [delegate imageNodeDidStartFetchingData:self]; } - if (URL.isFileURL) { + // We only support file URLs if there is one URL currently + if (URLs.count == 1 && [URLs lastObject].isFileURL) { dispatch_async(dispatch_get_main_queue(), ^{ ASDN::MutexLocker l(__instanceLock__); // Bail out if not the same URL anymore - if (!ASObjectIsEqual(URL, _URL)) { + if (!ASObjectIsEqual(URLs, _URLs)) { return; } + NSURL *URL = [URLs lastObject]; if (_shouldCacheImage) { - [self _locked__setImage:[UIImage imageNamed:_URL.path.lastPathComponent]]; + [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; } else { // First try to load the path directly, for efficiency assuming a developer who // doesn't want caching is trying to be as minimal as possible. - UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path]; + UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:URL.path]; if (nonAnimatedImage == nil) { // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; + NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil]; if (filename != nil) { nonAnimatedImage = [UIImage imageWithContentsOfFile:filename]; } @@ -636,7 +681,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; // If the file may be an animated gif and then created an animated image. id animatedImage = nil; if (_downloaderFlags.downloaderImplementsAnimatedImage) { - NSData *data = [NSData dataWithContentsOfURL:_URL]; + NSData *data = [NSData dataWithContentsOfURL:URL]; if (data != nil) { animatedImage = [_downloader animatedImageWithData:data]; @@ -671,7 +716,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; return; } - as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); + as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); // Grab the lock for the rest of the block ASDN::MutexLocker l(strongSelf->__instanceLock__); @@ -714,26 +759,35 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; _cacheUUID = cacheUUID; __instanceLock__.unlock(); - as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL); - [_cache cachedImageWithURL:URL - callbackQueue:dispatch_get_main_queue() - completion:^(id imageContainer) { - // If the cache UUID changed, that means this request was cancelled. - __instanceLock__.lock(); - NSUUID *currentCacheUUID = _cacheUUID; - __instanceLock__.unlock(); - - if (!ASObjectIsEqual(currentCacheUUID, cacheUUID)) { - return; - } - - if ([imageContainer asdk_image] == nil && _downloader != nil) { - [self _downloadImageWithCompletion:finished]; - } else { - as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); - finished(imageContainer, nil, nil); - } - }]; + as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ urls: %@", self, URLs); + + ASImageCacherCompletion completion = ^(id imageContainer) { + // If the cache UUID changed, that means this request was cancelled. + __instanceLock__.lock(); + NSUUID *currentCacheUUID = _cacheUUID; + __instanceLock__.unlock(); + + if (!ASObjectIsEqual(currentCacheUUID, cacheUUID)) { + return; + } + + if ([imageContainer asdk_image] == nil && _downloader != nil) { + [self _downloadImageWithCompletion:finished]; + } else { + as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); + finished(imageContainer, nil, nil); + } + }; + + if (_cacheFlags.cacheSupportsCachedURLs) { + [_cache cachedImageWithURLs:URLs + callbackQueue:dispatch_get_main_queue() + completion:completion]; + } else { + [_cache cachedImageWithURL:[URLs lastObject] + callbackQueue:dispatch_get_main_queue() + completion:completion]; + } } else { [self _downloadImageWithCompletion:finished]; } diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index 3fdf321e..bc671866 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -37,7 +37,7 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i @param URL The URL of the image to retrieve from the cache. @param callbackQueue The queue to call `completion` on. @param completion The block to be called when the cache has either hit or missed. - @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block + @discussion If `URL` is nil, `completion` should be invoked immediately with a nil image. This method should not block the calling thread as it is likely to be called from the main thread. */ - (void)cachedImageWithURL:(NSURL *)URL @@ -66,6 +66,19 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i */ - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL; +/** + @abstract Attempts to fetch an image with the given URLs from the cache in reverse order. + @param URLs The URLs of the image to retrieve from the cache. + @param callbackQueue The queue to call `completion` on. + @param completion The block to be called when the cache has either hit or missed. + @discussion If `URLs` is nil or empty, `completion` should be invoked immediately with a nil image. This method should not block + the calling thread as it is likely to be called from the main thread. + @see downloadImageWithURLs:callbackQueue:downloadProgress:completion: + */ +- (void)cachedImageWithURLs:(NSArray *)URLs + callbackQueue:(dispatch_queue_t)callbackQueue + completion:(ASImageCacherCompletion)completion; + @end /** @@ -154,6 +167,21 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { - (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier; +/** + @abstract Downloads an image from a list of URLs depending on previously observed network speed conditions. + @param URLs An array of URLs ordered by the cost of downloading them, the URL at index 0 being the lowest cost. + @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. + @param downloadProgress The block to be invoked when the download of `URL` progresses. + @param completion The block to be invoked when the download has completed, or has failed. + @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. + @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must + retain the identifier if you wish to use it later. + */ +- (nullable id)downloadImageWithURLs:(NSArray *)URLs + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion; + @end @protocol ASAnimatedImageProtocol diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index 3fd3c86d..0e6c6adf 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -196,6 +196,23 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil; } } +- (void)cachedImageWithURLs:(NSArray *)URLs + callbackQueue:(dispatch_queue_t)callbackQueue + completion:(ASImageCacherCompletion)completion +{ + [self cachedImageWithURL:[URLs lastObject] + callbackQueue:callbackQueue + completion:^(id _Nullable imageFromCache) { + if (imageFromCache.asdk_image == nil && URLs.count > 1) { + [self cachedImageWithURLs:[URLs subarrayWithRange:NSMakeRange(0, URLs.count - 1)] + callbackQueue:callbackQueue + completion:completion]; + } else { + completion(imageFromCache); + } + }]; +} + - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL { if ([self sharedImageManagerSupportsMemoryRemoval]) { @@ -210,43 +227,63 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil; downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion; { - return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode progressDownload:^(int64_t completedBytes, int64_t totalBytes) { - if (downloadProgress == nil) { return; } + NSArray *URLs = nil; + if (URL) { + URLs = @[URL]; + } + return [self downloadImageWithURLs:URLs callbackQueue:callbackQueue downloadProgress:downloadProgress completion:completion]; +} - /// If we're targeting the main queue and we're on the main thread, call immediately. - if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { - downloadProgress(completedBytes / (CGFloat)totalBytes); - } else { - dispatch_async(callbackQueue, ^{ - downloadProgress(completedBytes / (CGFloat)totalBytes); - }); - } - } completion:^(PINRemoteImageManagerResult * _Nonnull result) { - /// If we're targeting the main queue and we're on the main thread, complete immediately. - if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID); - } else { - completion(result.image, result.error, result.UUID); - } -#else - completion(result.image, result.error, result.UUID); -#endif - } else { - dispatch_async(callbackQueue, ^{ -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID); +- (nullable id)downloadImageWithURLs:(NSArray *)URLs + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion +{ + PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { + if (downloadProgress == nil) { return; } + + /// If we're targeting the main queue and we're on the main thread, call immediately. + if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { + downloadProgress(completedBytes / (CGFloat)totalBytes); } else { - completion(result.image, result.error, result.UUID); + dispatch_async(callbackQueue, ^{ + downloadProgress(completedBytes / (CGFloat)totalBytes); + }); } + }; + + PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) { + /// If we're targeting the main queue and we're on the main thread, complete immediately. + if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { +#if PIN_ANIMATED_AVAILABLE + if (result.alternativeRepresentation) { + completion(result.alternativeRepresentation, result.error, result.UUID); + } else { + completion(result.image, result.error, result.UUID); + } #else - completion(result.image, result.error, result.UUID); + completion(result.image, result.error, result.UUID); #endif - }); - } - }]; + } else { + dispatch_async(callbackQueue, ^{ +#if PIN_ANIMATED_AVAILABLE + if (result.alternativeRepresentation) { + completion(result.alternativeRepresentation, result.error, result.UUID); + } else { + completion(result.image, result.error, result.UUID); + } +#else + completion(result.image, result.error, result.UUID); +#endif + }); + } + }; + + return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs + options:PINRemoteImageManagerDownloadOptionsSkipDecode + progressImage:nil + progressDownload:progressDownload + completion:imageCompletion]; } - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier diff --git a/Texture.podspec b/Texture.podspec index 9c53d9f4..1fcc6cf0 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -45,7 +45,7 @@ Pod::Spec.new do |spec| end spec.subspec 'PINRemoteImage' do |pin| - pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.11' + pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.12' pin.dependency 'PINRemoteImage/PINCache' pin.dependency 'Texture/Core' end From fcee108af536571a4abc8b41da6472e75cef1b9e Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 11 Sep 2017 19:20:32 +0100 Subject: [PATCH 15/22] [ASCollectionNode][ASTableNode] Add content inset bridging property (#560) * Add content inset bridging property to table and collection nodes * Fix CHANGELOG * Fix typo * Minor fixes --- CHANGELOG.md | 2 +- Source/ASCollectionNode.h | 5 +++ Source/ASCollectionNode.mm | 23 +++++++++++ Source/ASCollectionView.h | 5 +++ Source/ASPagerNode.m | 2 +- Source/ASTableNode.h | 5 +++ Source/ASTableNode.mm | 40 +++++++++++++++---- Source/ASTableView.h | 5 +++ .../Private/ASCollectionView+Undeprecated.h | 2 + Source/Private/ASTableView+Undeprecated.h | 1 + Tests/ASPagerNodeTests.m | 4 +- 11 files changed, 83 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f086bd7..a8a96368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - Add -[ASDisplayNode detailedLayoutDescription] property to aid debugging. [Adlai Holler](https://github.com/Adlai-Holler) [#476](https://github.com/TextureGroup/Texture/pull/476) - Fix an issue that causes calculatedLayoutDidChange being called needlessly. [Huy Nguyen](https://github.com/nguyenhuy) [#490](https://github.com/TextureGroup/Texture/pull/490) - Negate iOS 11 automatic estimated table row heights. [Christian Selig](https://github.com/christianselig) [#485](https://github.com/TextureGroup/Texture/pull/485) -- Add content offset bridging property to ASTableNode and ASCollectionNode. Deprecate related methods in ASTableView and ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#460](https://github.com/TextureGroup/Texture/pull/460) +- Add content inset and offset bridging properties to ASTableNode and ASCollectionNode. Deprecate related properties and methods in ASTableView and ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#460](https://github.com/TextureGroup/Texture/pull/460) [#560](https://github.com/TextureGroup/Texture/pull/560) - Remove re-entrant access to self.view when applying initial pending state. [Adlai Holler](https://github.com/Adlai-Holler) [#510](https://github.com/TextureGroup/Texture/pull/510) - Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513) - Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520) diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 68e5d752..6913f872 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -128,6 +128,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, weak) id layoutInspector; +/** + * The distance that the content view is inset from the collection node edges. Defaults to UIEdgeInsetsZero. + */ +@property (nonatomic, assign) UIEdgeInsets contentInset; + /** * The offset of the content view's origin from the collection node's origin. Defaults to CGPointZero. */ diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index e5cde4c8..0be5cda5 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -49,6 +49,7 @@ @property (nonatomic, assign) BOOL usesSynchronousDataLoading; @property (nonatomic, assign) CGFloat leadingScreensForBatching; @property (weak, nonatomic) id layoutInspector; +@property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) CGPoint contentOffset; @property (nonatomic, assign) BOOL animatesContentOffset; @end @@ -63,6 +64,7 @@ _allowsSelection = YES; _allowsMultipleSelection = NO; _inverted = NO; + _contentInset = UIEdgeInsetsZero; _contentOffset = CGPointZero; _animatesContentOffset = NO; } @@ -188,6 +190,7 @@ view.allowsMultipleSelection = pendingState.allowsMultipleSelection; view.usesSynchronousDataLoading = pendingState.usesSynchronousDataLoading; view.layoutInspector = pendingState.layoutInspector; + view.contentInset = pendingState.contentInset; self.pendingState = nil; if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { @@ -440,6 +443,25 @@ } } +- (void)setContentInset:(UIEdgeInsets)contentInset +{ + if ([self pendingState]) { + _pendingState.contentInset = contentInset; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.contentInset = contentInset; + } +} + +- (UIEdgeInsets)contentInset +{ + if ([self pendingState]) { + return _pendingState.contentInset; + } else { + return self.view.contentInset; + } +} + - (void)setContentOffset:(CGPoint)contentOffset { [self setContentOffset:contentOffset animated:NO]; @@ -451,6 +473,7 @@ _pendingState.contentOffset = contentOffset; _pendingState.animatesContentOffset = animated; } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); [self.view setContentOffset:contentOffset animated:animated]; } } diff --git a/Source/ASCollectionView.h b/Source/ASCollectionView.h index 2c1f8798..ba9dda29 100644 --- a/Source/ASCollectionView.h +++ b/Source/ASCollectionView.h @@ -141,6 +141,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic) BOOL zeroContentInsets ASDISPLAYNODE_DEPRECATED_MSG("Set automaticallyAdjustsScrollViewInsets=NO on your view controller instead."); +/** + * The distance that the content view is inset from the collection view edges. Defaults to UIEdgeInsetsZero. + */ +@property (nonatomic, assign) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode property instead"); + /** * The point at which the origin of the content view is offset from the origin of the collection view. */ diff --git a/Source/ASPagerNode.m b/Source/ASPagerNode.m index 4d8195a1..719a4568 100644 --- a/Source/ASPagerNode.m +++ b/Source/ASPagerNode.m @@ -115,7 +115,7 @@ - (CGSize)pageSize { - UIEdgeInsets contentInset = self.view.contentInset; + UIEdgeInsets contentInset = self.contentInset; CGSize pageSize = self.bounds.size; pageSize.height -= (contentInset.top + contentInset.bottom); return pageSize; diff --git a/Source/ASTableNode.h b/Source/ASTableNode.h index 08b26a6f..8322faaf 100644 --- a/Source/ASTableNode.h +++ b/Source/ASTableNode.h @@ -55,6 +55,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL inverted; +/** + * The distance that the content view is inset from the table node edges. Defaults to UIEdgeInsetsZero. + */ +@property (nonatomic, assign) UIEdgeInsets contentInset; + /** * The offset of the content view's origin from the table node's origin. Defaults to CGPointZero. */ diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index b4a3e06d..d68e4a9d 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -43,6 +43,7 @@ @property (nonatomic, assign) BOOL allowsMultipleSelectionDuringEditing; @property (nonatomic, assign) BOOL inverted; @property (nonatomic, assign) CGFloat leadingScreensForBatching; +@property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) CGPoint contentOffset; @property (nonatomic, assign) BOOL animatesContentOffset; @property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset; @@ -60,6 +61,7 @@ _allowsMultipleSelectionDuringEditing = NO; _inverted = NO; _leadingScreensForBatching = 2; + _contentInset = UIEdgeInsetsZero; _contentOffset = CGPointZero; _animatesContentOffset = NO; _automaticallyAdjustsContentOffset = NO; @@ -113,17 +115,20 @@ if (_pendingState) { _ASTablePendingState *pendingState = _pendingState; - self.pendingState = nil; - view.asyncDelegate = pendingState.delegate; - view.asyncDataSource = pendingState.dataSource; - view.inverted = pendingState.inverted; - view.allowsSelection = pendingState.allowsSelection; - view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing; - view.allowsMultipleSelection = pendingState.allowsMultipleSelection; + view.asyncDelegate = pendingState.delegate; + view.asyncDataSource = pendingState.dataSource; + view.inverted = pendingState.inverted; + view.allowsSelection = pendingState.allowsSelection; + view.allowsSelectionDuringEditing = pendingState.allowsSelectionDuringEditing; + view.allowsMultipleSelection = pendingState.allowsMultipleSelection; view.allowsMultipleSelectionDuringEditing = pendingState.allowsMultipleSelectionDuringEditing; + view.contentInset = pendingState.contentInset; + self.pendingState = nil; + if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; } + [view setContentOffset:pendingState.contentOffset animated:pendingState.animatesContentOffset]; } } @@ -237,6 +242,27 @@ } } +- (void)setContentInset:(UIEdgeInsets)contentInset +{ + _ASTablePendingState *pendingState = self.pendingState; + if (pendingState) { + pendingState.contentInset = contentInset; + } else { + ASDisplayNodeAssert(self.nodeLoaded, @"ASTableNode should be loaded if pendingState doesn't exist"); + self.view.contentInset = contentInset; + } +} + +- (UIEdgeInsets)contentInset +{ + _ASTablePendingState *pendingState = self.pendingState; + if (pendingState) { + return pendingState.contentInset; + } else { + return self.view.contentInset; + } +} + - (void)setContentOffset:(CGPoint)contentOffset { [self setContentOffset:contentOffset animated:NO]; diff --git a/Source/ASTableView.h b/Source/ASTableView.h index 04e1dbd1..ba6736eb 100644 --- a/Source/ASTableView.h +++ b/Source/ASTableView.h @@ -67,6 +67,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) CGFloat leadingScreensForBatching ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead."); +/** + * The distance that the content view is inset from the table view edges. Defaults to UIEdgeInsetsZero. + */ +@property (nonatomic, assign) UIEdgeInsets contentInset ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode property instead"); + /** * The offset of the content view's origin from the table node's origin. Defaults to CGPointZero. */ diff --git a/Source/Private/ASCollectionView+Undeprecated.h b/Source/Private/ASCollectionView+Undeprecated.h index 811ba7ba..591f49ef 100644 --- a/Source/Private/ASCollectionView+Undeprecated.h +++ b/Source/Private/ASCollectionView+Undeprecated.h @@ -75,6 +75,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id layoutInspector; +@property (nonatomic, assign) UIEdgeInsets contentInset; + @property (nonatomic, assign) CGPoint contentOffset; /** diff --git a/Source/Private/ASTableView+Undeprecated.h b/Source/Private/ASTableView+Undeprecated.h index bef11339..bab43adf 100644 --- a/Source/Private/ASTableView+Undeprecated.h +++ b/Source/Private/ASTableView+Undeprecated.h @@ -33,6 +33,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id asyncDelegate; @property (nonatomic, weak) id asyncDataSource; +@property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) CGPoint contentOffset; @property (nonatomic, assign) BOOL automaticallyAdjustsContentOffset; @property (nonatomic, assign) BOOL inverted; diff --git a/Tests/ASPagerNodeTests.m b/Tests/ASPagerNodeTests.m index 4b45a8c1..05396387 100644 --- a/Tests/ASPagerNodeTests.m +++ b/Tests/ASPagerNodeTests.m @@ -138,7 +138,7 @@ XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); XCTAssertEqual(pagerNode.contentOffset.y, 0); - XCTAssertEqual(pagerNode.view.contentInset.top, 0); + XCTAssertEqual(pagerNode.contentInset.top, 0); e = [self expectationWithDescription:@"Transition completed"]; // Push another view controller @@ -168,7 +168,7 @@ XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(node.frame)); XCTAssertEqualObjects(NSStringFromCGRect(window.bounds), NSStringFromCGRect(cell.frame)); XCTAssertEqual(pagerNode.contentOffset.y, 0); - XCTAssertEqual(pagerNode.view.contentInset.top, 0); + XCTAssertEqual(pagerNode.contentInset.top, 0); } @end From 7c7a4acf0e36d5472ae3d74e5be3e8da61224876 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 11 Sep 2017 21:32:17 +0100 Subject: [PATCH 16/22] ASCollectionLayout to exclude content inset on scrollable directions from viewport size (#562) --- CHANGELOG.md | 2 +- Source/Private/ASCollectionLayout.mm | 61 ++++++++++++++++++++++------ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8a96368..fda12670 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - Negate iOS 11 automatic estimated table row heights. [Christian Selig](https://github.com/christianselig) [#485](https://github.com/TextureGroup/Texture/pull/485) - Add content inset and offset bridging properties to ASTableNode and ASCollectionNode. Deprecate related properties and methods in ASTableView and ASCollectionView [Huy Nguyen](https://github.com/nguyenhuy) [#460](https://github.com/TextureGroup/Texture/pull/460) [#560](https://github.com/TextureGroup/Texture/pull/560) - Remove re-entrant access to self.view when applying initial pending state. [Adlai Holler](https://github.com/Adlai-Holler) [#510](https://github.com/TextureGroup/Texture/pull/510) -- Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513) +- Small improvements in ASCollectionLayout [Huy Nguyen](https://github.com/nguyenhuy) [#509](https://github.com/TextureGroup/Texture/pull/509) [#513](https://github.com/TextureGroup/Texture/pull/513) [#562]((https://github.com/TextureGroup/Texture/pull/562) - Fix retain cycle between ASImageNode and PINAnimatedImage [Phil Larson](https://github.com/plarson) [#520](https://github.com/TextureGroup/Texture/pull/520) - Change the API for disabling logging from a compiler flag to a runtime C function ASDisableLogging(). [Adlai Holler](https://github.com/Adlai-Holler) [#528](https://github.com/TextureGroup/Texture/pull/528) - Table and collection views to consider content inset when calculating (default) element size range [Huy Nguyen](https://github.com/nguyenhuy) [#525](https://github.com/TextureGroup/Texture/pull/525) diff --git a/Source/Private/ASCollectionLayout.mm b/Source/Private/ASCollectionLayout.mm index 97451c32..59273c97 100644 --- a/Source/Private/ASCollectionLayout.mm +++ b/Source/Private/ASCollectionLayout.mm @@ -69,18 +69,35 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh - (ASCollectionLayoutContext *)layoutContextWithElements:(ASElementMap *)elements { ASDisplayNodeAssertMainThread(); - CGSize viewportSize = [self _viewportSize]; - CGPoint contentOffset = _collectionNode.contentOffset; + + Class layoutDelegateClass = [_layoutDelegate class]; + ASCollectionLayoutCache *layoutCache = _layoutCache; + ASCollectionNode *collectionNode = _collectionNode; + if (collectionNode == nil) { + return [[ASCollectionLayoutContext alloc] initWithViewportSize:CGSizeZero + initialContentOffset:CGPointZero + scrollableDirections:ASScrollDirectionNone + elements:[[ASElementMap alloc] init] + layoutDelegateClass:layoutDelegateClass + layoutCache:layoutCache + additionalInfo:nil]; + } + + ASScrollDirection scrollableDirections = [_layoutDelegate scrollableDirections]; + CGSize viewportSize = [ASCollectionLayout _viewportSizeForCollectionNode:collectionNode scrollableDirections:scrollableDirections]; + CGPoint contentOffset = collectionNode.contentOffset; + id additionalInfo = nil; if (_layoutDelegateFlags.implementsAdditionalInfoForLayoutWithElements) { additionalInfo = [_layoutDelegate additionalInfoForLayoutWithElements:elements]; } + return [[ASCollectionLayoutContext alloc] initWithViewportSize:viewportSize initialContentOffset:contentOffset - scrollableDirections:[_layoutDelegate scrollableDirections] + scrollableDirections:scrollableDirections elements:elements - layoutDelegateClass:[_layoutDelegate class] - layoutCache:_layoutCache + layoutDelegateClass:layoutDelegateClass + layoutCache:layoutCache additionalInfo:additionalInfo]; } @@ -208,21 +225,41 @@ static const ASScrollDirection kASStaticScrollDirection = (ASScrollDirectionRigh - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { - return (! CGSizeEqualToSize([self _viewportSize], newBounds.size)); + return (! CGSizeEqualToSize([ASCollectionLayout _boundsForCollectionNode:_collectionNode], newBounds.size)); } #pragma mark - Private methods -- (CGSize)_viewportSize ++ (CGSize)_boundsForCollectionNode:(nonnull ASCollectionNode *)collectionNode { - ASCollectionNode *collectionNode = _collectionNode; - if (collectionNode != nil && !collectionNode.isNodeLoaded) { + if (collectionNode == nil) { + return CGSizeZero; + } + + if (!collectionNode.isNodeLoaded) { // TODO consider calculatedSize as well return collectionNode.threadSafeBounds.size; - } else { - ASDisplayNodeAssertMainThread(); - return self.collectionView.bounds.size; } + + ASDisplayNodeAssertMainThread(); + return collectionNode.view.bounds.size; +} + ++ (CGSize)_viewportSizeForCollectionNode:(nonnull ASCollectionNode *)collectionNode scrollableDirections:(ASScrollDirection)scrollableDirections +{ + if (collectionNode == nil) { + return CGSizeZero; + } + + CGSize result = [ASCollectionLayout _boundsForCollectionNode:collectionNode]; + // TODO: Consider using adjustedContentInset on iOS 11 and later, to include the safe area of the scroll view + UIEdgeInsets contentInset = collectionNode.contentInset; + if (ASScrollDirectionContainsHorizontalDirection(scrollableDirections)) { + result.height -= (contentInset.top + contentInset.bottom); + } else { + result.width -= (contentInset.left + contentInset.right); + } + return result; } /** From 002c2d6978efab95207077811fbccd2d9cccda88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Est=C3=A9banez=20Tasc=C3=B3n?= Date: Mon, 11 Sep 2017 22:32:25 +0200 Subject: [PATCH 17/22] Mark ASRunLoopQueue as drained if it contains only NULLs (#558) * Mark ASRunLoopQueue as drained if it contains only NULLs * Update CHANGELOG.md * Cover ASRunLoopQueue with tests * Include PR link in CHANGELOG.md * Replace license header of ASRunLoopQueueTests.m with correct one * Insert a nil in _internalQueue to ensure compaction, instead of maintaining a state for _isQueueDrained --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 + CHANGELOG.md | 1 + Source/ASRunLoopQueue.h | 2 + Source/ASRunLoopQueue.mm | 20 ++- Tests/ASRunLoopQueueTests.m | 160 ++++++++++++++++++++++ 5 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 Tests/ASRunLoopQueueTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 41389f72..068938a4 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -96,6 +96,7 @@ 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; }; + 4E9127691F64157600499623 /* ASRunLoopQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */; }; 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E181B37339C007741D0 /* ASAbstractLayoutController.mm */; }; @@ -624,6 +625,7 @@ 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableLayoutController.m; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; + 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRunLoopQueueTests.m; sourceTree = ""; }; 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "ASImageNode+AnimatedImage.mm"; sourceTree = ""; }; 68355B361CB57A5A001D4E68 /* ASPINRemoteImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPINRemoteImageDownloader.m; sourceTree = ""; }; 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageContainerProtocolCategories.h; sourceTree = ""; }; @@ -1219,6 +1221,7 @@ 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */, 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, + 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */, ); path = Tests; sourceTree = ""; @@ -2140,6 +2143,7 @@ 696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */, CC583AD81EF9BDC300134156 /* OCMockObject+ASAdditions.m in Sources */, 69FEE53D1D95A9AF0086F066 /* ASLayoutElementStyleTests.m in Sources */, + 4E9127691F64157600499623 /* ASRunLoopQueueTests.m in Sources */, CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */, CC54A81E1D7008B300296A24 /* ASDispatchTests.m in Sources */, CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index fda12670..16e65716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - [Breaking] Rename ASCollectionGalleryLayoutSizeProviding to ASCollectionGalleryLayoutPropertiesProviding. Besides a fixed item size, it now can provide interitem and line spacings, as well as section inset [Huy Nguyen](https://github.com/nguyenhuy) [#496](https://github.com/TextureGroup/Texture/pull/496) [#533](https://github.com/TextureGroup/Texture/pull/533) - Deprecate `-[ASDisplayNode displayWillStart]` in favor of `-displayWillStartAsynchronously:` [Huy Nguyen](https://github.com/nguyenhuy) [536](https://github.com/TextureGroup/Texture/pull/536) - Add support for URLs on ASNetworkImageNode. [Garrett Moon](https://github.com/garrettmoon) +- Mark ASRunLoopQueue as drained if it contains only NULLs [Cesar Estebanez](https://github.com/cesteban) [#558](https://github.com/TextureGroup/Texture/pull/558) ##2.4 - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 7ff563f4..6b08540c 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -41,6 +41,8 @@ AS_SUBCLASSING_RESTRICTED - (void)enqueue:(ObjectType)object; +@property (nonatomic, readonly) BOOL isEmpty; + @property (nonatomic, assign) NSUInteger batchSize; // Default == 1. @property (nonatomic, assign) BOOL ensureExclusiveMembership; // Default == YES. Set-like behavior. diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 004d4abb..1902ad23 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -348,12 +348,12 @@ typedef enum { { ASDN::MutexLocker l(_internalQueueLock); - // Early-exit if the queue is empty. NSInteger internalQueueCount = _internalQueue.count; + // Early-exit if the queue is empty. if (internalQueueCount == 0) { return; } - + ASSignpostStart(ASSignpostRunLoopQueueBatch); // Snatch the next batch of items. @@ -382,6 +382,14 @@ typedef enum { } } + if (foundItemCount == 0) { + // If _internalQueue holds weak references, and all of them just become NULL, then the array + // is never marked as needsCompletion, and compact will return early, not removing the NULL's. + // Inserting a NULL here ensures the compaction will take place. + // See http://www.openradar.me/15396578 and https://stackoverflow.com/a/40274426/1136669 + [_internalQueue addPointer:NULL]; + } + [_internalQueue compact]; if (_internalQueue.count == 0) { isQueueDrained = YES; @@ -434,10 +442,16 @@ typedef enum { if (!foundObject) { [_internalQueue addPointer:(__bridge void *)object]; - + CFRunLoopSourceSignal(_runLoopSource); CFRunLoopWakeUp(_runLoop); } } +- (BOOL)isEmpty +{ + ASDN::MutexLocker l(_internalQueueLock); + return _internalQueue.count == 0; +} + @end diff --git a/Tests/ASRunLoopQueueTests.m b/Tests/ASRunLoopQueueTests.m new file mode 100644 index 00000000..ccf15ae7 --- /dev/null +++ b/Tests/ASRunLoopQueueTests.m @@ -0,0 +1,160 @@ +// +// ASRunLoopQueueTests.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run for one millisecond each time. + +@interface ASRunLoopQueueTests : XCTestCase + +@end + +@implementation ASRunLoopQueueTests + +#pragma mark enqueue tests + +- (void)testEnqueueNilObjectsToQueue +{ + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:nil]; + id object = nil; + [queue enqueue:object]; + XCTAssertTrue(queue.isEmpty); +} + +- (void)testEnqueueSameObjectTwiceToDefaultQueue +{ + id object = [[NSObject alloc] init]; + __unsafe_unretained id weakObject = object; + __block NSUInteger dequeuedCount = 0; + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { + if (dequeuedItem == weakObject) { + dequeuedCount++; + } + }]; + [queue enqueue:object]; + [queue enqueue:object]; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssert(dequeuedCount == 1); +} + +- (void)testEnqueueSameObjectTwiceToNonExclusiveMembershipQueue +{ + id object = [[NSObject alloc] init]; + __unsafe_unretained id weakObject = object; + __block NSUInteger dequeuedCount = 0; + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { + if (dequeuedItem == weakObject) { + dequeuedCount++; + } + }]; + queue.ensureExclusiveMembership = NO; + [queue enqueue:object]; + [queue enqueue:object]; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssert(dequeuedCount == 2); +} + +#pragma mark processQueue tests + +- (void)testDefaultQueueProcessObjectsOneAtATime +{ + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { + [NSThread sleepForTimeInterval:kRunLoopRunTime * 2]; // So each element takes more time than the available + }]; + [queue enqueue:[[NSObject alloc] init]]; + [queue enqueue:[[NSObject alloc] init]]; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssertFalse(queue.isEmpty); +} + +- (void)testQueueProcessObjectsInBatchesOfSpecifiedSize +{ + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { + [NSThread sleepForTimeInterval:kRunLoopRunTime * 2]; // So each element takes more time than the available + }]; + queue.batchSize = 2; + [queue enqueue:[[NSObject alloc] init]]; + [queue enqueue:[[NSObject alloc] init]]; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssertTrue(queue.isEmpty); +} + +- (void)testQueueOnlySendsIsDrainedForLastObjectInBatch +{ + id objectA = [[NSObject alloc] init]; + id objectB = [[NSObject alloc] init]; + __unsafe_unretained id weakObjectA = objectA; + __unsafe_unretained id weakObjectB = objectB; + __block BOOL isQueueDrainedWhenProcessingA = NO; + __block BOOL isQueueDrainedWhenProcessingB = NO; + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { + if (dequeuedItem == weakObjectA) { + isQueueDrainedWhenProcessingA = isQueueDrained; + } else if (dequeuedItem == weakObjectB) { + isQueueDrainedWhenProcessingB = isQueueDrained; + } + }]; + queue.batchSize = 2; + [queue enqueue:objectA]; + [queue enqueue:objectB]; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssertFalse(isQueueDrainedWhenProcessingA); + XCTAssertTrue(isQueueDrainedWhenProcessingB); +} + +#pragma mark strong/weak tests + +- (void)testStrongQueueRetainsObjects +{ + id object = [[NSObject alloc] init]; + __unsafe_unretained id weakObject = object; + __block BOOL didProcessObject = NO; + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:YES handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { + if (dequeuedItem == weakObject) { + didProcessObject = YES; + } + }]; + [queue enqueue:object]; + object = nil; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssertTrue(didProcessObject); +} + +- (void)testWeakQueueDoesNotRetainsObjects +{ + id object = [[NSObject alloc] init]; + __unsafe_unretained id weakObject = object; + __block BOOL didProcessObject = NO; + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:NO handler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { + if (dequeuedItem == weakObject) { + didProcessObject = YES; + } + }]; + [queue enqueue:object]; + object = nil; + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssertFalse(didProcessObject); +} + +- (void)testWeakQueueWithAllDeallocatedObjectsIsDrained +{ + ASRunLoopQueue *queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() retainObjects:NO handler:nil]; + id object = [[NSObject alloc] init]; + [queue enqueue:object]; + object = nil; + XCTAssertFalse(queue.isEmpty); + [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopRunTime]]; + XCTAssertTrue(queue.isEmpty); +} + +@end From 9df6909d71f28100e32cc846ff12a1d2be05512a Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 11 Sep 2017 21:33:30 +0100 Subject: [PATCH 18/22] [ASImageNode] Always dealloc images in a background queue (#561) * ASImageNode to always dealloc its images in a background queue * Update CHANGELOG --- CHANGELOG.md | 4 +++- Source/ASImageNode.mm | 17 ++++++++++++++--- Source/ASNetworkImageNode.mm | 16 +--------------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16e65716..648ec0b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,10 @@ - [ASDisplayNode] Ensure `-displayWillStartAsynchronously:` and `-displayDidFinish` are invoked on rasterized subnodes. [Eric Scheers](https://github.com/smeis) [#532](https://github.com/TextureGroup/Texture/pull/532) - Fixed a memory corruption issue in the ASImageNode display system. [Adlai Holler](https://github.com/Adlai-Holler) [#555](https://github.com/TextureGroup/Texture/pull/555) - [Breaking] Rename ASCollectionGalleryLayoutSizeProviding to ASCollectionGalleryLayoutPropertiesProviding. Besides a fixed item size, it now can provide interitem and line spacings, as well as section inset [Huy Nguyen](https://github.com/nguyenhuy) [#496](https://github.com/TextureGroup/Texture/pull/496) [#533](https://github.com/TextureGroup/Texture/pull/533) -- Deprecate `-[ASDisplayNode displayWillStart]` in favor of `-displayWillStartAsynchronously:` [Huy Nguyen](https://github.com/nguyenhuy) [536](https://github.com/TextureGroup/Texture/pull/536) +- Deprecate `-[ASDisplayNode displayWillStart]` in favor of `-displayWillStartAsynchronously:` [Huy Nguyen](https://github.com/nguyenhuy) [#536](https:/ +/github.com/TextureGroup/Texture/pull/536) - Add support for URLs on ASNetworkImageNode. [Garrett Moon](https://github.com/garrettmoon) +- [ASImageNode] Always dealloc images in a background queue [Huy Nguyen](https://github.com/nguyenhuy) [#561](https://github.com/TextureGroup/Texture/pull/561) - Mark ASRunLoopQueue as drained if it contains only NULLs [Cesar Estebanez](https://github.com/cesteban) [#558](https://github.com/TextureGroup/Texture/pull/558) ##2.4 diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index dce952d2..165d7453 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -41,6 +41,8 @@ #include +static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; + typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry); @interface ASImageNodeDrawParameters : NSObject { @@ -248,11 +250,11 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry); if (ASObjectIsEqual(_image, image)) { return; } - + + UIImage *oldImage = _image; _image = image; if (image != nil) { - // We explicitly call setNeedsDisplay in this case, although we know setNeedsDisplay will be called with lock held. // Therefore we have to be careful in methods that are involved with setNeedsDisplay to not run into a deadlock [self setNeedsDisplay]; @@ -265,10 +267,19 @@ typedef void (^ASImageNodeDrawParametersBlock)(ASWeakMapEntry *entry); [self addSubnode:_debugLabelNode]; }); } - } else { self.contents = nil; } + + // Destruction of bigger images on the main thread can be expensive + // and can take some time, so we dispatch onto a bg queue to + // actually dealloc. + CGSize oldImageSize = oldImage.size; + BOOL shouldReleaseImageOnBackgroundThread = oldImageSize.width > kMinReleaseImageOnBackgroundSize.width + || oldImageSize.height > kMinReleaseImageOnBackgroundSize.height; + if (shouldReleaseImageOnBackgroundThread) { + ASPerformBackgroundDeallocation(oldImage); + } } - (UIImage *)image diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 0e9c1fb1..d9693227 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -32,8 +32,6 @@ #import #endif -static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - @interface ASNetworkImageNode () { // Only access any of these with __instanceLock__. @@ -521,21 +519,9 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; { [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; - // Destruction of bigger images on the main thread can be expensive - // and can take some time, so we dispatch onto a bg queue to - // actually dealloc. - UIImage *image = [self _locked_Image]; - UIImage *defaultImage = _defaultImage; - CGSize imageSize = image.size; - BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width || - imageSize.height > kMinReleaseImageOnBackgroundSize.height; - if (shouldReleaseImageOnBackgroundThread) { - ASPerformBackgroundDeallocation(image); - } - [self _locked_setAnimatedImage:nil]; [self _locked_setCurrentImageQuality:0.0]; - [self _locked__setImage:defaultImage]; + [self _locked__setImage:_defaultImage]; _imageLoaded = NO; From 008b847a7a66495d9a63556b106be5e1285cadaf Mon Sep 17 00:00:00 2001 From: Samuel Hsiung Date: Wed, 13 Sep 2017 09:12:46 -0700 Subject: [PATCH 19/22] Fix -[ASPagerNode view] triggering pendingState + nodeLoaded assert (#564) --- Source/ASCollectionNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 0be5cda5..3ff691c1 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -183,6 +183,7 @@ if (_pendingState) { _ASCollectionPendingState *pendingState = _pendingState; + self.pendingState = nil; view.asyncDelegate = pendingState.delegate; view.asyncDataSource = pendingState.dataSource; view.inverted = pendingState.inverted; @@ -191,7 +192,6 @@ view.usesSynchronousDataLoading = pendingState.usesSynchronousDataLoading; view.layoutInspector = pendingState.layoutInspector; view.contentInset = pendingState.contentInset; - self.pendingState = nil; if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; From b9b2d7e3cac608ec8b0b71342244206b41ade494 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Sat, 16 Sep 2017 10:32:07 -0700 Subject: [PATCH 20/22] Update yoga version (#569) * fix SIMULATE_WEB_RESPONSE not imported #449 * update yoga version --- Texture.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Texture.podspec b/Texture.podspec index 1fcc6cf0..5731208e 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -57,7 +57,7 @@ Pod::Spec.new do |spec| spec.subspec 'Yoga' do |yoga| yoga.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) YOGA=1' } - yoga.dependency 'Yoga', '1.5.0' + yoga.dependency 'Yoga', '1.6.0' yoga.dependency 'Texture/Core' end From dcaca529b46bf8804c8d34195ddee886f0d52a3a Mon Sep 17 00:00:00 2001 From: Hannah Troisi Date: Sat, 16 Sep 2017 10:36:58 -0700 Subject: [PATCH 21/22] [ASDKgram Example] fix crash on startup (#566) * fix crash on ASDKgram startup * quicker fix --- examples/ASDKgram/Sample/PhotoFeedBaseController.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/ASDKgram/Sample/PhotoFeedBaseController.m b/examples/ASDKgram/Sample/PhotoFeedBaseController.m index c901d737..1d883ba7 100644 --- a/examples/ASDKgram/Sample/PhotoFeedBaseController.m +++ b/examples/ASDKgram/Sample/PhotoFeedBaseController.m @@ -59,7 +59,7 @@ [_activityIndicatorView stopAnimating]; - [self insertNewRows:newPhotos]; + [self.tableView reloadData]; [self requestCommentsForPhotos:newPhotos]; // immediately start second larger fetch @@ -74,7 +74,8 @@ NSMutableArray *indexPaths = [NSMutableArray array]; NSInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; - for (NSInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { + NSInteger existingNumberOfPhotos = newTotalNumberOfPhotos - newPhotos.count; + for (NSInteger row = existingNumberOfPhotos; row < newTotalNumberOfPhotos; row++) { NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; [indexPaths addObject:path]; } From cfc48679bacfd649037847136dc809d339c1ffbd Mon Sep 17 00:00:00 2001 From: appleguy Date: Sun, 17 Sep 2017 22:45:39 -0700 Subject: [PATCH 22/22] [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. (#469) * [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. * Add new "version" parameter to Yoga initialization of ASDisplayNodeLayout C++ struct. --- CHANGELOG.md | 1 + Source/ASDisplayNode+Beta.h | 5 +- Source/ASDisplayNode+Yoga.mm | 89 +++++++++++++++++++++++++-------- Source/ASDisplayNode.mm | 27 ++++++---- Source/Layout/ASLayout.mm | 21 ++++++++ Source/Layout/ASYogaUtilities.h | 4 +- 6 files changed, 115 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 648ec0b0..c1a53764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. [Scott Goodson](https://github.com/appleguy) - [ASCollectionNode] Add -isProcessingUpdates and -onDidFinishProcessingUpdates: APIs. [#522](https://github.com/TextureGroup/Texture/pull/522) [Scott Goodson](https://github.com/appleguy) - [Accessibility] Add .isAccessibilityContainer property, allowing automatic aggregation of children's a11y labels. [#468][Scott Goodson](https://github.com/appleguy) - [ASImageNode] Enabled .clipsToBounds by default, fixing the use of .cornerRadius and clipping of GIFs. [Scott Goodson](https://github.com/appleguy) [#466](https://github.com/TextureGroup/Texture/pull/466) diff --git a/Source/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h index e8b08a9a..ea241bd1 100644 --- a/Source/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -175,12 +175,15 @@ extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable - (void)addYogaChild:(ASDisplayNode *)child; - (void)removeYogaChild:(ASDisplayNode *)child; +- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index; - (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute; @property (nonatomic, assign) BOOL yogaLayoutInProgress; @property (nonatomic, strong, nullable) ASLayout *yogaCalculatedLayout; -// These methods should not normally be called directly. + +// These methods are intended to be used internally to Texture, and should not be called directly. +- (BOOL)shouldHaveYogaMeasureFunc; - (void)invalidateCalculatedYogaLayout; - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize; diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index 281bf1e7..6ef80ccf 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -59,20 +59,7 @@ - (void)addYogaChild:(ASDisplayNode *)child { - if (child == nil) { - return; - } - if (_yogaChildren == nil) { - _yogaChildren = [NSMutableArray array]; - } - - // Clean up state in case this child had another parent. - [self removeYogaChild:child]; - - [_yogaChildren addObject:child]; - - // YGNodeRef insertion is done in setParent: - child.yogaParent = self; + [self insertYogaChild:child atIndex:_yogaChildren.count]; } - (void)removeYogaChild:(ASDisplayNode *)child @@ -87,6 +74,24 @@ child.yogaParent = nil; } +- (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index +{ + if (child == nil) { + return; + } + if (_yogaChildren == nil) { + _yogaChildren = [NSMutableArray array]; + } + + // Clean up state in case this child had another parent. + [self removeYogaChild:child]; + + [_yogaChildren insertObject:child atIndex:index]; + + // YGNodeRef insertion is done in setParent: + child.yogaParent = self; +} + - (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute { if (AS_AT_LEAST_IOS9) { @@ -168,28 +173,72 @@ CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); ASLayout *layout = [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; - self.yogaCalculatedLayout = layout; +#if ASDISPLAYNODE_ASSERTIONS_ENABLED + // Assert that the sublayout is already flattened. + for (ASLayout *sublayout in layout.sublayouts) { + if (sublayout.sublayouts.count > 0 || ASDynamicCast(sublayout.layoutElement, ASDisplayNode) == nil) { + ASDisplayNodeAssert(NO, @"Yoga sublayout is not flattened! %@, %@", self, sublayout); + } + } +#endif + + // Because this layout won't go through the rest of the logic in calculateLayoutThatFits:, flatten it now. + layout = [layout filteredNodeLayoutTree]; + + if ([self.yogaCalculatedLayout isEqual:layout] == NO) { + self.yogaCalculatedLayout = layout; + } else { + layout = self.yogaCalculatedLayout; + ASYogaLog("-setupYogaCalculatedLayout: applying identical ASLayout: %@", layout); + } + + // Setup _pendingDisplayNodeLayout to reference the Yoga-calculated ASLayout, *unless* we are a leaf node. + // Leaf yoga nodes may have their own .sublayouts, if they use a layout spec (such as ASButtonNode). + // Their _pending variable is set after passing the Yoga checks at the start of -calculateLayoutThatFits: + + // For other Yoga nodes, there is no code that will set _pending unless we do it here. Why does it need to be set? + // When CALayer triggers the -[ASDisplayNode __layout] call, we will check if our current _pending layout + // has a size which matches our current bounds size. If it does, that layout will be used without recomputing it. + + // NOTE: Yoga does not make the constrainedSize available to intermediate nodes in the tree (e.g. not root or leaves). + // Although the size range provided here is not accurate, this will only affect caching of calls to layoutThatFits: + // These calls will behave as if they are not cached, starting a new Yoga layout pass, but this will tap into Yoga's + // own internal cache. + + if ([self shouldHaveYogaMeasureFunc] == NO) { + YGNodeRef parentNode = YGNodeGetParent(yogaNode); + CGSize parentSize = CGSizeZero; + if (parentNode) { + parentSize.width = YGNodeLayoutGetWidth(parentNode); + parentSize.height = YGNodeLayoutGetHeight(parentNode); + } + _pendingDisplayNodeLayout = std::make_shared(layout, ASSizeRangeUnconstrained, parentSize, 0); + } } -- (void)updateYogaMeasureFuncIfNeeded +- (BOOL)shouldHaveYogaMeasureFunc { // Size calculation via calculateSizeThatFits: or layoutSpecThatFits: // This will be used for ASTextNode, as well as any other node that has no Yoga children BOOL isLeafNode = (self.yogaChildren.count == 0); BOOL definesCustomLayout = [self implementsLayoutMethod]; + return (isLeafNode && definesCustomLayout); +} +- (void)updateYogaMeasureFuncIfNeeded +{ // We set the measure func only during layout. Otherwise, a cycle is created: // The YGNodeRef Context will retain the ASDisplayNode, which retains the style, which owns the YGNodeRef. - BOOL shouldHaveMeasureFunc = (isLeafNode && definesCustomLayout && checkFlag(YogaLayoutInProgress)); + BOOL shouldHaveMeasureFunc = ([self shouldHaveYogaMeasureFunc] && checkFlag(YogaLayoutInProgress)); ASLayoutElementYogaUpdateMeasureFunc(self.style.yogaNode, shouldHaveMeasureFunc ? self : nil); } - (void)invalidateCalculatedYogaLayout { - // Yoga internally asserts that this method may only be called on nodes with a measurement function. YGNodeRef yogaNode = self.style.yogaNode; if (yogaNode && YGNodeGetMeasureFunc(yogaNode)) { + // Yoga internally asserts that MarkDirty() may only be called on nodes with a measurement function. YGNodeMarkDirty(yogaNode); } self.yogaCalculatedLayout = nil; @@ -200,7 +249,7 @@ ASDisplayNode *yogaParent = self.yogaParent; if (yogaParent) { - ASYogaLog(@"ESCALATING to Yoga root: %@", self); + ASYogaLog("ESCALATING to Yoga root: %@", self); // TODO(appleguy): Consider how to get the constrainedSize for the yogaRoot when escalating manually. [yogaParent calculateLayoutFromYogaRoot:ASSizeRangeUnconstrained]; return; @@ -217,7 +266,7 @@ rootConstrainedSize = [self _locked_constrainedSizeForLayoutPass]; } - ASYogaLog(@"CALCULATING at Yoga root with constraint = {%@, %@}: %@", + ASYogaLog("CALCULATING at Yoga root with constraint = {%@, %@}: %@", NSStringFromCGSize(rootConstrainedSize.min), NSStringFromCGSize(rootConstrainedSize.max), self); YGNodeRef rootYogaNode = self.style.yogaNode; diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index e1f1d143..2b2041e2 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -967,23 +967,30 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) // - This node is a Yoga tree root: it has no yogaParent, but has yogaChildren. // - This node is a Yoga tree node: it has both a yogaParent and yogaChildren. // - This node is a Yoga tree leaf: it has a yogaParent, but no yogaChidlren. - // If we're a leaf node, we are probably being called by a measure function and proceed as normal. - // If we're a yoga root or tree node, initiate a new Yoga calculation pass from root. YGNodeRef yogaNode = _style.yogaNode; BOOL hasYogaParent = (_yogaParent != nil); BOOL hasYogaChildren = (_yogaChildren.count > 0); BOOL usesYoga = (yogaNode != NULL && (hasYogaParent || hasYogaChildren)); - if (usesYoga && (_yogaParent == nil || _yogaChildren.count > 0)) { + if (usesYoga) { // This node has some connection to a Yoga tree. - ASDN::MutexUnlocker ul(__instanceLock__); - - if (self.yogaLayoutInProgress == NO) { - [self calculateLayoutFromYogaRoot:constrainedSize]; + if ([self shouldHaveYogaMeasureFunc] == NO) { + // If we're a yoga root, tree node, or leaf with no measure func (e.g. spacer), then + // initiate a new Yoga calculation pass from root. + ASDN::MutexUnlocker ul(__instanceLock__); + as_activity_create_for_scope("Yoga layout calculation"); + if (self.yogaLayoutInProgress == NO) { + ASYogaLog("Calculating yoga layout from root %@, %@", self, NSStringFromASSizeRange(constrainedSize)); + [self calculateLayoutFromYogaRoot:constrainedSize]; + } else { + ASYogaLog("Reusing existing yoga layout %@", _yogaCalculatedLayout); + } + ASDisplayNodeAssert(_yogaCalculatedLayout, @"Yoga node should have a non-nil layout at this stage: %@", self); + return _yogaCalculatedLayout; + } else { + // If we're a yoga leaf node with custom measurement function, proceed with normal layout so layoutSpecs can run (e.g. ASButtonNode). + ASYogaLog("PROCEEDING past Yoga check to calculate ASLayout for: %@", self); } - ASDisplayNodeAssert(_yogaCalculatedLayout, @"Yoga node should have a non-nil layout at this stage: %@", self); - return _yogaCalculatedLayout; } - ASYogaLog(@"PROCEEDING past Yoga check to calculate ASLayout for: %@", self); #endif /* YOGA */ // Manual size calculation via calculateSizeThatFits: diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index 03112010..4e8d2f50 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -271,6 +271,27 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT( return layout; } +#pragma mark - Equality Checking + +- (BOOL)isEqual:(id)object +{ + ASLayout *layout = ASDynamicCast(object, ASLayout); + if (layout == nil) { + return NO; + } + + if (!CGSizeEqualToSize(_size, layout.size)) return NO; + if (!CGPointEqualToPoint(_position, layout.position)) return NO; + if (_layoutElement != layout.layoutElement) return NO; + + NSArray *sublayouts = layout.sublayouts; + if (sublayouts != _sublayouts && (sublayouts == nil || _sublayouts == nil || ![_sublayouts isEqual:sublayouts])) { + return NO; + } + + return YES; +} + #pragma mark - Accessors - (ASLayoutElementType)type diff --git a/Source/Layout/ASYogaUtilities.h b/Source/Layout/ASYogaUtilities.h index 2986c019..b229b343 100644 --- a/Source/Layout/ASYogaUtilities.h +++ b/Source/Layout/ASYogaUtilities.h @@ -15,9 +15,11 @@ #if YOGA /* YOGA */ #import +#import #import -#define ASYogaLog(...) //NSLog(__VA_ARGS__) +// Should pass a string literal, not an NSString as the first argument to ASYogaLog +#define ASYogaLog(x, ...) as_log_verbose(ASLayoutLog(), x, ##__VA_ARGS__); @interface ASDisplayNode (YogaHelpers)