Skip to content

Commit 28e3bae

Browse files
authored
[ASCellNodeVisibilityEvent] Add a new event when scrolling stops (#2084)
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.
1 parent a90ea0e commit 28e3bae

File tree

5 files changed

+240
-1
lines changed

5 files changed

+240
-1
lines changed

AsyncDisplayKit.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@
213213
9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; };
214214
9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.mm */; };
215215
9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */; settings = {ATTRIBUTES = (Public, ); }; };
216+
9C664E7D2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */; };
216217
9C6BB3B31B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */; settings = {ATTRIBUTES = (Public, ); }; };
217218
9C70F2051CDA4F06007D6C76 /* ASTraitCollection.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.mm */; };
218219
9C70F2061CDA4F0C007D6C76 /* ASTraitCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -793,6 +794,7 @@
793794
9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutElement.h; sourceTree = "<group>"; };
794795
9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAsciiArtBoxCreator.h; sourceTree = "<group>"; };
795796
9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAsciiArtBoxCreator.mm; sourceTree = "<group>"; };
797+
9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASCellVisibilityScrollEventTests.m; sourceTree = "<group>"; };
796798
9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbsoluteLayoutElement.h; sourceTree = "<group>"; };
797799
9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTraitCollection.h; sourceTree = "<group>"; };
798800
9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTraitCollection.mm; sourceTree = "<group>"; };
@@ -1401,6 +1403,7 @@
14011403
CC583ABF1EF9BAB400134156 /* Common */,
14021404
058D09C6195D04C000B7D73C /* Supporting Files */,
14031405
052EE06A1A15A0D8002C6279 /* TestResources */,
1406+
9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */,
14041407
);
14051408
path = Tests;
14061409
sourceTree = "<group>";
@@ -2341,6 +2344,7 @@
23412344
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.mm in Sources */,
23422345
CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.mm in Sources */,
23432346
AE6987C11DD04E1000B9E458 /* ASPagerNodeTests.mm in Sources */,
2347+
9C664E7D2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m in Sources */,
23442348
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.mm in Sources */,
23452349
9644CFE02193777C00213478 /* ASThrashUtility.m in Sources */,
23462350
696FCB311D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm in Sources */,

Source/ASCellNode.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) {
4040
* Indicates user has ended dragging the visible cell
4141
*/
4242
ASCellNodeVisibilityEventDidEndDragging,
43+
/**
44+
* Indicates a cell has stopped scrolling. May not be called if
45+
* ASCellNodeVisibilityEventDidEndDragging did not decelerate
46+
*/
47+
ASCellNodeVisibilityEventDidStopScrolling,
4348
};
4449

4550
/**

Source/ASCollectionView.mm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1655,7 +1655,10 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoi
16551655
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
16561656
{
16571657
_deceleratingVelocity = CGPointZero;
1658-
1658+
for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) {
1659+
[cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidStopScrolling inScrollView:scrollView];
1660+
}
1661+
16591662
if (_asyncDelegateFlags.scrollViewDidEndDecelerating) {
16601663
[_asyncDelegate scrollViewDidEndDecelerating:scrollView];
16611664
}

Source/ASTableView.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,11 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
13061306
}
13071307
_deceleratingVelocity = CGPointZero;
13081308

1309+
for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) {
1310+
[[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventDidStopScrolling
1311+
inScrollView:scrollView
1312+
withCellFrame:tableViewCell.frame];
1313+
}
13091314
if (_asyncDelegateFlags.scrollViewDidEndDecelerating) {
13101315
[_asyncDelegate scrollViewDidEndDecelerating:scrollView];
13111316
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
//
2+
// ASCellVisibilityScrollEventTests.m
3+
// Texture
4+
//
5+
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
6+
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
7+
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
10+
#import <Foundation/Foundation.h>
11+
#import <XCTest/XCTest.h>
12+
13+
#import <AsyncDisplayKit/AsyncDisplayKit.h>
14+
15+
@interface ASCellVisibilityTestNode: ASTextCellNode
16+
@property (nonatomic) NSUInteger cellNodeVisibilityEventVisibleCount;
17+
@property (nonatomic) NSUInteger cellNodeVisibilityEventVisibleRectChangedCount;
18+
@property (nonatomic) NSUInteger cellNodeVisibilityEventInvisibleCount;
19+
@property (nonatomic) NSUInteger cellNodeVisibilityEventWillBeginDraggingCount;
20+
@property (nonatomic) NSUInteger cellNodeVisibilityEventDidEndDraggingCount;
21+
@property (nonatomic) NSUInteger cellNodeVisibilityEventDidStopScrollingCount;
22+
@end
23+
24+
@implementation ASCellVisibilityTestNode
25+
26+
- (void)cellNodeVisibilityEvent:(ASCellNodeVisibilityEvent)event inScrollView:(UIScrollView *)scrollView withCellFrame:(CGRect)cellFrame
27+
{
28+
switch (event) {
29+
case ASCellNodeVisibilityEventVisible:
30+
self.cellNodeVisibilityEventVisibleCount++;
31+
break;
32+
case ASCellNodeVisibilityEventVisibleRectChanged:
33+
self.cellNodeVisibilityEventVisibleRectChangedCount++;
34+
break;
35+
case ASCellNodeVisibilityEventInvisible:
36+
self.cellNodeVisibilityEventInvisibleCount++;
37+
break;
38+
case ASCellNodeVisibilityEventWillBeginDragging:
39+
self.cellNodeVisibilityEventWillBeginDraggingCount++;
40+
break;
41+
case ASCellNodeVisibilityEventDidEndDragging:
42+
self.cellNodeVisibilityEventDidEndDraggingCount++;
43+
break;
44+
case ASCellNodeVisibilityEventDidStopScrolling:
45+
self.cellNodeVisibilityEventDidStopScrollingCount++;
46+
break;
47+
}
48+
}
49+
50+
@end
51+
52+
@interface ASTableView (Private_Testing)
53+
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
54+
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
55+
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
56+
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
57+
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath;
58+
@end
59+
60+
@interface ASCellVisibilityTableViewTestController: UIViewController<ASTableDataSource>
61+
62+
@property (nonatomic) ASTableNode *tableNode;
63+
@property (nonatomic) ASTableView *tableView;
64+
65+
@end
66+
67+
@implementation ASCellVisibilityTableViewTestController
68+
69+
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
70+
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
71+
if (self) {
72+
self.tableNode = [[ASTableNode alloc] init];
73+
self.tableView = self.tableNode.view;
74+
self.tableNode.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
75+
self.tableNode.dataSource = self;
76+
77+
[self.view addSubview:self.tableView];
78+
}
79+
return self;
80+
}
81+
82+
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section
83+
{
84+
return 1;
85+
}
86+
87+
- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath;
88+
{
89+
return ^{
90+
ASCellVisibilityTestNode *cell = [[ASCellVisibilityTestNode alloc] init];
91+
return cell;
92+
};
93+
}
94+
95+
@end
96+
97+
@interface ASCollectionView (Private_Testing)
98+
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
99+
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
100+
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
101+
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
102+
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)rawCell forItemAtIndexPath:(NSIndexPath *)indexPath;
103+
@end
104+
105+
@interface ASCellVisibilityCollectionViewTestController: UIViewController<ASCollectionDataSource>
106+
107+
@property (nonatomic) ASCollectionNode *collectionNode;
108+
@property (nonatomic) ASCollectionView *collectionView;
109+
110+
@end
111+
112+
@implementation ASCellVisibilityCollectionViewTestController
113+
114+
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
115+
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
116+
if (self) {
117+
id realLayout = [UICollectionViewFlowLayout new];
118+
self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:realLayout];
119+
self.collectionView = self.collectionNode.view;
120+
self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
121+
self.collectionNode.dataSource = self;
122+
123+
[self.view addSubview:self.collectionView];
124+
}
125+
return self;
126+
}
127+
128+
- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section
129+
{
130+
return 1;
131+
}
132+
133+
- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath
134+
{
135+
return ^{
136+
ASCellVisibilityTestNode *cell = [[ASCellVisibilityTestNode alloc] init];
137+
return cell;
138+
};
139+
}
140+
141+
@end
142+
143+
144+
@interface ASCellVisibilityScrollEventTests : XCTestCase
145+
@end
146+
147+
@implementation ASCellVisibilityScrollEventTests
148+
149+
- (void)testTableNodeEvents
150+
{
151+
ASCellVisibilityTableViewTestController *testController = [[ASCellVisibilityTableViewTestController alloc] initWithNibName:nil bundle:nil];
152+
153+
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
154+
[window setRootViewController:testController];
155+
[window makeKeyAndVisible];
156+
157+
[testController.tableNode reloadData];
158+
[testController.tableNode waitUntilAllUpdatesAreProcessed];
159+
[testController.tableNode layoutIfNeeded];
160+
161+
ASTableView *tableView = testController.tableView;
162+
163+
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
164+
ASCellVisibilityTestNode *cell = (ASCellVisibilityTestNode *)[testController.tableNode nodeForRowAtIndexPath:indexPath];
165+
UITableViewCell *uicell = [testController.tableNode cellForRowAtIndexPath:indexPath];
166+
167+
// Pretend the cell is appearing so it is added to _cellsForVisibilityUpdates
168+
[tableView tableView:tableView willDisplayCell:uicell forRowAtIndexPath:indexPath];
169+
170+
// simulator scrollViewDidScroll so we can see if the cell got the event
171+
[tableView scrollViewDidScroll:tableView];
172+
XCTAssertTrue(cell.cellNodeVisibilityEventVisibleRectChangedCount == 1);
173+
174+
[tableView scrollViewDidEndDecelerating:tableView];
175+
XCTAssertTrue(cell.cellNodeVisibilityEventDidStopScrollingCount == 1);
176+
177+
[tableView scrollViewWillBeginDragging:tableView];
178+
XCTAssertTrue(cell.cellNodeVisibilityEventWillBeginDraggingCount == 1);
179+
180+
[tableView scrollViewDidEndDragging:tableView willDecelerate:YES];
181+
XCTAssertTrue(cell.cellNodeVisibilityEventDidEndDraggingCount == 1);
182+
183+
}
184+
185+
- (void)testCollectionNodeEvents
186+
{
187+
ASCellVisibilityCollectionViewTestController *testController = [[ASCellVisibilityCollectionViewTestController alloc] initWithNibName:nil bundle:nil];
188+
189+
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
190+
[window setRootViewController:testController];
191+
[window makeKeyAndVisible];
192+
193+
[testController.collectionNode reloadData];
194+
[testController.collectionNode waitUntilAllUpdatesAreProcessed];
195+
[testController.collectionNode layoutIfNeeded];
196+
197+
ASCollectionView *collectionView = testController.collectionView;
198+
199+
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
200+
ASCellVisibilityTestNode *cell = (ASCellVisibilityTestNode *)[testController.collectionNode nodeForItemAtIndexPath:indexPath];
201+
UICollectionViewCell *uicell = [testController.collectionNode cellForItemAtIndexPath:indexPath];
202+
203+
// Pretend the cell is appearing so it is added to _cellsForVisibilityUpdates
204+
[collectionView collectionView:collectionView willDisplayCell:uicell forItemAtIndexPath:indexPath];
205+
206+
// simulator scrollViewDidScroll so we can see if the cell got the event
207+
[collectionView scrollViewDidScroll:collectionView];
208+
XCTAssertTrue(cell.cellNodeVisibilityEventVisibleRectChangedCount == 1);
209+
210+
[collectionView scrollViewDidEndDecelerating:collectionView];
211+
XCTAssertTrue(cell.cellNodeVisibilityEventDidStopScrollingCount == 1);
212+
213+
[collectionView scrollViewWillBeginDragging:collectionView];
214+
XCTAssertTrue(cell.cellNodeVisibilityEventWillBeginDraggingCount == 1);
215+
216+
[collectionView scrollViewDidEndDragging:collectionView willDecelerate:YES];
217+
XCTAssertTrue(cell.cellNodeVisibilityEventDidEndDraggingCount == 1);
218+
}
219+
220+
221+
@end
222+

0 commit comments

Comments
 (0)