Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions AsyncDisplayKit/ASCellNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@

#import <AsyncDisplayKit/ASDisplayNode.h>

@class ASCellNode;

typedef NSUInteger ASCellNodeAnimation;

@protocol ASCellNodeDelegate <NSObject>

/**
* Notifies the delegate that the specified cell node has done a relayout.
* The notification is done on main thread.
*
* @param node A node informing the delegate about the relayout.
*
* @param suggestedAnimation A constant indicates how the delegate should animate. See UITableViewRowAnimation.
*/
- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation;
@end

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

/*
* A delegate to be notified (on main thread) after a relayout.
*/
@property (nonatomic, weak) id<ASCellNodeDelegate> delegate;

/*
* A constant that is passed to the delegate to indicate how a relayout is to be animated.
*
* @see UITableViewRowAnimation
*/
@property (nonatomic, assign) ASCellNodeAnimation relayoutAnimation;

/*
* ASCellNode must forward touch events in order for UITableView and UICollectionView tap handling to work. Overriding
* these methods (e.g. for highlighting) requires the super method be called.
Expand All @@ -62,6 +90,17 @@
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event ASDISPLAYNODE_REQUIRES_SUPER;

/**
* Marks the node as needing layout. Convenience for use whether the view / layer is loaded or not. Safe to call from a background thread.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only safe to call from the background if the node isn't loaded, right? We should probably just remove that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in #812.

*
* If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated,
* and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size.
* The delegate will then be notified on main thread.
*
* This method can be called inside of an animation block (to animate all of the layout changes).
*/
- (void)setNeedsLayout;

@end


Expand Down
16 changes: 15 additions & 1 deletion AsyncDisplayKit/ASCellNode.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#import "ASCellNode.h"

#import "ASInternalHelpers.h"
#import <AsyncDisplayKit/_ASDisplayView.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASTextNode.h>
Expand All @@ -27,6 +28,7 @@ - (instancetype)init
// use UITableViewCell defaults
_selectionStyle = UITableViewCellSelectionStyleDefault;
self.clipsToBounds = YES;
_relayoutAnimation = UITableViewRowAnimationAutomatic;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of having a suggestedAnimation param when this is always automatic? How do you see this being extended?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be set to another value, by another object or in the subclass's initializer.


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

- (void)setNeedsLayout
{
ASDisplayNodeAssertThreadAffinity(self);
[super setNeedsLayout];

if (_delegate != nil) {
ASPerformBlockOnMainThread(^{
[_delegate node:self didRelayoutWithSuggestedAnimation:_relayoutAnimation];
});
}
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
ASDisplayNodeAssertMainThread();
Expand Down Expand Up @@ -122,7 +136,7 @@ - (void)setText:(NSString *)text
_text = [text copy];
_textNode.attributedString = [[NSAttributedString alloc] initWithString:_text
attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:kFontSize]}];

[self setNeedsLayout];
}

@end
10 changes: 0 additions & 10 deletions AsyncDisplayKit/ASCollectionView.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,16 +223,6 @@
*/
- (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths;

/**
* Relayouts the specified item.
*
* @param indexPath The index path identifying the item to relayout.
*
* @discussion This method must be called from the main thread. The relayout is excuted on main thread.
* The node of the specified item must be updated to cause layout changes before this method is called.
*/
- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath;

/**
* Moves the item at a specified location to a destination location.
*
Expand Down
25 changes: 14 additions & 11 deletions AsyncDisplayKit/ASCollectionView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#import "ASCollectionViewLayoutController.h"
#import "ASRangeController.h"
#import "ASCollectionDataController.h"
#import "ASDisplayNodeInternal.h"
#import "ASBatchFetching.h"
#import "UICollectionViewLayout+ASConvenience.h"
#import "ASInternalHelpers.h"
Expand Down Expand Up @@ -131,7 +130,7 @@ - (void)setHighlighted:(BOOL)highlighted
#pragma mark -
#pragma mark ASCollectionView.

@interface ASCollectionView () <ASRangeControllerDelegate, ASDataControllerSource> {
@interface ASCollectionView () <ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeDelegate> {
_ASCollectionViewProxy *_proxyDataSource;
_ASCollectionViewProxy *_proxyDelegate;

Expand Down Expand Up @@ -269,7 +268,7 @@ - (ASCollectionViewFlowLayoutInspector *)flowLayoutInspector
- (void)reloadDataWithCompletion:(void (^)())completion
{
ASDisplayNodeAssert(self.asyncDelegate, @"ASCollectionView's asyncDelegate property must be set.");
ASDisplayNodePerformBlockOnMainThread(^{
ASPerformBlockOnMainThread(^{
_superIsPendingDataLoad = YES;
[super reloadData];
});
Expand Down Expand Up @@ -458,14 +457,6 @@ - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths
[_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:kASCollectionViewAnimationNone];
}

- (void)relayoutItemAtIndexPath:(NSIndexPath *)indexPath
{
ASDisplayNodeAssertMainThread();
ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
[node setNeedsLayout];
[super reloadItemsAtIndexPaths:@[indexPath]];
}

- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
ASDisplayNodeAssertMainThread();
Expand Down Expand Up @@ -664,6 +655,7 @@ - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPat
{
ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath];
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
node.delegate = self;
return node;
}

Expand Down Expand Up @@ -907,4 +899,15 @@ - (void)rangeController:(ASRangeController *)rangeController didDeleteSectionsAt
}
}

#pragma mark - ASCellNodeDelegate

- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation
{
ASDisplayNodeAssertMainThread();
NSIndexPath *indexPath = [self indexPathForNode:node];
if (indexPath != nil) {
[super reloadItemsAtIndexPaths:@[indexPath]];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's a way to coalesce these without limiting the ability to wrap in animation blocks. Maybe it's not an issue, but do you see visually sequential loading / flashing if you set multiple cells as needing layout? Even if there were a longer delay due to main thread measurement, it would be best for all of those to happen together.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the changes in #812, it's up to the developer the animate the layout changes in multiple cells, i.e they can wrap multiple -setNeedsLayout calls inside a single animation block, or don't animate at all.

}
}

@end
8 changes: 0 additions & 8 deletions AsyncDisplayKit/ASDisplayNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -559,14 +559,6 @@ typedef void (^ASDisplayNodeDidLoadBlock)(ASDisplayNode *node);
*
* If this node was measured, calling this method triggers an internal relayout: the calculated layout is invalidated,
* and the supernode is notified or (if this node is the root one) a full measurement pass is executed using the old constrained size.
*
* Note: If the relayout causes a change in size of the root node that is attached to a container view,
* the container view must be notified to relayout.
* For ASTableView and ASCollectionView, instead of calling this method directly,
* it is recommended to call -relayoutRowAtIndexPath:withRowAnimation and -relayoutItemAtIndexPath: respectively.
*
* @see [ASTableView relayoutRowAtIndexPath:withRowAnimation:]
* @see [ASCollectionView relayoutItemAtIndexPath:]
*/
- (void)setNeedsLayout;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would replace this with something like "Note: ASCellNode has special behavior in that calling this method will automatically notify the containing ASTableView / ASCollectionView that the cell should be resized, if necessary."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in #812. Thanks.


Expand Down
13 changes: 1 addition & 12 deletions AsyncDisplayKit/ASDisplayNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,6 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
return ASSubclassOverridesSelector([ASDisplayNode class], subclass, selector);
}

void ASDisplayNodePerformBlockOnMainThread(void (^block)())
{
if ([NSThread isMainThread]) {
block();
} else {
dispatch_async(dispatch_get_main_queue(), ^{
block();
});
}
}

void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)())
{
ASDisplayNodeCAssertNotNil(block, @"block is required");
Expand All @@ -79,7 +68,7 @@ void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)
// Hold the lock to avoid a race where the node gets loaded while the block is in-flight.
ASDN::MutexLocker l(node->_propertyLock);
if (node.nodeLoaded) {
ASDisplayNodePerformBlockOnMainThread(^{
ASPerformBlockOnMainThread(^{
block();
});
} else {
Expand Down
6 changes: 3 additions & 3 deletions AsyncDisplayKit/ASImageNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ - (void)setImage:(UIImage *)image
_image = image;

ASDN::MutexUnlocker u(_imageLock);
ASDisplayNodePerformBlockOnMainThread(^{
ASPerformBlockOnMainThread(^{
[self invalidateCalculatedLayout];
[self setNeedsDisplay];
});
Expand Down Expand Up @@ -306,7 +306,7 @@ - (void)setCropEnabled:(BOOL)cropEnabled recropImmediately:(BOOL)recropImmediate
// If we have an image to display, display it, respecting our recrop flag.
if (self.image)
{
ASDisplayNodePerformBlockOnMainThread(^{
ASPerformBlockOnMainThread(^{
if (recropImmediately)
[self displayImmediately];
else
Expand Down Expand Up @@ -334,7 +334,7 @@ - (void)setCropRect:(CGRect)cropRect
BOOL isCroppingImage = ((boundsSize.width < imageSize.width) || (boundsSize.height < imageSize.height));

// Re-display if we need to.
ASDisplayNodePerformBlockOnMainThread(^{
ASPerformBlockOnMainThread(^{
if (self.nodeLoaded && self.contentMode == UIViewContentModeScaleAspectFill && isCroppingImage)
[self setNeedsDisplay];
});
Expand Down
12 changes: 0 additions & 12 deletions AsyncDisplayKit/ASTableView.h
Original file line number Diff line number Diff line change
Expand Up @@ -214,18 +214,6 @@
*/
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;

/**
* Relayouts the specified row using a given animation effect.
*
* @param indexPath The index path identifying the row to relayout.
*
* @param animation A constant that indicates how the relayout is to be animated. See UITableViewRowAnimation.
*
* @discussion This method must be called from the main thread. The relayout is excuted on main thread.
* The node of the specified row must be updated to cause layout changes before this method is called.
*/
- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation;

/**
* Moves the row at a specified location to a destination location.
*
Expand Down
25 changes: 14 additions & 11 deletions AsyncDisplayKit/ASTableView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
#import "ASCollectionViewLayoutController.h"
#import "ASLayoutController.h"
#import "ASRangeController.h"
#import "ASDisplayNodeInternal.h"
#import "ASBatchFetching.h"
#import "ASInternalHelpers.h"
#import "ASLayout.h"
Expand Down Expand Up @@ -152,7 +151,7 @@ - (void)setHighlighted:(BOOL)highlighted
#pragma mark -
#pragma mark ASTableView

@interface ASTableView () <ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate> {
@interface ASTableView () <ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeDelegate> {
_ASTableViewProxy *_proxyDataSource;
_ASTableViewProxy *_proxyDelegate;

Expand Down Expand Up @@ -334,7 +333,7 @@ - (void)setAsyncDelegate:(id<ASTableViewDelegate>)asyncDelegate
- (void)reloadDataWithCompletion:(void (^)())completion
{
ASDisplayNodeAssert(self.asyncDelegate, @"ASTableView's asyncDelegate property must be set.");
ASDisplayNodePerformBlockOnMainThread(^{
ASPerformBlockOnMainThread(^{
[super reloadData];
});
[_dataController reloadDataWithAnimationOptions:UITableViewRowAnimationNone completion:completion];
Expand Down Expand Up @@ -477,14 +476,6 @@ - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableVi
[_dataController reloadRowsAtIndexPaths:indexPaths withAnimationOptions:animation];
}

- (void)relayoutRowAtIndexPath:(NSIndexPath *)indexPath withRowAnimation:(UITableViewRowAnimation)animation
{
ASDisplayNodeAssertMainThread();
ASCellNode *node = [self nodeForRowAtIndexPath:indexPath];
[node setNeedsLayout];
[super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:animation];
}

- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
{
ASDisplayNodeAssertMainThread();
Expand Down Expand Up @@ -849,6 +840,7 @@ - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPat
{
ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath];
ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode");
node.delegate = self;
return node;
}

Expand Down Expand Up @@ -923,4 +915,15 @@ - (void)willLayoutSubviewsOfTableViewCell:(_ASTableViewCell *)tableViewCell
}
}

#pragma mark - ASCellNodeDelegate

- (void)node:(ASCellNode *)node didRelayoutWithSuggestedAnimation:(ASCellNodeAnimation)animation
{
ASDisplayNodeAssertMainThread();
NSIndexPath *indexPath = [self indexPathForNode:node];
if (indexPath != nil) {
[super reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:(UITableViewRowAnimation)animation];
}
}

@end
Loading