diff --git a/AsyncDisplayKit/ASViewController.h b/AsyncDisplayKit/ASViewController.h index dddf7784ef..3b4ce458c3 100644 --- a/AsyncDisplayKit/ASViewController.h +++ b/AsyncDisplayKit/ASViewController.h @@ -21,25 +21,34 @@ NS_ASSUME_NONNULL_BEGIN typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection); typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(CGSize windowSize); +/** + * ASViewController allows you to have a completely node backed hierarchy. It automatically + * handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes. + * + * You can opt-out of node backed hierarchy and use it like a normal UIViewController. + * More importantly, you can use it as a base class for all of your view controllers among which some use a node hierarchy and some don't. + * See examples/ASDKgram project for actual implementation. + */ @interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController /** - * ASViewController Designated initializer. - * - * @discussion ASViewController allows you to have a completely node backed heirarchy. It automatically - * handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes. + * ASViewController initializer. * * @param node An ASDisplayNode which will provide the root view (self.view) * @return An ASViewController instance whose root view will be backed by the provided ASDisplayNode. * * @see ASVisibilityDepth */ -- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithNode:(DisplayNodeType)node; + +NS_ASSUME_NONNULL_END /** * @return node Returns the ASDisplayNode which provides the backing view to the view controller. */ -@property (nonatomic, strong, readonly) DisplayNodeType node; +@property (nonatomic, strong, readonly, null_unspecified) DisplayNodeType node; + +NS_ASSUME_NONNULL_BEGIN /** * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given traitCollection. @@ -91,12 +100,4 @@ typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(C @end -@interface ASViewController (Unavailable) - -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); - -- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:"); - -@end - NS_ASSUME_NONNULL_END diff --git a/AsyncDisplayKit/ASViewController.mm b/AsyncDisplayKit/ASViewController.mm index 38725a8a9e..7d5f6db5c6 100644 --- a/AsyncDisplayKit/ASViewController.mm +++ b/AsyncDisplayKit/ASViewController.mm @@ -33,14 +33,24 @@ - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:"); - return [self initWithNode:[[ASDisplayNode alloc] init]]; + if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { + return nil; + } + + [self _initializeInstance]; + + return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { - ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:"); - return [self initWithNode:[[ASDisplayNode alloc] init]]; + if (!(self = [super initWithCoder:aDecoder])) { + return nil; + } + + [self _initializeInstance]; + + return self; } - (instancetype)initWithNode:(ASDisplayNode *)node @@ -49,10 +59,18 @@ return nil; } - ASDisplayNodeAssertNotNil(node, @"Node must not be nil"); - ASDisplayNodeAssertTrue(!node.layerBacked); _node = node; + [self _initializeInstance]; + return self; +} + +- (void)_initializeInstance +{ + if (_node == nil) { + return; + } + _selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; _nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; _automaticallyAdjustRangeModeBasedOnViewEvents = _selfConformsToRangeModeProtocol || _nodeConformsToRangeModeProtocol; @@ -62,7 +80,7 @@ // Node already loaded the view [self view]; } else { - // If the node didn't load yet add ourselves as on did load observer to laod the view in case the node gets loaded + // If the node didn't load yet add ourselves as on did load observer to load the view in case the node gets loaded // before the view controller __weak __typeof__(self) weakSelf = self; [_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) { @@ -71,8 +89,6 @@ } }]; } - - return self; } - (void)dealloc @@ -82,13 +98,18 @@ - (void)loadView { - ASDisplayNodeAssertTrue(!_node.layerBacked); - // Apple applies a frame and autoresizing masks we need. Allocating a view is not // nearly as expensive as adding and removing it from a hierarchy, and fortunately // we can avoid that here. Enabling layerBacking on a single node in the hierarchy // will have a greater performance benefit than the impact of this transient view. [super loadView]; + + if (_node == nil) { + return; + } + + ASDisplayNodeAssertTrue(!_node.layerBacked); + UIView *view = self.view; CGRect frame = view.frame; UIViewAutoresizing autoresizingMask = view.autoresizingMask; @@ -135,7 +156,7 @@ { if (_ensureDisplayed && self.neverShowPlaceholders) { _ensureDisplayed = NO; - [self.node recursivelyEnsureDisplaySynchronously:YES]; + [_node recursivelyEnsureDisplaySynchronously:YES]; } [super viewDidLayoutSubviews]; } @@ -212,7 +233,9 @@ ASVisibilityDepthImplementation; - (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode { - if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { return; } + if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { + return; + } if (_selfConformsToRangeModeProtocol) { id rangeUpdater = (id)self; @@ -268,7 +291,7 @@ ASVisibilityDepthImplementation; #pragma clang diagnostic ignored "-Wdeprecated-declarations" // Once we've propagated all the traits, layout this node. // Remeasure the node with the latest constrained size – old constrained size may be incorrect. - [self.node layoutThatFits:[self nodeConstrainedSize]]; + [_node layoutThatFits:[self nodeConstrainedSize]]; #pragma clang diagnostic pop } } @@ -286,7 +309,7 @@ ASVisibilityDepthImplementation; { [super didRotateFromInterfaceOrientation:fromInterfaceOrientation]; - ASPrimitiveTraitCollection traitCollection = self.node.primitiveTraitCollection; + ASPrimitiveTraitCollection traitCollection = _node.primitiveTraitCollection; traitCollection.containerSize = self.view.bounds.size; [self propagateNewTraitCollection:traitCollection]; } diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index 905ef9e41f..aab6d55e44 100644 --- a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -36,12 +36,14 @@ CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */; }; CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */; }; CC85250F1E36B392008EABE6 /* FeedHeaderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */; }; + E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 69CE83D91E515036004AA230 /* PhotoFeedControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedControllerProtocol.h; sourceTree = ""; }; 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; 76229A761CBB79E000B62CEF /* WindowWithStatusBarUnderlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowWithStatusBarUnderlay.h; sourceTree = ""; }; @@ -97,6 +99,8 @@ CC85250D1E36B392008EABE6 /* FeedHeaderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedHeaderNode.h; sourceTree = ""; }; CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedHeaderNode.m; sourceTree = ""; }; D09B5DF0BFB37583DE8F3142 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedBaseController.h; sourceTree = ""; }; + E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedBaseController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -183,6 +187,9 @@ 767A5F141CAA3D8A004CDA8D /* Controller */ = { isa = PBXGroup; children = ( + 69CE83D91E515036004AA230 /* PhotoFeedControllerProtocol.h */, + E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */, + E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */, 767A5F161CAA3D96004CDA8D /* UIKit */, 767A5F151CAA3D90004CDA8D /* ASDK */, CC00D1581E159132004E5502 /* ASDK-ListKit */, @@ -380,7 +387,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { @@ -412,6 +419,7 @@ 768843821CAA37EF00D8629E /* CommentModel.m in Sources */, 768843831CAA37EF00D8629E /* CommentsNode.m in Sources */, 768843961CAA37EF00D8629E /* Utilities.m in Sources */, + E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */, 768843931CAA37EF00D8629E /* UserModel.m in Sources */, CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */, 768843801CAA37EF00D8629E /* AppDelegate.m in Sources */, diff --git a/examples/ASDKgram/Sample/AppDelegate.h b/examples/ASDKgram/Sample/AppDelegate.h index cd0d7065ab..f5a8485f9b 100644 --- a/examples/ASDKgram/Sample/AppDelegate.h +++ b/examples/ASDKgram/Sample/AppDelegate.h @@ -17,10 +17,6 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -@protocol PhotoFeedControllerProtocol -- (void)resetAllData; -@end - @interface AppDelegate : UIResponder @end diff --git a/examples/ASDKgram/Sample/PhotoFeedBaseController.h b/examples/ASDKgram/Sample/PhotoFeedBaseController.h new file mode 100644 index 0000000000..10029863c3 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedBaseController.h @@ -0,0 +1,39 @@ +// +// PhotoFeedBaseController.h +// Sample +// +// Created by Huy Nguyen on 20/12/16. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import +#import "PhotoFeedControllerProtocol.h" + +@protocol PhotoFeedControllerProtocol; +@class PhotoFeedModel; + +@interface PhotoFeedBaseController : ASViewController + +@property (nonatomic, strong, readonly) PhotoFeedModel *photoFeed; +@property (nonatomic, strong, readonly) UITableView *tableView; + +- (void)refreshFeed; +- (void)insertNewRows:(NSArray *)newPhotos; + +#pragma mark - Subclasses must override these methods + +- (void)loadPage; +- (void)requestCommentsForPhotos:(NSArray *)newPhotos; + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedBaseController.m b/examples/ASDKgram/Sample/PhotoFeedBaseController.m new file mode 100644 index 0000000000..c901d737e2 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedBaseController.m @@ -0,0 +1,123 @@ +// +// PhotoFeedBaseController.m +// Sample +// +// Created by Huy Nguyen on 20/12/16. +// Copyright © 2016 Facebook. All rights reserved. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +#import "PhotoFeedBaseController.h" +#import "PhotoFeedModel.h" + +@implementation PhotoFeedBaseController +{ + UIActivityIndicatorView *_activityIndicatorView; +} + +// -loadView is guaranteed to be called on the main thread and is the appropriate place to +// set up an UIKit objects you may be using. +- (void)loadView +{ + [super loadView]; + + _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + + _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; + [self refreshFeed]; + + CGSize boundSize = self.view.bounds.size; + [_activityIndicatorView sizeToFit]; + CGRect refreshRect = _activityIndicatorView.frame; + refreshRect.origin = CGPointMake((boundSize.width - _activityIndicatorView.frame.size.width) / 2.0, + (boundSize.height - _activityIndicatorView.frame.size.height) / 2.0); + _activityIndicatorView.frame = refreshRect; + [self.view addSubview:_activityIndicatorView]; + + self.tableView.allowsSelection = NO; + self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + + self.view.backgroundColor = [UIColor whiteColor]; +} + +- (void)refreshFeed +{ + [_activityIndicatorView startAnimating]; + // small first batch + [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){ + + [_activityIndicatorView stopAnimating]; + + [self insertNewRows:newPhotos]; + [self requestCommentsForPhotos:newPhotos]; + + // immediately start second larger fetch + [self loadPage]; + + } numResultsToReturn:4]; +} + +- (void)insertNewRows:(NSArray *)newPhotos +{ + NSInteger section = 0; + NSMutableArray *indexPaths = [NSMutableArray array]; + + NSInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; + for (NSInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { + NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; + [indexPaths addObject:path]; + } + [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return UIStatusBarStyleLightContent; +} + +- (CGSize)imageSizeForScreenWidth +{ + CGRect screenRect = [[UIScreen mainScreen] bounds]; + CGFloat screenScale = [[UIScreen mainScreen] scale]; + return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); +} + +#pragma mark - PhotoFeedViewControllerProtocol + +- (void)resetAllData +{ + [_photoFeed clearFeed]; + [self.tableView reloadData]; + [self refreshFeed]; +} + +#pragma mark - Subclassing + +- (UITableView *)tableView +{ + NSAssert(NO, @"Subclasses must override this method"); + return nil; +} + +- (void)loadPage +{ + NSAssert(NO, @"Subclasses must override this method"); +} + +- (void)requestCommentsForPhotos:(NSArray *)newPhotos +{ + NSAssert(NO, @"Subclasses must override this method"); +} + +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h b/examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h new file mode 100644 index 0000000000..e7c5be3689 --- /dev/null +++ b/examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h @@ -0,0 +1,13 @@ +// +// PhotoFeedControllerProtocol.h +// Sample +// +// Created by Michael Schneider on 2/12/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import + +@protocol PhotoFeedControllerProtocol +- (void)resetAllData; +@end diff --git a/examples/ASDKgram/Sample/PhotoFeedListKitViewController.h b/examples/ASDKgram/Sample/PhotoFeedListKitViewController.h index 7e5c64886d..0720363a4c 100644 --- a/examples/ASDKgram/Sample/PhotoFeedListKitViewController.h +++ b/examples/ASDKgram/Sample/PhotoFeedListKitViewController.h @@ -7,7 +7,7 @@ // #import -#import "AppDelegate.h" +#import "PhotoFeedControllerProtocol.h" @interface PhotoFeedListKitViewController : ASViewController diff --git a/examples/ASDKgram/Sample/PhotoFeedNodeController.h b/examples/ASDKgram/Sample/PhotoFeedNodeController.h index 04c9aa896c..c97576b7bc 100644 --- a/examples/ASDKgram/Sample/PhotoFeedNodeController.h +++ b/examples/ASDKgram/Sample/PhotoFeedNodeController.h @@ -17,9 +17,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import -#import "AppDelegate.h" +#import "PhotoFeedBaseController.h" -@interface PhotoFeedNodeController : ASViewController +@interface PhotoFeedNodeController : PhotoFeedBaseController @end diff --git a/examples/ASDKgram/Sample/PhotoFeedNodeController.m b/examples/ASDKgram/Sample/PhotoFeedNodeController.m index 322c9de49a..1e22925e6d 100644 --- a/examples/ASDKgram/Sample/PhotoFeedNodeController.m +++ b/examples/ASDKgram/Sample/PhotoFeedNodeController.m @@ -31,9 +31,7 @@ @implementation PhotoFeedNodeController { - PhotoFeedModel *_photoFeed; - ASTableNode *_tableNode; - UIActivityIndicatorView *_activityIndicatorView; + ASTableNode *_tableNode; } #pragma mark - Lifecycle @@ -51,7 +49,6 @@ _tableNode.dataSource = self; _tableNode.delegate = self; - } return self; @@ -63,112 +60,48 @@ { [super loadView]; - _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - - _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; - [self refreshFeed]; - - CGSize boundSize = self.view.bounds.size; - - [_activityIndicatorView sizeToFit]; - CGRect refreshRect = _activityIndicatorView.frame; - refreshRect.origin = CGPointMake((boundSize.width - _activityIndicatorView.frame.size.width) / 2.0, - (boundSize.height - _activityIndicatorView.frame.size.height) / 2.0); - _activityIndicatorView.frame = refreshRect; - - [self.view addSubview:_activityIndicatorView]; - - self.view.backgroundColor = [UIColor whiteColor]; - _tableNode.view.allowsSelection = NO; - _tableNode.view.separatorStyle = UITableViewCellSeparatorStyleNone; _tableNode.view.leadingScreensForBatching = AUTO_TAIL_LOADING_NUM_SCREENFULS; // overriding default of 2.0 } -#pragma mark - helper methods - -- (void)refreshFeed -{ - [_activityIndicatorView startAnimating]; - // small first batch - [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){ - - [_activityIndicatorView stopAnimating]; - - [self insertNewRowsInTableNode:newPhotos]; -// [self requestCommentsForPhotos:newPhotos]; - - // immediately start second larger fetch - [self loadPageWithContext:nil]; - - } numResultsToReturn:4]; -} - - (void)loadPageWithContext:(ASBatchContext *)context { - [_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ + [self.photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ - [self insertNewRowsInTableNode:newPhotos]; -// [self requestCommentsForPhotos:newPhotos]; + [self insertNewRows:newPhotos]; + [self requestCommentsForPhotos:newPhotos]; if (context) { [context completeBatchFetching:YES]; } } numResultsToReturn:20]; } -//- (void)requestCommentsForPhotos:(NSArray *)newPhotos -//{ -// for (PhotoModel *photo in newPhotos) { -// [photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) { -// -// NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo]; -// NSIndexPath *cellPath = [NSIndexPath indexPathForRow:rowNum inSection:0]; -// PhotoCellNode *cell = (PhotoCellNode *)[_tableNode.view nodeForRowAtIndexPath:cellPath]; -// -// if (cell) { -// [cell loadCommentsForPhoto:photo]; -// [_tableNode.view beginUpdates]; -// [_tableNode.view endUpdates]; -// } -// }]; -// } -//} +#pragma mark - Subclassing -- (void)insertNewRowsInTableNode:(NSArray *)newPhotos +- (UITableView *)tableView { - NSInteger section = 0; - NSMutableArray *indexPaths = [NSMutableArray array]; - - NSUInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; - for (NSUInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { - NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; - [indexPaths addObject:path]; - } - - [_tableNode insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; + return _tableNode.view; } -- (UIStatusBarStyle)preferredStatusBarStyle +- (void)loadPage { - return UIStatusBarStyleLightContent; + [self loadPageWithContext:nil]; } -- (CGSize)imageSizeForScreenWidth +- (void)requestCommentsForPhotos:(NSArray *)newPhotos { - CGRect screenRect = [[UIScreen mainScreen] bounds]; - CGFloat screenScale = [[UIScreen mainScreen] scale]; - return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); + // Do nothing (#1530). } #pragma mark - ASTableDataSource methods - (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section { - return [_photoFeed numberOfItemsInFeed]; + return [self.photoFeed numberOfItemsInFeed]; } - (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath { - PhotoModel *photoModel = [_photoFeed objectAtIndex:indexPath.row]; + PhotoModel *photoModel = [self.photoFeed objectAtIndex:indexPath.row]; // this will be executed on a background thread - important to make sure it's thread safe ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() { PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhotoObject:photoModel]; @@ -187,13 +120,4 @@ [self loadPageWithContext:context]; } -#pragma mark - PhotoFeedViewControllerProtocol - -- (void)resetAllData -{ - [_photoFeed clearFeed]; - [_tableNode reloadData]; - [self refreshFeed]; -} - @end diff --git a/examples/ASDKgram/Sample/PhotoFeedViewController.h b/examples/ASDKgram/Sample/PhotoFeedViewController.h index 3942ec5c27..3733fde82b 100644 --- a/examples/ASDKgram/Sample/PhotoFeedViewController.h +++ b/examples/ASDKgram/Sample/PhotoFeedViewController.h @@ -17,8 +17,8 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import "AppDelegate.h" +#import "PhotoFeedBaseController.h" -@interface PhotoFeedViewController : UIViewController +@interface PhotoFeedViewController : PhotoFeedBaseController @end diff --git a/examples/ASDKgram/Sample/PhotoFeedViewController.m b/examples/ASDKgram/Sample/PhotoFeedViewController.m index ddaf2a7cf9..3a36861e96 100644 --- a/examples/ASDKgram/Sample/PhotoFeedViewController.m +++ b/examples/ASDKgram/Sample/PhotoFeedViewController.m @@ -30,9 +30,7 @@ @implementation PhotoFeedViewController { - PhotoFeedModel *_photoFeed; - UITableView *_tableView; - UIActivityIndicatorView *_activityIndicatorView; + UITableView *_tableView; } #pragma mark - Lifecycle @@ -49,8 +47,6 @@ _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth; _tableView.delegate = self; _tableView.dataSource = self; - - _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; } return self; @@ -61,51 +57,22 @@ { [super viewDidLoad]; - _photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]]; - [self refreshFeed]; - - CGSize boundSize = self.view.bounds.size; - [self.view addSubview:_tableView]; - _tableView.frame = self.view.bounds; - _tableView.allowsSelection = NO; - _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; [_tableView registerClass:[PhotoTableViewCell class] forCellReuseIdentifier:@"photoCell"]; - - [self.view addSubview:_activityIndicatorView]; - - [_activityIndicatorView sizeToFit]; - CGRect refreshRect = _activityIndicatorView.frame; - refreshRect.origin = CGPointMake((boundSize.width - _activityIndicatorView.frame.size.width) / 2.0, - (boundSize.height - _activityIndicatorView.frame.size.height) / 2.0); - _activityIndicatorView.frame = refreshRect; } -#pragma mark - helper methods +#pragma mark - Subclassing -- (void)refreshFeed +- (UITableView *)tableView { - [_activityIndicatorView startAnimating]; - - // small first batch - [_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){ - - [_activityIndicatorView stopAnimating]; - - [self insertNewRowsInTableView:newPhotos]; - [self requestCommentsForPhotos:newPhotos]; - - // immediately start second larger fetch - [self loadPage]; - - } numResultsToReturn:4]; + return _tableView; } - (void)loadPage { - [_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ - [self insertNewRowsInTableView:newPhotos]; + [self.photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){ + [self insertNewRows:newPhotos]; [self requestCommentsForPhotos:newPhotos]; } numResultsToReturn:20]; } @@ -115,7 +82,7 @@ for (PhotoModel *photo in newPhotos) { [photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) { - NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo]; + NSInteger rowNum = [self.photoFeed indexOfPhotoModel:photo]; NSIndexPath *cellPath = [NSIndexPath indexPathForRow:rowNum inSection:0]; PhotoTableViewCell *cell = [_tableView cellForRowAtIndexPath:cellPath]; @@ -135,49 +102,24 @@ } } -- (void)insertNewRowsInTableView:(NSArray *)newPhotos -{ - NSInteger section = 0; - NSMutableArray *indexPaths = [NSMutableArray array]; - - NSInteger newTotalNumberOfPhotos = [_photoFeed numberOfItemsInFeed]; - for (NSInteger row = newTotalNumberOfPhotos - newPhotos.count; row < newTotalNumberOfPhotos; row++) { - NSIndexPath *path = [NSIndexPath indexPathForRow:row inSection:section]; - [indexPaths addObject:path]; - } - [_tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone]; -} - -- (UIStatusBarStyle)preferredStatusBarStyle -{ - return UIStatusBarStyleLightContent; -} - -- (CGSize)imageSizeForScreenWidth -{ - CGRect screenRect = [[UIScreen mainScreen] bounds]; - CGFloat screenScale = [[UIScreen mainScreen] scale]; - return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale); -} - #pragma mark - UITableViewDataSource methods - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return [_photoFeed numberOfItemsInFeed]; + return [self.photoFeed numberOfItemsInFeed]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"photoCell" forIndexPath:indexPath]; - [cell updateCellWithPhotoObject:[_photoFeed objectAtIndex:indexPath.row]]; + [cell updateCellWithPhotoObject:[self.photoFeed objectAtIndex:indexPath.row]]; return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - PhotoModel *photo = [_photoFeed objectAtIndex:indexPath.row]; + PhotoModel *photo = [self.photoFeed objectAtIndex:indexPath.row]; return [PhotoTableViewCell heightForPhotoModel:photo withWidth:self.view.bounds.size.width]; } @@ -196,13 +138,4 @@ } } -#pragma mark - PhotoFeedViewControllerProtocol - -- (void)resetAllData -{ - [_photoFeed clearFeed]; - [_tableView reloadData]; - [self refreshFeed]; -} - @end