diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index c7da8e107d..4248e98a99 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -254,6 +254,8 @@ AC47D9451B3BB41900AAEE9D /* ASRelativeSize.h in Headers */ = {isa = PBXBuildFile; fileRef = AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */; settings = {ATTRIBUTES = (Public, ); }; }; AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */ = {isa = PBXBuildFile; fileRef = AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */; }; AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = AC6456071B0A335000CF11B8 /* ASCellNode.m */; }; + AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; }; + AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */; }; ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */; }; ACF6ED1A1B17843500DA7C62 /* ASBackgroundLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -617,6 +619,7 @@ AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeSize.h; path = AsyncDisplayKit/Layout/ASRelativeSize.h; sourceTree = ""; }; AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeSize.mm; path = AsyncDisplayKit/Layout/ASRelativeSize.mm; sourceTree = ""; }; AC6456071B0A335000CF11B8 /* ASCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCellNode.m; sourceTree = ""; }; + AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableViewInternal.h; sourceTree = ""; }; ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASViewController.h; sourceTree = ""; }; ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASViewController.m; sourceTree = ""; }; ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBackgroundLayoutSpec.h; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h; sourceTree = ""; }; @@ -803,6 +806,7 @@ D785F6611A74327E00291744 /* ASScrollNode.m */, 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */, 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */, + AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */, 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */, 058D09DF195D050800B7D73C /* ASTextNode.h */, 058D09E0195D050800B7D73C /* ASTextNode.mm */, @@ -1128,6 +1132,7 @@ 058D0A4C195D05CB00B7D73C /* ASDisplayNode+Subclasses.h in Headers */, 058D0A4A195D05CB00B7D73C /* ASDisplayNode.h in Headers */, 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */, + AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */, @@ -1243,6 +1248,7 @@ B350625B1B010F070018CF92 /* ASEqualityHelpers.h in Headers */, B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */, B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */, + AC7A2C181BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, B35062531B010EFD0018CF92 /* ASImageNode+CGExtras.h in Headers */, B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 9dded57ba4..f1d4d3d5fb 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -7,6 +7,7 @@ */ #import "ASTableView.h" +#import "ASTableViewInternal.h" #import "ASAssert.h" #import "ASChangeSetDataController.h" @@ -155,7 +156,6 @@ static BOOL _isInterceptedSelector(SEL sel) _ASTableViewProxy *_proxyDataSource; _ASTableViewProxy *_proxyDelegate; - ASDataController *_dataController; ASFlowLayoutController *_layoutController; ASRangeController *_rangeController; @@ -174,6 +174,7 @@ static BOOL _isInterceptedSelector(SEL sel) } @property (atomic, assign) BOOL asyncDataSourceLocked; +@property (nonatomic, retain, readwrite) ASDataController *dataController; @end @@ -199,24 +200,29 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { } } ++ (Class)dataControllerClass +{ + return [ASChangeSetDataController class]; +} + #pragma mark - #pragma mark Lifecycle -- (void)configureWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +- (void)configureWithDataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetching { _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; _rangeController = [[ASRangeController alloc] init]; _rangeController.layoutController = _layoutController; _rangeController.delegate = self; - - _dataController = [[ASChangeSetDataController alloc] initWithAsyncDataFetching:asyncDataFetchingEnabled]; + + _dataController = [[dataControllerClass alloc] initWithAsyncDataFetching:asyncDataFetching]; _dataController.dataSource = self; _dataController.delegate = _rangeController; _layoutController.dataSource = _dataController; - _asyncDataFetchingEnabled = asyncDataFetchingEnabled; + _asyncDataFetchingEnabled = asyncDataFetching; _asyncDataSourceLocked = NO; _leadingScreensForBatching = 1.0; @@ -236,6 +242,11 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { } - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + return [self initWithFrame:frame style:style dataControllerClass:[self.class dataControllerClass] asyncDataFetching:asyncDataFetchingEnabled]; +} + +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled { if (!(self = [super initWithFrame:frame style:style])) return nil; @@ -244,8 +255,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { // https://github.com/facebook/AsyncDisplayKit/issues/385 asyncDataFetchingEnabled = NO; - [self configureWithAsyncDataFetching:asyncDataFetchingEnabled]; - + [self configureWithDataControllerClass:dataControllerClass asyncDataFetching:asyncDataFetchingEnabled]; + return self; } @@ -254,7 +265,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { if (!(self = [super initWithCoder:aDecoder])) return nil; - [self configureWithAsyncDataFetching:NO]; + [self configureWithDataControllerClass:[self.class dataControllerClass] asyncDataFetching:NO]; return self; } diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/AsyncDisplayKit/ASTableViewInternal.h new file mode 100644 index 0000000000..af940837e2 --- /dev/null +++ b/AsyncDisplayKit/ASTableViewInternal.h @@ -0,0 +1,31 @@ +// +// ASTableViewInternal.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 26/10/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "ASTableView.h" + +@class ASDataController; + +@interface ASTableView (Internal) + +@property (nonatomic, retain, readonly) ASDataController *dataController; + +/** + * Initializer. + * + * @param frame A rectangle specifying the initial location and size of the table view in its superview’€™s coordinates. + * The frame of the table view changes as table cells are added and deleted. + * + * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. + * + * @param dataControllerClass A controller class injected to and used to create a data controller for the table view. + * + * @param asyncDataFetchingEnabled This option is reserved for future use, and currently a no-op. + */ +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass asyncDataFetching:(BOOL)asyncDataFetchingEnabled; + +@end diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index e492379a62..b4e137bcd7 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -9,18 +9,44 @@ #import #import "ASTableView.h" +#import "ASTableViewInternal.h" #import "ASDisplayNode+Subclasses.h" +#import "ASChangeSetDataController.h" #define NumberOfSections 10 #define NumberOfRowsPerSection 20 #define NumberOfReloadIterations 50 +@interface ASTestDataController : ASChangeSetDataController +@property (atomic) int numberOfAllNodesRelayouts; +@end + +@implementation ASTestDataController + +- (void)relayoutAllNodes +{ + _numberOfAllNodesRelayouts++; + [super relayoutAllNodes]; +} + +@end + @interface ASTestTableView : ASTableView @property (atomic, copy) void (^willDeallocBlock)(ASTableView *tableView); @end @implementation ASTestTableView +- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + return [super initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] asyncDataFetching:asyncDataFetchingEnabled]; +} + +- (ASTestDataController *)testDataController +{ + return (ASTestDataController *)self.dataController; +} + - (void)dealloc { if (_willDeallocBlock) { @@ -219,7 +245,7 @@ } } -- (void)testRelayoutAllRowsWithNonZeroSizeInitially +- (void)testRelayoutAllNodesWithNonZeroSizeInitially { // Initial width of the table view is non-zero and all nodes are measured with this size. // Any subsequence size change must trigger a relayout. @@ -233,12 +259,14 @@ tableView.asyncDelegate = dataSource; tableView.asyncDataSource = dataSource; + + [tableView layoutIfNeeded]; - [self triggerFirstLayoutMeasurementForTableView:tableView]; - [self triggerSizeChangeAndAssertRelayoutAllRowsForTableView:tableView newSize:tableViewFinalSize]; + XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 0); + [self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize]; } -- (void)testRelayoutAllRowsWithZeroSizeInitially +- (void)testRelayoutAllNodesWithZeroSizeInitially { // Initial width of the table view is 0. The first size change is part of the initial config. // Any subsequence size change after that must trigger a relayout. @@ -256,10 +284,10 @@ [superview addSubview:tableView]; // Width and height are swapped so that a later size change will simulate a rotation tableView.frame = CGRectMake(0, 0, tableViewFinalSize.height, tableViewFinalSize.width); - // Trigger layout measurement on all nodes [tableView layoutIfNeeded]; - [self triggerSizeChangeAndAssertRelayoutAllRowsForTableView:tableView newSize:tableViewFinalSize]; + XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 0); + [self triggerSizeChangeAndAssertRelayoutAllNodesForTableView:tableView newSize:tableViewFinalSize]; } - (void)testRelayoutVisibleRowsWhenEditingModeIsChanged @@ -407,7 +435,7 @@ }]; } -- (void)triggerSizeChangeAndAssertRelayoutAllRowsForTableView:(ASTableView *)tableView newSize:(CGSize)newSize +- (void)triggerSizeChangeAndAssertRelayoutAllNodesForTableView:(ASTestTableView *)tableView newSize:(CGSize)newSize { XCTestExpectation *nodesMeasuredUsingNewConstrainedSizeExpectation = [self expectationWithDescription:@"nodesMeasuredUsingNewConstrainedSize"]; @@ -419,6 +447,8 @@ [tableView layoutIfNeeded]; [tableView endUpdatesAnimated:NO completion:^(BOOL completed) { + XCTAssertEqual(tableView.testDataController.numberOfAllNodesRelayouts, 1); + for (int section = 0; section < NumberOfSections; section++) { for (int row = 0; row < NumberOfRowsPerSection; row++) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section];