Skip to content

Commit

Permalink
[ASWrapperCellNode] Introduce a new class allowing more control of UI…
Browse files Browse the repository at this point in the history
…Kit passthrough cells. (#797)

* - [ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells.

A few minor fixes to Collections behavior as well, including a new isSynchronized
API. The difference from processingUpdates is that after Synchronized, all animations
have also completed (or runloop turn if animations disabled, so .collectionViewLayout
can be relied on being fully in sync).

More upstreaming to come after this can land...

* Fix -[ASDataController clearData] to take no action before initial data loading.

* Empty commit to kick CI

* Spacing change to kick CI (since an empty commit doesn't work...)

* Tweak ASDataController changes to handle an edge case in _editingTransactionQueueCount management.

* Avoid excess cyclic calls to onDidFinishProcessingUpdates: by avoiding ASMainSerialQueue.

* Reverting my initial change as it wasn't the right approach, following the real fix before this.
  • Loading branch information
appleguy committed Mar 13, 2018
1 parent 5cafdb9 commit a41cbb4
Show file tree
Hide file tree
Showing 15 changed files with 415 additions and 105 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Add your own contributions to the next release on the line below this with your name.
- [tvOS] Fixes errors when building against tvOS SDK [Alex Hill](https://github.com/alexhillc) [#728](https://github.com/TextureGroup/Texture/pull/728)
- [ASDisplayNode] Add unit tests for layout z-order changes (with an open issue to fix).
- [ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells.
- [ASDisplayNode] Consolidate main thread initialization and allow apps to invoke it manually instead of +load.
- [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.
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
19 changes: 19 additions & 0 deletions Source/ASCellNode.mm
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,25 @@ - (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
13 changes: 13 additions & 0 deletions Source/ASCollectionNode+Beta.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (nonatomic, assign) BOOL usesSynchronousDataLoading;

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

/**
* 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:(void (^)(void))didFinishSynchronizing;

- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;

- (instancetype)initWithLayoutDelegate:(id<ASCollectionLayoutDelegate>)layoutDelegate layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
Expand Down
26 changes: 25 additions & 1 deletion 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 @@ -284,7 +308,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks.
*/
- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))didFinishProcessingUpdates;
- (void)onDidFinishProcessingUpdates:(void (^)(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.
Expand Down
128 changes: 122 additions & 6 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 @@ -745,15 +841,35 @@ - (BOOL)isProcessingUpdates
return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO);
}

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

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

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

- (void)waitUntilAllUpdatesAreProcessed
{
ASDisplayNodeAssertMainThread();
Expand Down
8 changes: 7 additions & 1 deletion Source/ASCollectionView.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,9 +296,15 @@ NS_ASSUME_NONNULL_BEGIN
* See ASCollectionNode.h for full documentation of these methods.
*/
@property (nonatomic, readonly) BOOL isProcessingUpdates;
- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion;
- (void)onDidFinishProcessingUpdates:(void (^)(void))completion;
- (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead.");

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

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

0 comments on commit a41cbb4

Please sign in to comment.