Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions AsyncDisplayKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,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 */; };
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, ); }; };
ACF6ED1B1B17843500DA7C62 /* ASBackgroundLayoutSpec.mm in Sources */ = {isa = PBXBuildFile; fileRef = ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */; };
ACF6ED1C1B17843500DA7C62 /* ASCenterLayoutSpec.h in Headers */ = {isa = PBXBuildFile; fileRef = ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -574,6 +576,8 @@
AC47D9431B3BB41900AAEE9D /* ASRelativeSize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASRelativeSize.h; path = AsyncDisplayKit/Layout/ASRelativeSize.h; sourceTree = "<group>"; };
AC47D9441B3BB41900AAEE9D /* ASRelativeSize.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASRelativeSize.mm; path = AsyncDisplayKit/Layout/ASRelativeSize.mm; sourceTree = "<group>"; };
AC6456071B0A335000CF11B8 /* ASCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCellNode.m; sourceTree = "<group>"; };
ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASViewController.h; sourceTree = "<group>"; };
ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASViewController.m; sourceTree = "<group>"; };
ACF6ED011B17843500DA7C62 /* ASBackgroundLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBackgroundLayoutSpec.h; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.h; sourceTree = "<group>"; };
ACF6ED021B17843500DA7C62 /* ASBackgroundLayoutSpec.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; name = ASBackgroundLayoutSpec.mm; path = AsyncDisplayKit/Layout/ASBackgroundLayoutSpec.mm; sourceTree = "<group>"; };
ACF6ED031B17843500DA7C62 /* ASCenterLayoutSpec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCenterLayoutSpec.h; path = AsyncDisplayKit/Layout/ASCenterLayoutSpec.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -758,6 +762,8 @@
0574D5E119C110610097DC25 /* ASTableViewProtocols.h */,
058D09DF195D050800B7D73C /* ASTextNode.h */,
058D09E0195D050800B7D73C /* ASTextNode.mm */,
ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */,
ACC945AA1BA9E7C1005E1FB8 /* ASViewController.m */,
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */,
058D09E1195D050800B7D73C /* Details */,
058D0A01195D050800B7D73C /* Private */,
Expand Down Expand Up @@ -1114,6 +1120,7 @@
058D0A63195D05DC00B7D73C /* ASTextNodeTypes.h in Headers */,
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */,
058D0A81195D05F900B7D73C /* ASThread.h in Headers */,
ACC945A91BA9E7A0005E1FB8 /* ASViewController.h in Headers */,
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */,
205F0E211B376416007741D0 /* CGRect+ASConvenience.h in Headers */,
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
Expand Down Expand Up @@ -1478,6 +1485,7 @@
058D0A1E195D050800B7D73C /* ASTextNodeShadower.m in Sources */,
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
058D0A20195D050800B7D73C /* ASTextNodeWordKerner.m in Sources */,
ACC945AB1BA9E7C1005E1FB8 /* ASViewController.m in Sources */,
205F0E221B376416007741D0 /* CGRect+ASConvenience.m in Sources */,
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
205F0E101B371875007741D0 /* UICollectionViewLayout+ASConvenience.m in Sources */,
Expand Down
19 changes: 19 additions & 0 deletions AsyncDisplayKit/ASViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ASViewController.h
// AsyncDisplayKit
//
// Created by Huy Nguyen on 16/09/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASDisplayNode.h>

@interface ASViewController : UIViewController

@property (nonatomic, strong, readonly) ASDisplayNode *node;

//TODO Use nonnull annotation late on. Travis doesn't recognize it (yet).
- (instancetype)initWithNode:(ASDisplayNode *)node;

@end
48 changes: 48 additions & 0 deletions AsyncDisplayKit/ASViewController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// ASViewController.m
// AsyncDisplayKit
//
// Created by Huy Nguyen on 16/09/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import "ASViewController.h"
#import "ASAssert.h"
#import "ASDimension.h"

@implementation ASViewController

- (instancetype)initWithNode:(ASDisplayNode *)node
{
if (!(self = [super init])) {
return nil;
}

ASDisplayNodeAssertNotNil(node, @"Node must not be nil");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can now modify your method signature + parameters to indicate that node must not be nil by doing the following:
- (instancetype)initWithNode:(ASDisplayNode *_Nonnull)node ( XCode 7+)
- (instancetype)initWithNode:(ASDisplayNode * nonnull)node (Xcode 6.4)

https://developer.apple.com/swift/blog/?id=25

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great. Thanks!

P/S: actually nonnull must be right after the open parenthesis.
And I'm wondering why it is nonnull instead of nonnil?

ASDisplayNodeAssertTrue(!node.layerBacked);
_node = node;

return self;
}

- (void)loadView
{
ASDisplayNodeAssertTrue(!_node.layerBacked);
self.view = _node.view;
}

- (void)viewWillLayoutSubviews
{
CGSize viewSize = self.view.bounds.size;
ASSizeRange constrainedSize = ASSizeRangeMake(viewSize, viewSize);
[_node measureWithSizeRange:constrainedSize];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I think this effectively does synchronous measurement here and means that just barely in time for -layout, there will be cached sizes.

What would happen if measureWithSizeRange were called on a background thread (on the node) and were still running at this point? Interestingly there haven't been other cases in usage (either framework or app) where I have needed to consider that.

Lastly, small nit, but always create a local variable for a structure that needs to be accessed twice. CGSize boundsSize = self.view.bounds.size;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is synchronous measurement. If it is instead asynchronous and still running at this point, the (backing) view will have the correct size (set previously) but look empty, because the node doesn't layout its subnodes. Once layout is done, we can call setNeedsLayout and subviews will appear correctly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right — it's totally fine for this to be synchronous. Later, we can add the API to allow a user to trigger preparation of the view in advance, which would include layout. My question is - what if a user dispatch_async'd a call to measure:, and then pushed the view controller?

The answer may be that we get bad behavior. That's also not a big deal right now, as it would be unusual to manually call measure: in that way and then push the view — but it will be a key part of the final version of 2.0 to support kicking off asynchronous layout, and then having a call that is able to block on the remainder of it (e.g. "-ensureMeasurementComplete" or something — that is probably not a good name). It would return immediately if done, otherwise wait on the background operation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for misunderstood your question. That's indeed a tricky question. Yeah I think the behaviour would be undefined, at least for the current implementation. My main reason is that the async layout pass can finished right before or after any of these events: VC is pushed, loadView, viewWillLayoutSubviews, etc. If we block on the remainder in, say, viewDidAppear, we may be able to do final set up there in case the background operation missed these events.

(There is a high chance that I'm talking non-sense here, please take it with a grain of salt haha)

[super viewWillLayoutSubviews];
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[_node recursivelyFetchData];
}

@end
2 changes: 2 additions & 0 deletions AsyncDisplayKit/AsyncDisplayKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

#import <AsyncDisplayKit/ASScrollNode.h>

#import <AsyncDisplayKit/ASViewController.h>

#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASLayoutable.h>
Expand Down
6 changes: 6 additions & 0 deletions examples/Multiplex/Sample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; };
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; };
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; };
ACC945AE1BA9EFBA005E1FB8 /* ScreenNode.m in Sources */ = {isa = PBXBuildFile; fileRef = ACC945AD1BA9EFBA005E1FB8 /* ScreenNode.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -29,6 +30,8 @@
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
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; };
ACC945AC1BA9EFB3005E1FB8 /* ScreenNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScreenNode.h; sourceTree = "<group>"; };
ACC945AD1BA9EFBA005E1FB8 /* ScreenNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ScreenNode.m; sourceTree = "<group>"; };
C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -72,6 +75,8 @@
05E2128919D4DB510098F589 /* AppDelegate.m */,
05E2128B19D4DB510098F589 /* ViewController.h */,
05E2128C19D4DB510098F589 /* ViewController.m */,
ACC945AC1BA9EFB3005E1FB8 /* ScreenNode.h */,
ACC945AD1BA9EFBA005E1FB8 /* ScreenNode.m */,
05E2128419D4DB510098F589 /* Supporting Files */,
);
path = Sample;
Expand Down Expand Up @@ -214,6 +219,7 @@
05E2128D19D4DB510098F589 /* ViewController.m in Sources */,
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */,
05E2128719D4DB510098F589 /* main.m in Sources */,
ACC945AE1BA9EFBA005E1FB8 /* ScreenNode.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
19 changes: 19 additions & 0 deletions examples/Multiplex/Sample/ScreenNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// ScreenNode.h
// Sample
//
// Created by Huy Nguyen on 16/09/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import <AsyncDisplayKit/AsyncDisplayKit.h>

@interface ScreenNode : ASDisplayNode

@property (nonatomic, strong) ASMultiplexImageNode *imageNode;
@property (nonatomic, strong) ASTextNode *textNode;

- (void)start;
- (void)reload;

@end
152 changes: 152 additions & 0 deletions examples/Multiplex/Sample/ScreenNode.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//
// ScreenNode.m
// Sample
//
// Created by Huy Nguyen on 16/09/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//

#import "ScreenNode.h"

@interface ScreenNode() <ASMultiplexImageNodeDataSource, ASMultiplexImageNodeDelegate, ASImageDownloaderProtocol>
@end

@implementation ScreenNode

- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}

// multiplex image node!
// NB: we're using a custom downloader with an artificial delay for this demo, but ASBasicImageDownloader works too!
_imageNode = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:self];
_imageNode.dataSource = self;
_imageNode.delegate = self;

// placeholder colour
_imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor();

// load low-quality images before high-quality images
_imageNode.downloadsIntermediateImages = YES;

// simple status label
_textNode = [[ASTextNode alloc] init];

[self addSubnode:_imageNode];
[self addSubnode:_textNode];

return self;
}

- (void)start
{
[self setText:@"loading…"];
_textNode.userInteractionEnabled = NO;
_imageNode.imageIdentifiers = @[ @"best", @"medium", @"worst" ]; // go!
}

- (void)reload {
[self start];
[_imageNode reloadImageIdentifierSources];
}

- (void)setText:(NSString *)text
{
NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:text
attributes:attributes];
_textNode.attributedString = string;
[self setNeedsLayout];
}

- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASRatioLayoutSpec *imagePlaceholder = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1 child:_imageNode];
ASStackLayoutSpec *verticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:10
justifyContent:ASStackLayoutJustifyContentCenter
alignItems:ASStackLayoutAlignItemsCenter
children:@[imagePlaceholder, _textNode]];
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:verticalStack];
}

#pragma mark -
#pragma mark ASMultiplexImageNode data source & delegate.

- (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(id)imageIdentifier
{
if ([imageIdentifier isEqualToString:@"worst"]) {
return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/worst.png"];
}

if ([imageIdentifier isEqualToString:@"medium"]) {
return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/medium.png"];
}

if ([imageIdentifier isEqualToString:@"best"]) {
return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples/Multiplex/best.png"];
}

// unexpected identifier
return nil;
}

- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didFinishDownloadingImageWithIdentifier:(id)imageIdentifier error:(NSError *)error
{
[self setText:[NSString stringWithFormat:@"loaded '%@'", imageIdentifier]];

if ([imageIdentifier isEqualToString:@"best"]) {
[self setText:[_textNode.attributedString.string stringByAppendingString:@". tap to reload"]];
_textNode.userInteractionEnabled = YES;
}
}


#pragma mark -
#pragma mark ASImageDownloaderProtocol.

- (id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^)(CGFloat progress))downloadProgressBlock
completion:(void (^)(CGImageRef image, NSError *error))completion
{
// if no callback queue is supplied, run on the main thread
if (callbackQueue == nil) {
callbackQueue = dispatch_get_main_queue();
}

// call completion blocks
void (^handler)(NSURLResponse *, NSData *, NSError *) = ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// add an artificial delay
usleep(1.0 * USEC_PER_SEC);

// ASMultiplexImageNode callbacks
dispatch_async(callbackQueue, ^{
if (downloadProgressBlock) {
downloadProgressBlock(1.0f);
}

if (completion) {
completion([[UIImage imageWithData:data] CGImage], connectionError);
}
});
};

// let NSURLConnection do the heavy lifting
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:handler];

// return nil, don't support cancellation
return nil;
}

- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
{
// no-op, don't support cancellation
}

@end
4 changes: 2 additions & 2 deletions examples/Multiplex/Sample/ViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASViewController.h>

@interface ViewController : UIViewController
@interface ViewController : ASViewController

@end
Loading