Skip to content

Commit

Permalink
[ASCellNodeVisibilityEvent] Add a new event when scrolling stops
Browse files Browse the repository at this point in the history
We have `ASCellNodeVisibilityEvent` events that roughly correlate to the scrollViewDid… delegate methods in UIScrollView. With the current events we get a callback when a user stops dragging a cell, but if the cell decelerates we do not get an event when it comes to a rest. I’ve added  `ASCellNodeVisibilityEventDidStopScrolling` to have both `ASTableView` and `ASCollectionView` send this event to the cells in `_cellsForVisibilityUpdates` in `- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView`.

I created unit tests to ensure that the proper events are being called for the proper scroll delegate methods.
  • Loading branch information
rcancro committed Jul 25, 2023
1 parent 68dd71c commit 5b73333
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 1 deletion.
4 changes: 4 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@
9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.mm */; };
9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C664E7D2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */; };
9C6BB3B31B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; };
9C70F2051CDA4F06007D6C76 /* ASTraitCollection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.mm */; };
9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -793,6 +794,7 @@
9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutElement.h; sourceTree = "<group>"; };
9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAsciiArtBoxCreator.h; sourceTree = "<group>"; };
9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAsciiArtBoxCreator.mm; sourceTree = "<group>"; };
9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASCellVisibilityScrollEventTests.m; sourceTree = "<group>"; };
9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbsoluteLayoutElement.h; sourceTree = "<group>"; };
9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTraitCollection.h; sourceTree = "<group>"; };
9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTraitCollection.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1401,6 +1403,7 @@
CC583ABF1EF9BAB400134156 /* Common */,
058D09C6195D04C000B7D73C /* Supporting Files */,
052EE06A1A15A0D8002C6279 /* TestResources */,
9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */,
);
path = Tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -2341,6 +2344,7 @@
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.mm in Sources */,
CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.mm in Sources */,
AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.mm in Sources */,
9C664E7D2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m in Sources */,
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.mm in Sources */,
9644CFE02193777C00213478 /* ASThrashUtility.m in Sources */,
696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions Source/ASCellNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
* Indicates user has ended dragging the visible cell
*/
ASCellNodeVisibilityEventDidEndDragging,
/**
* Indicates a cell has stopped scrolling. May not be called if
* ASCellNodeVisibilityEventDidEndDragging did not decelerate
*/
ASCellNodeVisibilityEventDidStopScrolling,
};

/**
Expand Down
5 changes: 4 additions & 1 deletion Source/ASCollectionView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1655,7 +1655,10 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
_deceleratingVelocity = CGPointZero;

for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) {
[cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidStopScrolling inScrollView:scrollView];
}

if (_asyncDelegateFlags.scrollViewDidEndDecelerating) {
[_asyncDelegate scrollViewDidEndDecelerating:scrollView];
}
Expand Down
5 changes: 5 additions & 0 deletions Source/ASTableView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,11 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
}
_deceleratingVelocity = CGPointZero;

for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) {
[[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidStopScrolling
inScrollView:scrollView
withCellFrame:tableViewCell.frame];
}
if (_asyncDelegateFlags.scrollViewDidEndDecelerating) {
[_asyncDelegate scrollViewDidEndDecelerating:scrollView];
}
Expand Down
222 changes: 222 additions & 0 deletions Tests/ASCellVisibilityScrollEventTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
//
// ASCellVisibilityScrollEventTests.m
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//

#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

#import <AsyncDisplayKit/AsyncDisplayKit.h>

@interface ASCellVisibilityTestNode: ASTextCellNode
@property (nonatomic) NSUInteger cellNodeVisibilityEventVisibleCount;
@property (nonatomic) NSUInteger cellNodeVisibilityEventVisibleRectChangedCount;
@property (nonatomic) NSUInteger cellNodeVisibilityEventInvisibleCount;
@property (nonatomic) NSUInteger cellNodeVisibilityEventWillBeginDraggingCount;
@property (nonatomic) NSUInteger cellNodeVisibilityEventDidEndDraggingCount;
@property (nonatomic) NSUInteger cellNodeVisibilityEventDidStopScrollingCount;
@end

@implementation ASCellVisibilityTestNode

- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame
{
switch (event) {
case ASCellNodeVisibilityEventVisible:
self.cellNodeVisibilityEventVisibleCount++;
break;
case ASCellNodeVisibilityEventVisibleRectChanged:
self.cellNodeVisibilityEventVisibleRectChangedCount++;
break;
case ASCellNodeVisibilityEventInvisible:
self.cellNodeVisibilityEventInvisibleCount++;
break;
case ASCellNodeVisibilityEventWillBeginDragging:
self.cellNodeVisibilityEventWillBeginDraggingCount++;
break;
case ASCellNodeVisibilityEventDidEndDragging:
self.cellNodeVisibilityEventDidEndDraggingCount++;
break;
case ASCellNodeVisibilityEventDidStopScrolling:
self.cellNodeVisibilityEventDidStopScrollingCount++;
break;
}
}

@end

@interface ASTableView (Private_Testing)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
@end

@interface ASCellVisibilityTableViewTestController: UIViewController<ASTableDataSource>

@property (nonatomic) ASTableNode *tableNode;
@property (nonatomic) ASTableView *tableView;

@end

@implementation ASCellVisibilityTableViewTestController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.tableNode = [[ASTableNode alloc] init];
self.tableView = self.tableNode.view;
self.tableNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.tableNode.dataSource = self;

[self.view addSubview:self.tableView];
}
return self;
}

- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section
{
return 1;
}

- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath;
{
return ^{
ASCellVisibilityTestNode *cell = [[ASCellVisibilityTestNode alloc] init];
return cell;
};
}

@end

@interface ASCollectionView (Private_Testing)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath;
@end

@interface ASCellVisibilityCollectionViewTestController: UIViewController<ASCollectionDataSource>

@property (nonatomic) ASCollectionNode *collectionNode;
@property (nonatomic) ASCollectionView *collectionView;

@end

@implementation ASCellVisibilityCollectionViewTestController

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
id realLayout = [UICollectionViewFlowLayout new];
self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:realLayout];
self.collectionView = self.collectionNode.view;
self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.collectionNode.dataSource = self;

[self.view addSubview:self.collectionView];
}
return self;
}

- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section
{
return 1;
}

- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
{
return ^{
ASCellVisibilityTestNode *cell = [[ASCellVisibilityTestNode alloc] init];
return cell;
};
}

@end


@interface ASCellVisibilityScrollEventTests : XCTestCase
@end

@implementation ASCellVisibilityScrollEventTests

- (void)testTableNodeEvents
{
ASCellVisibilityTableViewTestController *testController = [[ASCellVisibilityTableViewTestController alloc] initWithNibName:nil bundle:nil];

UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window setRootViewController:testController];
[window makeKeyAndVisible];

[testController.tableNode reloadData];
[testController.tableNode waitUntilAllUpdatesAreProcessed];
[testController.tableNode layoutIfNeeded];

ASTableView *tableView = testController.tableView;

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
ASCellVisibilityTestNode *cell = (ASCellVisibilityTestNode *)[testController.tableNode nodeForRowAtIndexPath:indexPath];
UITableViewCell *uicell = [testController.tableNode cellForRowAtIndexPath:indexPath];

// Pretend the cell is appearing so it is added to _cellsForVisibilityUpdates
[tableView tableView:tableView willDisplayCell:uicell forRowAtIndexPath:indexPath];

// simulator scrollViewDidScroll so we can see if the cell got the event
[tableView scrollViewDidScroll:tableView];
XCTAssertTrue(cell.cellNodeVisibilityEventVisibleRectChangedCount == 1);

[tableView scrollViewDidEndDecelerating:tableView];
XCTAssertTrue(cell.cellNodeVisibilityEventDidStopScrollingCount == 1);

[tableView scrollViewWillBeginDragging:tableView];
XCTAssertTrue(cell.cellNodeVisibilityEventWillBeginDraggingCount == 1);

[tableView scrollViewDidEndDragging:tableView willDecelerate:YES];
XCTAssertTrue(cell.cellNodeVisibilityEventDidEndDraggingCount == 1);

}

- (void)testCollectionNodeEvents
{
ASCellVisibilityCollectionViewTestController *testController = [[ASCellVisibilityCollectionViewTestController alloc] initWithNibName:nil bundle:nil];

UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window setRootViewController:testController];
[window makeKeyAndVisible];

[testController.collectionNode reloadData];
[testController.collectionNode waitUntilAllUpdatesAreProcessed];
[testController.collectionNode layoutIfNeeded];

ASCollectionView *collectionView = testController.collectionView;

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
ASCellVisibilityTestNode *cell = (ASCellVisibilityTestNode *)[testController.collectionNode nodeForItemAtIndexPath:indexPath];
UICollectionViewCell *uicell = [testController.collectionNode cellForItemAtIndexPath:indexPath];

// Pretend the cell is appearing so it is added to _cellsForVisibilityUpdates
[collectionView collectionView:collectionView willDisplayCell:uicell forItemAtIndexPath:indexPath];

// simulator scrollViewDidScroll so we can see if the cell got the event
[collectionView scrollViewDidScroll:collectionView];
XCTAssertTrue(cell.cellNodeVisibilityEventVisibleRectChangedCount == 1);

[collectionView scrollViewDidEndDecelerating:collectionView];
XCTAssertTrue(cell.cellNodeVisibilityEventDidStopScrollingCount == 1);

[collectionView scrollViewWillBeginDragging:collectionView];
XCTAssertTrue(cell.cellNodeVisibilityEventWillBeginDraggingCount == 1);

[collectionView scrollViewDidEndDragging:collectionView willDecelerate:YES];
XCTAssertTrue(cell.cellNodeVisibilityEventDidEndDraggingCount == 1);
}


@end

0 comments on commit 5b73333

Please sign in to comment.