Skip to content

Commit 0598935

Browse files
committed
Merge pull request facebookarchive#793 from nguyenhuy/ImproveCellNodeRelayout
ASCellNode delegate to automatically resize in table & collection when -setNeedsLayout called.
2 parents a641a6e + fb18e76 commit 0598935

17 files changed

Lines changed: 116 additions & 97 deletions

AsyncDisplayKit/ASCellNode.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@
88

99
#import <AsyncDisplayKit/ASDisplayNode.h>
1010

11+
@class ASCellNode;
12+
13+
typedef NSUInteger ASCellNodeAnimation;
14+
15+
@protocol ASCellNodeDelegate <NSObject>
16+
17+
/**
18+
* Notifies the delegate that the specified cell node has done a relayout.
19+
* The notification is done on main thread.
20+
*
21+
* @param node A node informing the delegate about the relayout.
22+
*
23+
* @param suggestedAnimation A constant indicates how the delegate should animate. See UITableViewRowAnimation.
24+
*/
25+
- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation;
26+
@end
1127

1228
/**
1329
* Generic cell node. Subclass this instead of `ASDisplayNode` to use with `ASTableView` and `ASCollectionView`.
@@ -53,6 +69,18 @@
5369
*/
5470
@property (nonatomic, assign) BOOL highlighted;
5571

72+
/*
73+
* A delegate to be notified (on main thread) after a relayout.
74+
*/
75+
@property (nonatomic, weak) id<ASCellNodeDelegate> delegate;
76+
77+
/*
78+
* A constant that is passed to the delegate to indicate how a relayout is to be animated.
79+
*
80+
* @see UITableViewRowAnimation
81+
*/
82+
@property (nonatomic, assign) ASCellNodeAnimation relayoutAnimation;
83+
5684
/*
5785
* ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding
5886
* these methods (e.g. for highlighting) requires the super method be called.
@@ -62,6 +90,17 @@
6290
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
6391
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
6492

93+
/**
94+
* Marks the node as needing layout. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread.
95+
*
96+
* If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated,
97+
* and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size.
98+
* The delegate will then be notified on main thread.
99+
*
100+
* This method can be called inside of an animation block (to animate all of the layout changes).
101+
*/
102+
- (void)setNeedsLayout;
103+
65104
@end
66105

67106

AsyncDisplayKit/ASCellNode.m

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#import "ASCellNode.h"
1010

11+
#import "ASInternalHelpers.h"
1112
#import <AsyncDisplayKit/_ASDisplayView.h>
1213
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
1314
#import <AsyncDisplayKit/ASTextNode.h>
@@ -27,6 +28,7 @@ - (instancetype)init
2728
// use UITableViewCell defaults
2829
_selectionStyle = UITableViewCellSelectionStyleDefault;
2930
self.clipsToBounds = YES;
31+
_relayoutAnimation = UITableViewRowAnimationAutomatic;
3032

3133
return self;
3234
}
@@ -49,6 +51,18 @@ - (void)setLayerBacked:(BOOL)layerBacked
4951
ASDisplayNodeAssert(!layerBacked, @"ASCellNode does not support layer-backing.");
5052
}
5153

54+
- (void)setNeedsLayout
55+
{
56+
ASDisplayNodeAssertThreadAffinity(self);
57+
[super setNeedsLayout];
58+
59+
if (_delegate != nil) {
60+
ASPerformBlockOnMainThread(^{
61+
[_delegate node:self didRelayoutWithSuggestedAnimation:_relayoutAnimation];
62+
});
63+
}
64+
}
65+
5266
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
5367
{
5468
ASDisplayNodeAssertMainThread();
@@ -122,7 +136,7 @@ - (void)setText:(NSString *)text
122136
_text = [text copy];
123137
_textNode.attributedString = [[NSAttributedString alloc] initWithString:_text
124138
attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}];
125-
139+
[self setNeedsLayout];
126140
}
127141

128142
@end

AsyncDisplayKit/ASCollectionView.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -223,16 +223,6 @@
223223
*/
224224
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths;
225225

226-
/**
227-
* Relayouts the specified item.
228-
*
229-
* @param indexPath The index path identifying the item to relayout.
230-
*
231-
* @discussion This method must be called from the main thread. The relayout is excuted on main thread.
232-
* The node of the specified item must be updated to cause layout changes before this method is called.
233-
*/
234-
- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath;
235-
236226
/**
237227
* Moves the item at a specified location to a destination location.
238228
*

AsyncDisplayKit/ASCollectionView.mm

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
#import "ASCollectionViewLayoutController.h"
1313
#import "ASRangeController.h"
1414
#import "ASCollectionDataController.h"
15-
#import "ASDisplayNodeInternal.h"
1615
#import "ASBatchFetching.h"
1716
#import "UICollectionViewLayout+ASConvenience.h"
1817
#import "ASInternalHelpers.h"
@@ -131,7 +130,7 @@ - (void)setHighlighted:(BOOL)highlighted
131130
#pragma mark -
132131
#pragma mark ASCollectionView.
133132

134-
@interface ASCollectionView () <ASRangeControllerDelegate, ASDataControllerSource> {
133+
@interface ASCollectionView () <ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeDelegate> {
135134
_ASCollectionViewProxy *_proxyDataSource;
136135
_ASCollectionViewProxy *_proxyDelegate;
137136

@@ -269,7 +268,7 @@ - (ASCollectionViewFlowLayoutInspector *)flowLayoutInspector
269268
- (void)reloadDataWithCompletion:(void (^)())completion
270269
{
271270
ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set.");
272-
ASDisplayNodePerformBlockOnMainThread(^{
271+
ASPerformBlockOnMainThread(^{
273272
_superIsPendingDataLoad = YES;
274273
[super reloadData];
275274
});
@@ -458,14 +457,6 @@ - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
458457
[_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone];
459458
}
460459

461-
- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath
462-
{
463-
ASDisplayNodeAssertMainThread();
464-
ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
465-
[node setNeedsLayout];
466-
[super reloadItemsAtIndexPaths:@[indexPath]];
467-
}
468-
469460
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
470461
{
471462
ASDisplayNodeAssertMainThread();
@@ -664,6 +655,7 @@ - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPat
664655
{
665656
ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
666657
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
658+
node.delegate = self;
667659
return node;
668660
}
669661

@@ -895,4 +887,15 @@ - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAt
895887
}
896888
}
897889

890+
#pragma mark - ASCellNodeDelegate
891+
892+
- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation
893+
{
894+
ASDisplayNodeAssertMainThread();
895+
NSIndexPath *indexPath = [self indexPathForNode:node];
896+
if (indexPath != nil) {
897+
[super reloadItemsAtIndexPaths:@[indexPath]];
898+
}
899+
}
900+
898901
@end

AsyncDisplayKit/ASDisplayNode.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -559,14 +559,6 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
559559
*
560560
* If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated,
561561
* and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size.
562-
*
563-
* Note: If the relayout causes a change in size of the root node that is attached to a container view,
564-
* the container view must be notified to relayout.
565-
* For ASTableView and ASCollectionView, instead of calling this method directly,
566-
* it is recommended to call -relayoutRowAtIndexPath:withRowAnimation and -relayoutItemAtIndexPath: respectively.
567-
*
568-
* @see [ASTableView relayoutRowAtIndexPath:withRowAnimation:]
569-
* @see [ASCollectionView relayoutItemAtIndexPath:]
570562
*/
571563
- (void)setNeedsLayout;
572564

AsyncDisplayKit/ASDisplayNode.mm

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,6 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
5757
return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector);
5858
}
5959

60-
void ASDisplayNodePerformBlockOnMainThread(void (^block)())
61-
{
62-
if ([NSThread isMainThread]) {
63-
block();
64-
} else {
65-
dispatch_async(dispatch_get_main_queue(), ^{
66-
block();
67-
});
68-
}
69-
}
70-
7160
void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)())
7261
{
7362
ASDisplayNodeCAssertNotNil(block, @"block is required");
@@ -79,7 +68,7 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
7968
// Hold the lock to avoid a race where the node gets loaded while the block is in-flight.
8069
ASDN::MutexLocker l(node->_propertyLock);
8170
if (node.nodeLoaded) {
82-
ASDisplayNodePerformBlockOnMainThread(^{
71+
ASPerformBlockOnMainThread(^{
8372
block();
8473
});
8574
} else {

AsyncDisplayKit/ASImageNode.mm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ - (void)setImage:(UIImage *)image
128128
_image = image;
129129

130130
ASDN::MutexUnlocker u(_imageLock);
131-
ASDisplayNodePerformBlockOnMainThread(^{
131+
ASPerformBlockOnMainThread(^{
132132
[self invalidateCalculatedLayout];
133133
[self setNeedsDisplay];
134134
});
@@ -306,7 +306,7 @@ - (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediate
306306
// If we have an image to display, display it, respecting our recrop flag.
307307
if (self.image)
308308
{
309-
ASDisplayNodePerformBlockOnMainThread(^{
309+
ASPerformBlockOnMainThread(^{
310310
if (recropImmediately)
311311
[self displayImmediately];
312312
else
@@ -334,7 +334,7 @@ - (void)setCropRect:(CGRect)cropRect
334334
BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height));
335335

336336
// Re-display if we need to.
337-
ASDisplayNodePerformBlockOnMainThread(^{
337+
ASPerformBlockOnMainThread(^{
338338
if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage)
339339
[self setNeedsDisplay];
340340
});

AsyncDisplayKit/ASTableView.h

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -214,18 +214,6 @@
214214
*/
215215
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
216216

217-
/**
218-
* Relayouts the specified row using a given animation effect.
219-
*
220-
* @param indexPath The index path identifying the row to relayout.
221-
*
222-
* @param animation A constant that indicates how the relayout is to be animated. See UITableViewRowAnimation.
223-
*
224-
* @discussion This method must be called from the main thread. The relayout is excuted on main thread.
225-
* The node of the specified row must be updated to cause layout changes before this method is called.
226-
*/
227-
- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation;
228-
229217
/**
230218
* Moves the row at a specified location to a destination location.
231219
*

AsyncDisplayKit/ASTableView.mm

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#import "ASCollectionViewLayoutController.h"
1515
#import "ASLayoutController.h"
1616
#import "ASRangeController.h"
17-
#import "ASDisplayNodeInternal.h"
1817
#import "ASBatchFetching.h"
1918
#import "ASInternalHelpers.h"
2019
#import "ASLayout.h"
@@ -152,7 +151,7 @@ - (void)setHighlighted:(BOOL)highlighted
152151
#pragma mark -
153152
#pragma mark ASTableView
154153

155-
@interface ASTableView () <ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate> {
154+
@interface ASTableView () <ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeDelegate> {
156155
_ASTableViewProxy *_proxyDataSource;
157156
_ASTableViewProxy *_proxyDelegate;
158157

@@ -314,7 +313,7 @@ - (void)setAsyncDelegate:(id<ASTableViewDelegate>)asyncDelegate
314313
- (void)reloadDataWithCompletion:(void (^)())completion
315314
{
316315
ASDisplayNodeAssert(self.asyncDelegate, @"ASTableView's asyncDelegate property must be set.");
317-
ASDisplayNodePerformBlockOnMainThread(^{
316+
ASPerformBlockOnMainThread(^{
318317
[super reloadData];
319318
});
320319
[_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion];
@@ -457,14 +456,6 @@ - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableVi
457456
[_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation];
458457
}
459458

460-
- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation
461-
{
462-
ASDisplayNodeAssertMainThread();
463-
ASCellNode *node = [self nodeForRowAtIndexPath:indexPath];
464-
[node setNeedsLayout];
465-
[super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:animation];
466-
}
467-
468459
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
469460
{
470461
ASDisplayNodeAssertMainThread();
@@ -829,6 +820,7 @@ - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPat
829820
{
830821
ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath];
831822
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
823+
node.delegate = self;
832824
return node;
833825
}
834826

@@ -903,4 +895,15 @@ - (void)willLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell
903895
}
904896
}
905897

898+
#pragma mark - ASCellNodeDelegate
899+
900+
- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation
901+
{
902+
ASDisplayNodeAssertMainThread();
903+
NSIndexPath *indexPath = [self indexPathForNode:node];
904+
if (indexPath != nil) {
905+
[super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:(UITableViewRowAnimation)animation];
906+
}
907+
}
908+
906909
@end

0 commit comments

Comments
 (0)