Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells. #797

Merged
merged 13 commits into from
Mar 13, 2018
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## master
* Add your own contributions to the next release on the line below this with your name.
- [ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells.
- [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions.
- [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling.
- **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749).
Expand Down
2 changes: 1 addition & 1 deletion Source/ASCellNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {

- (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable;

- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing");
- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing, although subnodes may be layer-backed.");

@end

Expand Down
18 changes: 18 additions & 0 deletions Source/ASCellNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,24 @@ - (BOOL)supportsLayerBacking
return NO;
}

- (BOOL)shouldUseUIKitCell
{
return NO;
}

@end

#pragma mark -
#pragma mark ASWrapperCellNode

// TODO: Consider if other calls, such as willDisplayCell, should be bridged to this class.
@implementation ASWrapperCellNode : ASCellNode

- (BOOL)shouldUseUIKitCell
{
return YES;
}

@end


Expand Down
37 changes: 37 additions & 0 deletions Source/ASCollectionNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,30 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) BOOL allowsMultipleSelection;

/**
* A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content.
* The default value of this property is NO.
*/
@property (nonatomic, assign) BOOL alwaysBounceVertical;

/**
* A Boolean value that determines whether bouncing always occurs when horizontal scrolling reaches the end of the content view.
* The default value of this property is NO.
*/
@property (nonatomic, assign) BOOL alwaysBounceHorizontal;

/**
* A Boolean value that controls whether the vertical scroll indicator is visible.
* The default value of this property is YES.
*/
@property (nonatomic, assign) BOOL showsVerticalScrollIndicator;

/**
* A Boolean value that controls whether the horizontal scroll indicator is visible.
* The default value of this property is NO.
*/
@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator;

/**
* The layout used to organize the node's items.
*
Expand Down Expand Up @@ -291,6 +315,19 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)waitUntilAllUpdatesAreProcessed;

/**
* Returns YES if the ASCollectionNode contents are completely synchronized with the underlying collection-view layout.
*/
@property (nonatomic, readonly) BOOL isSynchronized;
Copy link
Member

Choose a reason for hiding this comment

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

synchronized with an isSynchronized getter?

Copy link
Member Author

Choose a reason for hiding this comment

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

@Adlai-Holler This is an interesting case because it's a read-only property, so only the getter applies. I'm happy to change it in the name of convention, though it felt a bit unusual to write!


/**
* Schedules a block to be performed (on the main thread) as soon as the completion block is called
* on performBatchUpdates:.
*
* When isSynchronized == YES, the block is run block immediately (before the method returns).
*/
- (void)onDidFinishSynchronizing:(nullable void (^)(void))didFinishSynchronizing;
Copy link
Member

Choose a reason for hiding this comment

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

Nit: This decl says nullable but the impl will crash with a nil block.

Probably best to let this argument default to nonnull since I can't think of a use case for knowingly passing nil in here and also add a nil check in the implementation.

Copy link
Member Author

Choose a reason for hiding this comment

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

@Adlai-Holler cool, fixed all definitions (and also made the change for the existing onDidFinishProcessingUpdates:).

I moved Synchronizing to +Beta for now. A better name might be onDidFinishSynchronizingLayout, or ...LayoutSynchronization. Or even merging with onDidFinishProcessingUpdates with the option to specify a type of "finished", with synchronized including the end of animation. It's all a bit subtle and confusing, but having this call point available has been very useful even as the name remains a bit unclear.


/**
* Inserts one or more sections.
*
Expand Down
120 changes: 115 additions & 5 deletions Source/ASCollectionNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ @interface _ASCollectionPendingState : NSObject
@property (nonatomic, assign) BOOL usesSynchronousDataLoading;
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
@property (weak, nonatomic) id <ASCollectionViewLayoutInspecting> layoutInspector;
@property (nonatomic, assign) BOOL alwaysBounceVertical;
@property (nonatomic, assign) BOOL alwaysBounceHorizontal;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) CGPoint contentOffset;
@property (nonatomic, assign) BOOL animatesContentOffset;
@property (nonatomic, assign) BOOL showsVerticalScrollIndicator;
@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator;
@end

@implementation _ASCollectionPendingState
Expand Down Expand Up @@ -203,13 +207,28 @@ - (void)didLoad
view.allowsMultipleSelection = pendingState.allowsMultipleSelection;
view.usesSynchronousDataLoading = pendingState.usesSynchronousDataLoading;
view.layoutInspector = pendingState.layoutInspector;
view.contentInset = pendingState.contentInset;


// Only apply these flags if they're enabled; the view might come with them turned on.
if (pendingState.alwaysBounceVertical) {
view.alwaysBounceVertical = YES;
}
if (pendingState.alwaysBounceHorizontal) {
view.alwaysBounceHorizontal = YES;
}

UIEdgeInsets contentInset = pendingState.contentInset;
if (!UIEdgeInsetsEqualToEdgeInsets(contentInset, UIEdgeInsetsZero)) {
view.contentInset = contentInset;
}

CGPoint contentOffset = pendingState.contentOffset;
if (!CGPointEqualToPoint(contentOffset, CGPointZero)) {
[view setContentOffset:contentOffset animated:pendingState.animatesContentOffset];
}

if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) {
[view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode];
}

[view setContentOffset:pendingState.contentOffset animated:pendingState.animatesContentOffset];

// Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block.
}
Expand All @@ -235,10 +254,11 @@ - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfac
- (void)didEnterPreloadState
{
[super didEnterPreloadState];
// ASCollectionNode is often nested inside of other collections. In this case, ASHierarchyState's RangeManaged bit will be set.
// Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load.
// We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view.
// TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view
if (CGRectEqualToRect(self.bounds, CGRectZero) == NO) {
if (ASHierarchyStateIncludesRangeManaged(self.hierarchyState) && CGRectEqualToRect(self.bounds, CGRectZero) == NO) {
[[self view] layoutIfNeeded];
}
}
Expand Down Expand Up @@ -435,6 +455,82 @@ - (BOOL)allowsMultipleSelection
}
}

- (void)setAlwaysBounceVertical:(BOOL)alwaysBounceVertical
{
if ([self pendingState]) {
_pendingState.alwaysBounceVertical = alwaysBounceVertical;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.alwaysBounceVertical = alwaysBounceVertical;
}
}

- (BOOL)alwaysBounceVertical
{
if ([self pendingState]) {
return _pendingState.alwaysBounceVertical;
} else {
return self.view.alwaysBounceVertical;
}
}

- (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal
{
if ([self pendingState]) {
_pendingState.alwaysBounceHorizontal = alwaysBounceHorizontal;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.alwaysBounceHorizontal = alwaysBounceHorizontal;
}
}

- (BOOL)alwaysBounceHorizontal
{
if ([self pendingState]) {
return _pendingState.alwaysBounceHorizontal;
} else {
return self.view.alwaysBounceHorizontal;
}
}

- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator
{
if ([self pendingState]) {
_pendingState.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
}
}

- (BOOL)showsVerticalScrollIndicator
{
if ([self pendingState]) {
return _pendingState.showsVerticalScrollIndicator;
} else {
return self.view.showsVerticalScrollIndicator;
}
}

- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator
{
if ([self pendingState]) {
_pendingState.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
}
}

- (BOOL)showsHorizontalScrollIndicator
{
if ([self pendingState]) {
return _pendingState.showsHorizontalScrollIndicator;
} else {
return self.view.showsHorizontalScrollIndicator;
}
}

- (void)setCollectionViewLayout:(UICollectionViewLayout *)layout
{
if ([self pendingState]) {
Expand Down Expand Up @@ -754,6 +850,20 @@ - (void)onDidFinishProcessingUpdates:(nullable void (^)())completion
}
}

- (BOOL)isSynchronized
{
return (self.nodeLoaded ? [self.view isSynchronized] : YES);
}

- (void)onDidFinishSynchronizing:(void (^)())completion
{
if (!self.nodeLoaded) {
completion();
} else {
[self.view onDidFinishSynchronizing:completion];
}
}

- (void)waitUntilAllUpdatesAreProcessed
{
ASDisplayNodeAssertMainThread();
Expand Down
6 changes: 6 additions & 0 deletions Source/ASCollectionView.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ NS_ASSUME_NONNULL_BEGIN
- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion;
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead.");

/**
* See ASCollectionNode.h for full documentation of these methods.
*/
@property (nonatomic, readonly) BOOL isSynchronized;
- (void)onDidFinishSynchronizing:(nullable void (^)(void))completion;

/**
* Registers the given kind of supplementary node for use in creating node-backed supplementary views.
*
Expand Down
Loading