diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index ce40a8a61..f6f4fc28b 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -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, ); }; }; @@ -793,6 +794,7 @@ 9C49C36E1B853957000B0DD5 /* ASStackLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASStackLayoutElement.h; sourceTree = ""; }; 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAsciiArtBoxCreator.h; sourceTree = ""; }; 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASAsciiArtBoxCreator.mm; sourceTree = ""; }; + 9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASCellVisibilityScrollEventTests.m; sourceTree = ""; }; 9C6BB3B01B8CC9C200F13F52 /* ASAbsoluteLayoutElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAbsoluteLayoutElement.h; sourceTree = ""; }; 9C70F2011CDA4EFA007D6C76 /* ASTraitCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTraitCollection.h; sourceTree = ""; }; 9C70F2021CDA4EFA007D6C76 /* ASTraitCollection.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTraitCollection.mm; sourceTree = ""; }; @@ -1401,6 +1403,7 @@ CC583ABF1EF9BAB400134156 /* Common */, 058D09C6195D04C000B7D73C /* Supporting Files */, 052EE06A1A15A0D8002C6279 /* TestResources */, + 9C664E7C2A7048BE0059B2AB /* ASCellVisibilityScrollEventTests.m */, ); path = Tests; sourceTree = ""; @@ -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 */, diff --git a/Source/ASCellNode.h b/Source/ASCellNode.h index c09becc4f..d0e751c61 100644 --- a/Source/ASCellNode.h +++ b/Source/ASCellNode.h @@ -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, }; /** diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index e08c55f98..c6ba31683 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -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]; } diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 195cb183c..be916d2e7 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -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]; } diff --git a/Tests/ASCellVisibilityScrollEventTests.m b/Tests/ASCellVisibilityScrollEventTests.m new file mode 100644 index 000000000..c056496d1 --- /dev/null +++ b/Tests/ASCellVisibilityScrollEventTests.m @@ -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 +#import + +#import + +@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 + +@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 + +@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 +