Skip to content

Commit aecd36a

Browse files
authored
[ASViewController] Support optional node (facebookarchive#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
1 parent cd448a1 commit aecd36a

12 files changed

+266
-207
lines changed

AsyncDisplayKit/ASViewController.h

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,34 @@ NS_ASSUME_NONNULL_BEGIN
2121
typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitCollectionBlock)(UITraitCollection *traitCollection);
2222
typedef ASTraitCollection * _Nonnull (^ASDisplayTraitsForTraitWindowSizeBlock)(CGSize windowSize);
2323

24+
/**
25+
* ASViewController allows you to have a completely node backed hierarchy. It automatically
26+
* handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes.
27+
*
28+
* You can opt-out of node backed hierarchy and use it like a normal UIViewController.
29+
* 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.
30+
* See examples/ASDKgram project for actual implementation.
31+
*/
2432
@interface ASViewController<__covariant DisplayNodeType : ASDisplayNode *> : UIViewController <ASVisibilityDepth>
2533

2634
/**
27-
* ASViewController Designated initializer.
28-
*
29-
* @discussion ASViewController allows you to have a completely node backed heirarchy. It automatically
30-
* handles @c ASVisibilityDepth, automatic range mode and propogating @c ASDisplayTraits to contained nodes.
35+
* ASViewController initializer.
3136
*
3237
* @param node An ASDisplayNode which will provide the root view (self.view)
3338
* @return An ASViewController instance whose root view will be backed by the provided ASDisplayNode.
3439
*
3540
* @see ASVisibilityDepth
3641
*/
37-
- (instancetype)initWithNode:(DisplayNodeType)node NS_DESIGNATED_INITIALIZER;
42+
- (instancetype)initWithNode:(DisplayNodeType)node;
43+
44+
NS_ASSUME_NONNULL_END
3845

3946
/**
4047
* @return node Returns the ASDisplayNode which provides the backing view to the view controller.
4148
*/
42-
@property (nonatomic, strong, readonly) DisplayNodeType node;
49+
@property (nonatomic, strong, readonly, null_unspecified) DisplayNodeType node;
50+
51+
NS_ASSUME_NONNULL_BEGIN
4352

4453
/**
4554
* 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
91100

92101
@end
93102

94-
@interface ASViewController (Unavailable)
95-
96-
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil AS_UNAVAILABLE("ASViewController requires using -initWithNode:");
97-
98-
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder AS_UNAVAILABLE("ASViewController requires using -initWithNode:");
99-
100-
@end
101-
102103
NS_ASSUME_NONNULL_END

AsyncDisplayKit/ASViewController.mm

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,24 @@ @implementation ASViewController
3333

3434
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
3535
{
36-
ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:");
37-
return [self initWithNode:[[ASDisplayNode alloc] init]];
36+
if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
37+
return nil;
38+
}
39+
40+
[self _initializeInstance];
41+
42+
return self;
3843
}
3944

4045
- (instancetype)initWithCoder:(NSCoder *)aDecoder
4146
{
42-
ASDisplayNodeAssert(NO, @"ASViewController requires using -initWithNode:");
43-
return [self initWithNode:[[ASDisplayNode alloc] init]];
47+
if (!(self = [super initWithCoder:aDecoder])) {
48+
return nil;
49+
}
50+
51+
[self _initializeInstance];
52+
53+
return self;
4454
}
4555

4656
- (instancetype)initWithNode:(ASDisplayNode *)node
@@ -49,10 +59,18 @@ - (instancetype)initWithNode:(ASDisplayNode *)node
4959
return nil;
5060
}
5161

52-
ASDisplayNodeAssertNotNil(node, @"Node must not be nil");
53-
ASDisplayNodeAssertTrue(!node.layerBacked);
5462
_node = node;
63+
[self _initializeInstance];
5564

65+
return self;
66+
}
67+
68+
- (void)_initializeInstance
69+
{
70+
if (_node == nil) {
71+
return;
72+
}
73+
5674
_selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)];
5775
_nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)];
5876
_automaticallyAdjustRangeModeBasedOnViewEvents = _selfConformsToRangeModeProtocol || _nodeConformsToRangeModeProtocol;
@@ -62,7 +80,7 @@ - (instancetype)initWithNode:(ASDisplayNode *)node
6280
// Node already loaded the view
6381
[self view];
6482
} else {
65-
// If the node didn't load yet add ourselves as on did load observer to laod the view in case the node gets loaded
83+
// If the node didn't load yet add ourselves as on did load observer to load the view in case the node gets loaded
6684
// before the view controller
6785
__weak __typeof__(self) weakSelf = self;
6886
[_node onDidLoad:^(__kindof ASDisplayNode * _Nonnull node) {
@@ -71,8 +89,6 @@ - (instancetype)initWithNode:(ASDisplayNode *)node
7189
}
7290
}];
7391
}
74-
75-
return self;
7692
}
7793

7894
- (void)dealloc
@@ -82,13 +98,18 @@ - (void)dealloc
8298

8399
- (void)loadView
84100
{
85-
ASDisplayNodeAssertTrue(!_node.layerBacked);
86-
87101
// Apple applies a frame and autoresizing masks we need. Allocating a view is not
88102
// nearly as expensive as adding and removing it from a hierarchy, and fortunately
89103
// we can avoid that here. Enabling layerBacking on a single node in the hierarchy
90104
// will have a greater performance benefit than the impact of this transient view.
91105
[super loadView];
106+
107+
if (_node == nil) {
108+
return;
109+
}
110+
111+
ASDisplayNodeAssertTrue(!_node.layerBacked);
112+
92113
UIView *view = self.view;
93114
CGRect frame = view.frame;
94115
UIViewAutoresizing autoresizingMask = view.autoresizingMask;
@@ -135,7 +156,7 @@ - (void)viewDidLayoutSubviews
135156
{
136157
if (_ensureDisplayed && self.neverShowPlaceholders) {
137158
_ensureDisplayed = NO;
138-
[self.node recursivelyEnsureDisplaySynchronously:YES];
159+
[_node recursivelyEnsureDisplaySynchronously:YES];
139160
}
140161
[super viewDidLayoutSubviews];
141162
}
@@ -212,7 +233,9 @@ - (void)setAutomaticallyAdjustRangeModeBasedOnViewEvents:(BOOL)automaticallyAdju
212233

213234
- (void)updateCurrentRangeModeWithModeIfPossible:(ASLayoutRangeMode)rangeMode
214235
{
215-
if (!_automaticallyAdjustRangeModeBasedOnViewEvents) { return; }
236+
if (!_automaticallyAdjustRangeModeBasedOnViewEvents) {
237+
return;
238+
}
216239

217240
if (_selfConformsToRangeModeProtocol) {
218241
id<ASRangeControllerUpdateRangeProtocol> rangeUpdater = (id<ASRangeControllerUpdateRangeProtocol>)self;
@@ -268,7 +291,7 @@ - (void)propagateNewTraitCollection:(ASPrimitiveTraitCollection)traitCollection
268291
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
269292
// Once we've propagated all the traits, layout this node.
270293
// Remeasure the node with the latest constrained size – old constrained size may be incorrect.
271-
[self.node layoutThatFits:[self nodeConstrainedSize]];
294+
[_node layoutThatFits:[self nodeConstrainedSize]];
272295
#pragma clang diagnostic pop
273296
}
274297
}
@@ -286,7 +309,7 @@ - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceO
286309
{
287310
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
288311

289-
ASPrimitiveTraitCollection traitCollection = self.node.primitiveTraitCollection;
312+
ASPrimitiveTraitCollection traitCollection = _node.primitiveTraitCollection;
290313
traitCollection.containerSize = self.view.bounds.size;
291314
[self propagateNewTraitCollection:traitCollection];
292315
}

examples/ASDKgram/Sample.xcodeproj/project.pbxproj

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@
3636
CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */; };
3737
CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */; };
3838
CC85250F1E36B392008EABE6 /* FeedHeaderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */; };
39+
E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */; };
3940
/* End PBXBuildFile section */
4041

4142
/* Begin PBXFileReference section */
4243
0585427F19D4DBE100606EA6 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "[email protected]"; path = "../[email protected]"; sourceTree = "<group>"; };
4344
05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
4445
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
46+
69CE83D91E515036004AA230 /* PhotoFeedControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedControllerProtocol.h; sourceTree = "<group>"; };
4547
6C2C82AA19EE274300767484 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = SOURCE_ROOT; };
4648
6C2C82AB19EE274300767484 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = SOURCE_ROOT; };
4749
76229A761CBB79E000B62CEF /* WindowWithStatusBarUnderlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowWithStatusBarUnderlay.h; sourceTree = "<group>"; };
@@ -97,6 +99,8 @@
9799
CC85250D1E36B392008EABE6 /* FeedHeaderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedHeaderNode.h; sourceTree = "<group>"; };
98100
CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedHeaderNode.m; sourceTree = "<group>"; };
99101
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>"; };
102+
E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedBaseController.h; sourceTree = "<group>"; };
103+
E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedBaseController.m; sourceTree = "<group>"; };
100104
/* End PBXFileReference section */
101105

102106
/* Begin PBXFrameworksBuildPhase section */
@@ -183,6 +187,9 @@
183187
767A5F141CAA3D8A004CDA8D /* Controller */ = {
184188
isa = PBXGroup;
185189
children = (
190+
69CE83D91E515036004AA230 /* PhotoFeedControllerProtocol.h */,
191+
E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */,
192+
E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */,
186193
767A5F161CAA3D96004CDA8D /* UIKit */,
187194
767A5F151CAA3D90004CDA8D /* ASDK */,
188195
CC00D1581E159132004E5502 /* ASDK-ListKit */,
@@ -380,7 +387,7 @@
380387
);
381388
runOnlyForDeploymentPostprocessing = 0;
382389
shellPath = /bin/sh;
383-
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";
390+
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";
384391
showEnvVarsInLog = 0;
385392
};
386393
F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = {
@@ -412,6 +419,7 @@
412419
768843821CAA37EF00D8629E /* CommentModel.m in Sources */,
413420
768843831CAA37EF00D8629E /* CommentsNode.m in Sources */,
414421
768843961CAA37EF00D8629E /* Utilities.m in Sources */,
422+
E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */,
415423
768843931CAA37EF00D8629E /* UserModel.m in Sources */,
416424
CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */,
417425
768843801CAA37EF00D8629E /* AppDelegate.m in Sources */,

examples/ASDKgram/Sample/AppDelegate.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@
1717
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1818
//
1919

20-
@protocol PhotoFeedControllerProtocol <NSObject>
21-
- (void)resetAllData;
22-
@end
23-
2420
@interface AppDelegate : UIResponder <UIApplicationDelegate>
2521

2622
@end
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// PhotoFeedBaseController.h
3+
// Sample
4+
//
5+
// Created by Huy Nguyen on 20/12/16.
6+
//
7+
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
8+
// This source code is licensed under the BSD-style license found in the
9+
// LICENSE file in the root directory of this source tree. An additional grant
10+
// of patent rights can be found in the PATENTS file in the same directory.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
15+
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
16+
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
17+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18+
//
19+
20+
#import <AsyncDisplayKit/AsyncDisplayKit.h>
21+
#import "PhotoFeedControllerProtocol.h"
22+
23+
@protocol PhotoFeedControllerProtocol;
24+
@class PhotoFeedModel;
25+
26+
@interface PhotoFeedBaseController : ASViewController <PhotoFeedControllerProtocol>
27+
28+
@property (nonatomic, strong, readonly) PhotoFeedModel *photoFeed;
29+
@property (nonatomic, strong, readonly) UITableView *tableView;
30+
31+
- (void)refreshFeed;
32+
- (void)insertNewRows:(NSArray *)newPhotos;
33+
34+
#pragma mark - Subclasses must override these methods
35+
36+
- (void)loadPage;
37+
- (void)requestCommentsForPhotos:(NSArray *)newPhotos;
38+
39+
@end

0 commit comments

Comments
 (0)