mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-13 18:00:17 +00:00
[ASViewController] Support optional node (#3021)
* ASViewController can be used without a node - If a node isn't provided by developers via -initWithNode:, a default one will be created and used internally. - This allows developers to use ASViewController like a normal UIViewController and as a base class for all view controllers among which some use a node hierarchy and some don't. * Update ASDKgram to use a shared base ASViewController * Minor fixes in ASViewController: - If its node isn't provided by users, don't replace the view controller's view with the default node's view because it might be loaded from a nib. - Init a vanilla ASDisplayNode if a node isn't provided. * Some smaller cleanup * Remove dummy node for ASViewController if it’s used without a node
This commit is contained in:
parent
cd448a105e
commit
aecd36a4df
@ -21,25 +21,34 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection);
|
typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection);
|
||||||
typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(CGSize windowSize);
|
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 <ASVisibilityDepth>
|
@interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController <ASVisibilityDepth>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ASViewController Designated initializer.
|
* ASViewController 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.
|
|
||||||
*
|
*
|
||||||
* @param node An ASDisplayNode which will provide the root view (self.view)
|
* @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.
|
* @return An ASViewController instance whose root view will be backed by the provided ASDisplayNode.
|
||||||
*
|
*
|
||||||
* @see ASVisibilityDepth
|
* @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.
|
* @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.
|
* 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
|
@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
|
NS_ASSUME_NONNULL_END
|
||||||
|
|||||||
@ -33,14 +33,24 @@
|
|||||||
|
|
||||||
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
|
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:");
|
if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
|
||||||
return [self initWithNode:[[ASDisplayNode alloc] init]];
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self _initializeInstance];
|
||||||
|
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
- (instancetype)initWithCoder:(NSCoder *)aDecoder
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:");
|
if (!(self = [super initWithCoder:aDecoder])) {
|
||||||
return [self initWithNode:[[ASDisplayNode alloc] init]];
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
[self _initializeInstance];
|
||||||
|
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithNode:(ASDisplayNode *)node
|
- (instancetype)initWithNode:(ASDisplayNode *)node
|
||||||
@ -49,9 +59,17 @@
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASDisplayNodeAssertNotNil(node, @"Node must not be nil");
|
|
||||||
ASDisplayNodeAssertTrue(!node.layerBacked);
|
|
||||||
_node = node;
|
_node = node;
|
||||||
|
[self _initializeInstance];
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)_initializeInstance
|
||||||
|
{
|
||||||
|
if (_node == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)];
|
_selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)];
|
||||||
_nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)];
|
_nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)];
|
||||||
@ -62,7 +80,7 @@
|
|||||||
// Node already loaded the view
|
// Node already loaded the view
|
||||||
[self view];
|
[self view];
|
||||||
} else {
|
} 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
|
// before the view controller
|
||||||
__weak __typeof__(self) weakSelf = self;
|
__weak __typeof__(self) weakSelf = self;
|
||||||
[_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) {
|
[_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) {
|
||||||
@ -71,8 +89,6 @@
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
@ -82,13 +98,18 @@
|
|||||||
|
|
||||||
- (void)loadView
|
- (void)loadView
|
||||||
{
|
{
|
||||||
ASDisplayNodeAssertTrue(!_node.layerBacked);
|
|
||||||
|
|
||||||
// Apple applies a frame and autoresizing masks we need. Allocating a view is not
|
// 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
|
// 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
|
// 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.
|
// will have a greater performance benefit than the impact of this transient view.
|
||||||
[super loadView];
|
[super loadView];
|
||||||
|
|
||||||
|
if (_node == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASDisplayNodeAssertTrue(!_node.layerBacked);
|
||||||
|
|
||||||
UIView *view = self.view;
|
UIView *view = self.view;
|
||||||
CGRect frame = view.frame;
|
CGRect frame = view.frame;
|
||||||
UIViewAutoresizing autoresizingMask = view.autoresizingMask;
|
UIViewAutoresizing autoresizingMask = view.autoresizingMask;
|
||||||
@ -135,7 +156,7 @@
|
|||||||
{
|
{
|
||||||
if (_ensureDisplayed && self.neverShowPlaceholders) {
|
if (_ensureDisplayed && self.neverShowPlaceholders) {
|
||||||
_ensureDisplayed = NO;
|
_ensureDisplayed = NO;
|
||||||
[self.node recursivelyEnsureDisplaySynchronously:YES];
|
[_node recursivelyEnsureDisplaySynchronously:YES];
|
||||||
}
|
}
|
||||||
[super viewDidLayoutSubviews];
|
[super viewDidLayoutSubviews];
|
||||||
}
|
}
|
||||||
@ -212,7 +233,9 @@ ASVisibilityDepthImplementation;
|
|||||||
|
|
||||||
- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode
|
- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode
|
||||||
{
|
{
|
||||||
if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { return; }
|
if (!_automaticallyAdjustRangeModeBasedOnViewEvents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_selfConformsToRangeModeProtocol) {
|
if (_selfConformsToRangeModeProtocol) {
|
||||||
id<ASRangeControllerUpdateRangeProtocol> rangeUpdater = (id<ASRangeControllerUpdateRangeProtocol>)self;
|
id<ASRangeControllerUpdateRangeProtocol> rangeUpdater = (id<ASRangeControllerUpdateRangeProtocol>)self;
|
||||||
@ -268,7 +291,7 @@ ASVisibilityDepthImplementation;
|
|||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
// Once we've propagated all the traits, layout this node.
|
// Once we've propagated all the traits, layout this node.
|
||||||
// Remeasure the node with the latest constrained size – old constrained size may be incorrect.
|
// 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
|
#pragma clang diagnostic pop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,7 +309,7 @@ ASVisibilityDepthImplementation;
|
|||||||
{
|
{
|
||||||
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
|
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
|
||||||
|
|
||||||
ASPrimitiveTraitCollection traitCollection = self.node.primitiveTraitCollection;
|
ASPrimitiveTraitCollection traitCollection = _node.primitiveTraitCollection;
|
||||||
traitCollection.containerSize = self.view.bounds.size;
|
traitCollection.containerSize = self.view.bounds.size;
|
||||||
[self propagateNewTraitCollection:traitCollection];
|
[self propagateNewTraitCollection:traitCollection];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,12 +36,14 @@
|
|||||||
CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */; };
|
CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */; };
|
||||||
CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */; };
|
CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */; };
|
||||||
CC85250F1E36B392008EABE6 /* FeedHeaderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC85250E1E36B392008EABE6 /* FeedHeaderNode.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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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 = "<group>"; };
|
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = "<group>"; };
|
||||||
05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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; };
|
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 = "<group>"; };
|
||||||
6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; };
|
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; };
|
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 = "<group>"; };
|
76229A761CBB79E000B62CEF /* WindowWithStatusBarUnderlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowWithStatusBarUnderlay.h; sourceTree = "<group>"; };
|
||||||
@ -97,6 +99,8 @@
|
|||||||
CC85250D1E36B392008EABE6 /* FeedHeaderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedHeaderNode.h; sourceTree = "<group>"; };
|
CC85250D1E36B392008EABE6 /* FeedHeaderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedHeaderNode.h; sourceTree = "<group>"; };
|
||||||
CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedHeaderNode.m; sourceTree = "<group>"; };
|
CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedHeaderNode.m; sourceTree = "<group>"; };
|
||||||
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 = "<group>"; };
|
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 = "<group>"; };
|
||||||
|
E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedBaseController.h; sourceTree = "<group>"; };
|
||||||
|
E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedBaseController.m; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -183,6 +187,9 @@
|
|||||||
767A5F141CAA3D8A004CDA8D /* Controller */ = {
|
767A5F141CAA3D8A004CDA8D /* Controller */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
69CE83D91E515036004AA230 /* PhotoFeedControllerProtocol.h */,
|
||||||
|
E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */,
|
||||||
|
E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */,
|
||||||
767A5F161CAA3D96004CDA8D /* UIKit */,
|
767A5F161CAA3D96004CDA8D /* UIKit */,
|
||||||
767A5F151CAA3D90004CDA8D /* ASDK */,
|
767A5F151CAA3D90004CDA8D /* ASDK */,
|
||||||
CC00D1581E159132004E5502 /* ASDK-ListKit */,
|
CC00D1581E159132004E5502 /* ASDK-ListKit */,
|
||||||
@ -380,7 +387,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
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;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = {
|
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = {
|
||||||
@ -412,6 +419,7 @@
|
|||||||
768843821CAA37EF00D8629E /* CommentModel.m in Sources */,
|
768843821CAA37EF00D8629E /* CommentModel.m in Sources */,
|
||||||
768843831CAA37EF00D8629E /* CommentsNode.m in Sources */,
|
768843831CAA37EF00D8629E /* CommentsNode.m in Sources */,
|
||||||
768843961CAA37EF00D8629E /* Utilities.m in Sources */,
|
768843961CAA37EF00D8629E /* Utilities.m in Sources */,
|
||||||
|
E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */,
|
||||||
768843931CAA37EF00D8629E /* UserModel.m in Sources */,
|
768843931CAA37EF00D8629E /* UserModel.m in Sources */,
|
||||||
CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */,
|
CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */,
|
||||||
768843801CAA37EF00D8629E /* AppDelegate.m in Sources */,
|
768843801CAA37EF00D8629E /* AppDelegate.m in Sources */,
|
||||||
|
|||||||
@ -17,10 +17,6 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
//
|
//
|
||||||
|
|
||||||
@protocol PhotoFeedControllerProtocol <NSObject>
|
|
||||||
- (void)resetAllData;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
39
examples/ASDKgram/Sample/PhotoFeedBaseController.h
Normal file
39
examples/ASDKgram/Sample/PhotoFeedBaseController.h
Normal file
@ -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 <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||||
|
#import "PhotoFeedControllerProtocol.h"
|
||||||
|
|
||||||
|
@protocol PhotoFeedControllerProtocol;
|
||||||
|
@class PhotoFeedModel;
|
||||||
|
|
||||||
|
@interface PhotoFeedBaseController : ASViewController <PhotoFeedControllerProtocol>
|
||||||
|
|
||||||
|
@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
|
||||||
123
examples/ASDKgram/Sample/PhotoFeedBaseController.m
Normal file
123
examples/ASDKgram/Sample/PhotoFeedBaseController.m
Normal file
@ -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
|
||||||
13
examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h
Normal file
13
examples/ASDKgram/Sample/PhotoFeedControllerProtocol.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// PhotoFeedControllerProtocol.h
|
||||||
|
// Sample
|
||||||
|
//
|
||||||
|
// Created by Michael Schneider on 2/12/17.
|
||||||
|
// Copyright © 2017 Facebook. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@protocol PhotoFeedControllerProtocol <NSObject>
|
||||||
|
- (void)resetAllData;
|
||||||
|
@end
|
||||||
@ -7,7 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
||||||
#import "AppDelegate.h"
|
#import "PhotoFeedControllerProtocol.h"
|
||||||
|
|
||||||
@interface PhotoFeedListKitViewController<ASCollectionNode> : ASViewController <PhotoFeedControllerProtocol>
|
@interface PhotoFeedListKitViewController<ASCollectionNode> : ASViewController <PhotoFeedControllerProtocol>
|
||||||
|
|
||||||
|
|||||||
@ -17,9 +17,8 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
#import "PhotoFeedBaseController.h"
|
||||||
#import "AppDelegate.h"
|
|
||||||
|
|
||||||
@interface PhotoFeedNodeController : ASViewController <PhotoFeedControllerProtocol>
|
@interface PhotoFeedNodeController : PhotoFeedBaseController
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -31,9 +31,7 @@
|
|||||||
|
|
||||||
@implementation PhotoFeedNodeController
|
@implementation PhotoFeedNodeController
|
||||||
{
|
{
|
||||||
PhotoFeedModel *_photoFeed;
|
ASTableNode *_tableNode;
|
||||||
ASTableNode *_tableNode;
|
|
||||||
UIActivityIndicatorView *_activityIndicatorView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Lifecycle
|
#pragma mark - Lifecycle
|
||||||
@ -51,7 +49,6 @@
|
|||||||
|
|
||||||
_tableNode.dataSource = self;
|
_tableNode.dataSource = self;
|
||||||
_tableNode.delegate = self;
|
_tableNode.delegate = self;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
@ -63,112 +60,48 @@
|
|||||||
{
|
{
|
||||||
[super 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.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
|
_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
|
- (void)loadPageWithContext:(ASBatchContext *)context
|
||||||
{
|
{
|
||||||
[_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){
|
[self.photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){
|
||||||
|
|
||||||
[self insertNewRowsInTableNode:newPhotos];
|
[self insertNewRows:newPhotos];
|
||||||
// [self requestCommentsForPhotos:newPhotos];
|
[self requestCommentsForPhotos:newPhotos];
|
||||||
if (context) {
|
if (context) {
|
||||||
[context completeBatchFetching:YES];
|
[context completeBatchFetching:YES];
|
||||||
}
|
}
|
||||||
} numResultsToReturn:20];
|
} numResultsToReturn:20];
|
||||||
}
|
}
|
||||||
|
|
||||||
//- (void)requestCommentsForPhotos:(NSArray *)newPhotos
|
#pragma mark - Subclassing
|
||||||
//{
|
|
||||||
// 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];
|
|
||||||
// }
|
|
||||||
// }];
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
- (void)insertNewRowsInTableNode:(NSArray *)newPhotos
|
- (UITableView *)tableView
|
||||||
{
|
{
|
||||||
NSInteger section = 0;
|
return _tableNode.view;
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UIStatusBarStyle)preferredStatusBarStyle
|
- (void)loadPage
|
||||||
{
|
{
|
||||||
return UIStatusBarStyleLightContent;
|
[self loadPageWithContext:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGSize)imageSizeForScreenWidth
|
- (void)requestCommentsForPhotos:(NSArray *)newPhotos
|
||||||
{
|
{
|
||||||
CGRect screenRect = [[UIScreen mainScreen] bounds];
|
// Do nothing (#1530).
|
||||||
CGFloat screenScale = [[UIScreen mainScreen] scale];
|
|
||||||
return CGSizeMake(screenRect.size.width * screenScale, screenRect.size.width * screenScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - ASTableDataSource methods
|
#pragma mark - ASTableDataSource methods
|
||||||
|
|
||||||
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section
|
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section
|
||||||
{
|
{
|
||||||
return [_photoFeed numberOfItemsInFeed];
|
return [self.photoFeed numberOfItemsInFeed];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
|
- (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
|
// this will be executed on a background thread - important to make sure it's thread safe
|
||||||
ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() {
|
ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() {
|
||||||
PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhotoObject:photoModel];
|
PhotoCellNode *cellNode = [[PhotoCellNode alloc] initWithPhotoObject:photoModel];
|
||||||
@ -187,13 +120,4 @@
|
|||||||
[self loadPageWithContext:context];
|
[self loadPageWithContext:context];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - PhotoFeedViewControllerProtocol
|
|
||||||
|
|
||||||
- (void)resetAllData
|
|
||||||
{
|
|
||||||
[_photoFeed clearFeed];
|
|
||||||
[_tableNode reloadData];
|
|
||||||
[self refreshFeed];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -17,8 +17,8 @@
|
|||||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "PhotoFeedBaseController.h"
|
||||||
|
|
||||||
@interface PhotoFeedViewController : UIViewController <PhotoFeedControllerProtocol>
|
@interface PhotoFeedViewController : PhotoFeedBaseController
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -30,9 +30,7 @@
|
|||||||
|
|
||||||
@implementation PhotoFeedViewController
|
@implementation PhotoFeedViewController
|
||||||
{
|
{
|
||||||
PhotoFeedModel *_photoFeed;
|
UITableView *_tableView;
|
||||||
UITableView *_tableView;
|
|
||||||
UIActivityIndicatorView *_activityIndicatorView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Lifecycle
|
#pragma mark - Lifecycle
|
||||||
@ -49,8 +47,6 @@
|
|||||||
_tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
_tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
|
||||||
_tableView.delegate = self;
|
_tableView.delegate = self;
|
||||||
_tableView.dataSource = self;
|
_tableView.dataSource = self;
|
||||||
|
|
||||||
_activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
@ -61,51 +57,22 @@
|
|||||||
{
|
{
|
||||||
[super viewDidLoad];
|
[super viewDidLoad];
|
||||||
|
|
||||||
_photoFeed = [[PhotoFeedModel alloc] initWithPhotoFeedModelType:PhotoFeedModelTypePopular imageSize:[self imageSizeForScreenWidth]];
|
|
||||||
[self refreshFeed];
|
|
||||||
|
|
||||||
CGSize boundSize = self.view.bounds.size;
|
|
||||||
|
|
||||||
[self.view addSubview:_tableView];
|
[self.view addSubview:_tableView];
|
||||||
|
|
||||||
_tableView.frame = self.view.bounds;
|
_tableView.frame = self.view.bounds;
|
||||||
_tableView.allowsSelection = NO;
|
|
||||||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
|
||||||
[_tableView registerClass:[PhotoTableViewCell class] forCellReuseIdentifier:@"photoCell"];
|
[_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];
|
return _tableView;
|
||||||
|
|
||||||
// small first batch
|
|
||||||
[_photoFeed refreshFeedWithCompletionBlock:^(NSArray *newPhotos){
|
|
||||||
|
|
||||||
[_activityIndicatorView stopAnimating];
|
|
||||||
|
|
||||||
[self insertNewRowsInTableView:newPhotos];
|
|
||||||
[self requestCommentsForPhotos:newPhotos];
|
|
||||||
|
|
||||||
// immediately start second larger fetch
|
|
||||||
[self loadPage];
|
|
||||||
|
|
||||||
} numResultsToReturn:4];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)loadPage
|
- (void)loadPage
|
||||||
{
|
{
|
||||||
[_photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){
|
[self.photoFeed requestPageWithCompletionBlock:^(NSArray *newPhotos){
|
||||||
[self insertNewRowsInTableView:newPhotos];
|
[self insertNewRows:newPhotos];
|
||||||
[self requestCommentsForPhotos:newPhotos];
|
[self requestCommentsForPhotos:newPhotos];
|
||||||
} numResultsToReturn:20];
|
} numResultsToReturn:20];
|
||||||
}
|
}
|
||||||
@ -115,7 +82,7 @@
|
|||||||
for (PhotoModel *photo in newPhotos) {
|
for (PhotoModel *photo in newPhotos) {
|
||||||
[photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) {
|
[photo.commentFeed refreshFeedWithCompletionBlock:^(NSArray *newComments) {
|
||||||
|
|
||||||
NSInteger rowNum = [_photoFeed indexOfPhotoModel:photo];
|
NSInteger rowNum = [self.photoFeed indexOfPhotoModel:photo];
|
||||||
NSIndexPath *cellPath = [NSIndexPath indexPathForRow:rowNum inSection:0];
|
NSIndexPath *cellPath = [NSIndexPath indexPathForRow:rowNum inSection:0];
|
||||||
PhotoTableViewCell *cell = [_tableView cellForRowAtIndexPath:cellPath];
|
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
|
#pragma mark - UITableViewDataSource methods
|
||||||
|
|
||||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||||
{
|
{
|
||||||
return [_photoFeed numberOfItemsInFeed];
|
return [self.photoFeed numberOfItemsInFeed];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||||
{
|
{
|
||||||
PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"photoCell" forIndexPath:indexPath];
|
PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"photoCell" forIndexPath:indexPath];
|
||||||
[cell updateCellWithPhotoObject:[_photoFeed objectAtIndex:indexPath.row]];
|
[cell updateCellWithPhotoObject:[self.photoFeed objectAtIndex:indexPath.row]];
|
||||||
|
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
|
- (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];
|
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
|
@end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user