Merge pull request #143 from facebook/placeholder-api

Node placeholder API
This commit is contained in:
Nadine Salter 2014-12-15 17:31:11 -08:00
commit bf01b07443
33 changed files with 1140 additions and 2 deletions

View File

@ -185,6 +185,24 @@
*/
- (void)displayDidFinish ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Indicates that the receiver is about to display its subnodes. This method is not called if there are no
* subnodes present.
*
* @discussion Subclasses may override this method to be notified when subnode display (asynchronous or synchronous) is
* about to begin.
*/
- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Indicates that the receiver is finished displaying its subnodes. This method is not called if there are
* no subnodes present.
*
* @discussion Subclasses may override this method to be notified when subnode display (asynchronous or synchronous) is
* about to begin.
*/
- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode ASDISPLAYNODE_REQUIRES_SUPER;
/**
* @abstract Marks the receiver's bounds as needing to be redrawn, with a scale value.
@ -303,6 +321,26 @@
- (void)reclaimMemory ASDISPLAYNODE_REQUIRES_SUPER;
/** @name Placeholders */
/**
* @abstract Optionally provide an image to serve as the placeholder for the backing store while the contents are being
* displayed.
*
* @discussion
* Subclasses may override this method and return an image to use as the placeholder. Take caution as there may be a
* time and place where this method is called on a background thread. Note that -[UIImage imageNamed:] is not thread
* safe when using image assets.
*
* To retrieve the CGSize to do any image drawing, use the node's calculatedSize property.
*
* Defaults to nil.
*
* @note Called on the display queue and/or main queue (MUST BE THREAD SAFE)
*/
- (UIImage *)placeholderImage;
/** @name Description */

View File

@ -330,6 +330,14 @@
- (void)recursivelyReclaimMemory;
/**
* @abstract Toggle displaying a placeholder over the node that covers content until the node and all subnodes are
* displayed.
*
* @discussion Defaults to NO.
*/
@property (nonatomic, assign) BOOL placeholderEnabled;
/** @name Hit Testing */

View File

@ -184,6 +184,8 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
_replaceAsyncSentinel = nil;
_displaySentinel = nil;
_pendingDisplayNodes = nil;
}
#pragma mark - UIResponder overrides
@ -270,6 +272,10 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
TIME_SCOPED(_debugTimeForDidLoad);
[self didLoad];
}
if (self.placeholderEnabled) {
[self _setupPlaceholderLayer];
}
}
- (UIView *)view
@ -349,6 +355,7 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
- (CGSize)measure:(CGSize)constrainedSize
{
ASDisplayNodeAssertThreadAffinity(self);
ASDN::MutexLocker l(_propertyLock);
if (![self __shouldSize])
return CGSizeZero;
@ -365,6 +372,18 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
ASDisplayNodeAssertTrue(_size.width >= 0.0);
ASDisplayNodeAssertTrue(_size.height >= 0.0);
// we generate placeholders at measure: time so that a node is guaranteed to have a placeholder ready to go
if (self.placeholderEnabled && [self displaysAsynchronously]) {
if (!_placeholderImage) {
_placeholderImage = [self placeholderImage];
}
if (_placeholderLayer) {
_placeholderLayer.contents = (id)_placeholderImage.CGImage;
}
}
return _size;
}
@ -448,8 +467,10 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
{
ASDisplayNodeAssertMainThread();
ASDN::MutexLocker l(_propertyLock);
if (CGRectEqualToRect(_layer.bounds, CGRectZero))
if (CGRectEqualToRect(_layer.bounds, CGRectZero)) {
return; // Performing layout on a zero-bounds view often results in frame calculations with negative sizes after applying margins, which will cause measure: on subnodes to assert.
}
_placeholderLayer.frame = _layer.bounds;
[self layout];
[self layoutDidFinish];
}
@ -1091,6 +1112,60 @@ static NSInteger incrementIfFound(NSInteger i) {
_supernode = supernode;
}
// Track that a node will be displayed as part of the current node hierarchy.
// The node sending the message should usually be passed as the parameter, similar to the delegation pattern.
- (void)_pendingNodeWillDisplay:(ASDisplayNode *)node
{
ASDN::MutexLocker l(_propertyLock);
[_pendingDisplayNodes addObject:node];
}
// Notify that a node that was pending display finished
// The node sending the message should usually be passed as the parameter, similar to the delegation pattern.
- (void)_pendingNodeDidDisplay:(ASDisplayNode *)node
{
ASDN::MutexLocker l(_propertyLock);
[_pendingDisplayNodes removeObject:node];
// only trampoline if there is a placeholder and nodes are done displaying
if ([self _pendingDisplayNodesHaveFinished] && _placeholderLayer.superlayer) {
dispatch_async(dispatch_get_main_queue(), ^{
[self _tearDownPlaceholderLayer];
});
}
}
// Helper method to check that all nodes that the current node is waiting to display are finished
// Use this method to check to remove any placeholder layers
- (BOOL)_pendingDisplayNodesHaveFinished
{
return _pendingDisplayNodes.count == 0;
}
// Helper method to summarize whether or not the node run through the display process
- (BOOL)_implementsDisplay
{
return _flags.implementsDrawRect == YES || _flags.implementsImageDisplay == YES;
}
- (void)_setupPlaceholderLayer
{
ASDisplayNodeAssertMainThread();
_placeholderLayer = [CALayer layer];
// do not set to CGFLOAT_MAX in the case that something needs to be overtop the placeholder
_placeholderLayer.zPosition = 9999.0;
}
- (void)_tearDownPlaceholderLayer
{
ASDisplayNodeAssertMainThread();
[_placeholderLayer removeFromSuperlayer];
}
#pragma mark - For Subclasses
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
@ -1111,6 +1186,11 @@ static NSInteger incrementIfFound(NSInteger i) {
return _constrainedSize;
}
- (UIImage *)placeholderImage
{
return nil;
}
- (void)invalidateCalculatedSize
{
ASDisplayNodeAssertThreadAffinity(self);
@ -1142,6 +1222,7 @@ static NSInteger incrementIfFound(NSInteger i) {
- (void)reclaimMemory
{
self.layer.contents = nil;
_placeholderLayer.contents = nil;
}
- (void)recursivelyReclaimMemory
@ -1159,10 +1240,36 @@ static NSInteger incrementIfFound(NSInteger i) {
- (void)displayWillStart
{
// in case current node takes longer to display than it's subnodes, treat it as a dependent node
[self _pendingNodeWillDisplay:self];
[_supernode subnodeDisplayWillStart:self];
if (_placeholderImage && _placeholderLayer) {
_placeholderLayer.contents = (id)_placeholderImage.CGImage;
[self.layer addSublayer:_placeholderLayer];
}
}
- (void)displayDidFinish
{
[self _pendingNodeDidDisplay:self];
[_supernode subnodeDisplayDidFinish:self];
if (_placeholderLayer && [self _pendingDisplayNodesHaveFinished]) {
[self _tearDownPlaceholderLayer];
}
}
- (void)subnodeDisplayWillStart:(ASDisplayNode *)subnode
{
[self _pendingNodeWillDisplay:subnode];
}
- (void)subnodeDisplayDidFinish:(ASDisplayNode *)subnode
{
[self _pendingNodeDidDisplay:subnode];
}
- (void)setNeedsDisplayAtScale:(CGFloat)contentsScale
@ -1394,6 +1501,14 @@ static void _recursivelySetDisplaySuspended(ASDisplayNode *node, CALayer *layer,
_flags.displaySuspended = flag;
self.asyncLayer.displaySuspended = flag;
if ([self _implementsDisplay]) {
if (flag) {
[_supernode subnodeDisplayDidFinish:self];
} else {
[_supernode subnodeDisplayWillStart:self];
}
}
}
- (BOOL)isInHierarchy

View File

@ -59,6 +59,8 @@ extern id ASDisplayNodeFindFirstSubnode(ASDisplayNode *start, BOOL (^block)(ASDi
*/
extern id ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c);
extern UIColor *ASDisplayNodeDefaultPlaceholderColor();
/**
Disable willAppear / didAppear / didDisappear notifications for a sub-hierarchy, then re-enable when done. Nested calls are supported.
*/

View File

@ -122,6 +122,15 @@ extern id ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c)
});
}
#pragma mark - Placeholders
UIColor *ASDisplayNodeDefaultPlaceholderColor()
{
return [UIColor colorWithWhite:0.95 alpha:1.0];
}
#pragma mark - Hierarchy Notifications
void ASDisplayNodeDisableHierarchyNotifications(ASDisplayNode *node)
{
[node __incrementVisibilityNotificationsDisabled];

View File

@ -53,6 +53,11 @@ typedef UIImage *(^asimagenode_modification_block_t)(UIImage *image);
*/
@property (nonatomic, assign) ASImageNodeTint tint;
/**
@abstract The placeholder color.
*/
@property (nonatomic, strong) UIColor *placeholderColor;
/**
* @abstract Indicates whether efficient cropping of the receiver is enabled.
*

View File

@ -13,6 +13,7 @@
#import <AsyncDisplayKit/ASAssert.h>
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import "ASImageNode+CGExtras.h"
@ -91,6 +92,7 @@
_cropEnabled = YES;
_cropRect = CGRectMake(0.5, 0.5, 0, 0);
_cropDisplayBounds = CGRectNull;
_placeholderColor = ASDisplayNodeDefaultPlaceholderColor();
return self;
}
@ -139,6 +141,14 @@
return _tint;
}
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
_placeholderColor = placeholderColor;
// prevent placeholders if we don't have a color
self.placeholderEnabled = placeholderColor != nil;
}
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer;
{
BOOL hasValidCropBounds = _cropEnabled && !CGRectIsNull(_cropDisplayBounds) && !CGRectIsEmpty(_cropDisplayBounds);

View File

@ -69,6 +69,18 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) {
*/
@property (nonatomic, readonly, assign) NSUInteger lineCount;
#pragma mark - Placeholders
/**
@abstract The placeholder color.
*/
@property (nonatomic, strong) UIColor *placeholderColor;
/**
@abstract Inset each line of the placeholder.
*/
@property (nonatomic, assign) UIEdgeInsets placeholderInsets;
#pragma mark - Shadow
/**

View File

@ -14,6 +14,7 @@
#import <AsyncDisplayKit/ASHighlightOverlayLayer.h>
#import <AsyncDisplayKit/ASTextNodeCoreTextAdditions.h>
#import <AsyncDisplayKit/ASTextNodeTextKitHelpers.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import "ASTextNodeRenderer.h"
#import "ASTextNodeShadower.h"
@ -135,6 +136,11 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
self.accessibilityTraits = UIAccessibilityTraitStaticText;
_constrainedSize = CGSizeMake(-INFINITY, -INFINITY);
// Placeholders
self.placeholderEnabled = YES;
_placeholderColor = ASDisplayNodeDefaultPlaceholderColor();
_placeholderInsets = UIEdgeInsetsMake(1.0, 0.0, 1.0, 0.0);
}
return self;
@ -666,6 +672,43 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f)
return [self.class _adjustRendererRect:frame forShadowPadding:self.shadowPadding];
}
#pragma mark - Placeholders
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
_placeholderColor = placeholderColor;
// prevent placeholders if we don't have a color
self.placeholderEnabled = placeholderColor != nil;
}
- (UIImage *)placeholderImage
{
CGSize size = self.calculatedSize;
UIGraphicsBeginImageContext(size);
[self.placeholderColor setFill];
ASTextNodeRenderer *renderer = [self _renderer];
NSRange textRange = [renderer visibleRange];
// cap height is both faster and creates less subpixel blending
NSArray *lineRects = [self _rectsForTextRange:textRange measureOption:ASTextNodeRendererMeasureOptionLineHeight];
// fill each line with the placeholder color
for (NSValue *rectValue in lineRects) {
CGRect lineRect = [rectValue CGRectValue];
CGRect fillBounds = UIEdgeInsetsInsetRect(lineRect, self.placeholderInsets);
if (fillBounds.size.width > 0.0 && fillBounds.size.height > 0.0) {
UIRectFill(fillBounds);
}
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

View File

@ -53,6 +53,12 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)());
UIView *_view;
CALayer *_layer;
UIImage *_placeholderImage;
CALayer *_placeholderLayer;
// keeps track of nodes/subnodes that have not finished display, used with placeholders
NSMutableSet *_pendingDisplayNodes;
_ASPendingState *_pendingViewState;
struct {

View File

@ -75,7 +75,7 @@ static const CGFloat kInnerPadding = 10.0f;
// kitten image, with a purple background colour serving as placeholder
_imageNode = [[ASNetworkImageNode alloc] init];
_imageNode.backgroundColor = [UIColor purpleColor];
_imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor();
_imageNode.URL = [NSURL URLWithString:[NSString stringWithFormat:@"http://placekitten.com/%zd/%zd",
(NSInteger)roundl(_kittenSize.width),
(NSInteger)roundl(_kittenSize.height)]];

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,3 @@
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'AsyncDisplayKit', :path => '../..'

View File

@ -0,0 +1,372 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; };
05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; };
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; };
05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; };
29A7F3A51A3638DE00CF34F2 /* SlowpokeTextNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A7F3A41A3638DE00CF34F2 /* SlowpokeTextNode.m */; };
29E35E9B1A2F8DB0007B4B17 /* SlowpokeImageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E35E9A1A2F8DB0007B4B17 /* SlowpokeImageNode.m */; };
29E35E9E1A2F8DBC007B4B17 /* SlowpokeShareNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E35E9D1A2F8DBC007B4B17 /* SlowpokeShareNode.m */; };
29E35EA01A2F9650007B4B17 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = 29E35E9F1A2F9650007B4B17 /* logo.png */; };
29E35EA31A2FD0E9007B4B17 /* PostNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 29E35EA21A2FD0E9007B4B17 /* PostNode.m */; };
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 */; };
/* 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 = "<group>"; };
05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
29A7F3A31A3638DE00CF34F2 /* SlowpokeTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SlowpokeTextNode.h; sourceTree = "<group>"; };
29A7F3A41A3638DE00CF34F2 /* SlowpokeTextNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SlowpokeTextNode.m; sourceTree = "<group>"; };
29E35E991A2F8DB0007B4B17 /* SlowpokeImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SlowpokeImageNode.h; sourceTree = "<group>"; };
29E35E9A1A2F8DB0007B4B17 /* SlowpokeImageNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SlowpokeImageNode.m; sourceTree = "<group>"; };
29E35E9C1A2F8DBC007B4B17 /* SlowpokeShareNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SlowpokeShareNode.h; sourceTree = "<group>"; };
29E35E9D1A2F8DBC007B4B17 /* SlowpokeShareNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SlowpokeShareNode.m; sourceTree = "<group>"; };
29E35E9F1A2F9650007B4B17 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
29E35EA11A2FD0E9007B4B17 /* PostNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostNode.h; sourceTree = "<group>"; };
29E35EA21A2FD0E9007B4B17 /* PostNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostNode.m; sourceTree = "<group>"; };
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; };
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 */
/* Begin PBXFrameworksBuildPhase section */
05E2127E19D4DB510098F589 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
05E2127819D4DB510098F589 = {
isa = PBXGroup;
children = (
05E2128319D4DB510098F589 /* Sample */,
05E2128219D4DB510098F589 /* Products */,
1A943BF0259746F18D6E423F /* Frameworks */,
1AE410B73DA5C3BD087ACDD7 /* Pods */,
);
sourceTree = "<group>";
};
05E2128219D4DB510098F589 /* Products */ = {
isa = PBXGroup;
children = (
05E2128119D4DB510098F589 /* Sample.app */,
);
name = Products;
sourceTree = "<group>";
};
05E2128319D4DB510098F589 /* Sample */ = {
isa = PBXGroup;
children = (
05E2128819D4DB510098F589 /* AppDelegate.h */,
05E2128919D4DB510098F589 /* AppDelegate.m */,
29E35E9F1A2F9650007B4B17 /* logo.png */,
29E35EA11A2FD0E9007B4B17 /* PostNode.h */,
29E35EA21A2FD0E9007B4B17 /* PostNode.m */,
29E35E991A2F8DB0007B4B17 /* SlowpokeImageNode.h */,
29E35E9A1A2F8DB0007B4B17 /* SlowpokeImageNode.m */,
29E35E9C1A2F8DBC007B4B17 /* SlowpokeShareNode.h */,
29E35E9D1A2F8DBC007B4B17 /* SlowpokeShareNode.m */,
29A7F3A31A3638DE00CF34F2 /* SlowpokeTextNode.h */,
29A7F3A41A3638DE00CF34F2 /* SlowpokeTextNode.m */,
05E2128419D4DB510098F589 /* Supporting Files */,
05E2128B19D4DB510098F589 /* ViewController.h */,
05E2128C19D4DB510098F589 /* ViewController.m */,
);
path = Sample;
sourceTree = "<group>";
};
05E2128419D4DB510098F589 /* Supporting Files */ = {
isa = PBXGroup;
children = (
0585427F19D4DBE100606EA6 /* Default-568h@2x.png */,
6C2C82AA19EE274300767484 /* Default-667h@2x.png */,
6C2C82AB19EE274300767484 /* Default-736h@3x.png */,
05E2128519D4DB510098F589 /* Info.plist */,
05E2128619D4DB510098F589 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
1A943BF0259746F18D6E423F /* Frameworks */ = {
isa = PBXGroup;
children = (
3D24B17D1E4A4E7A9566C5E9 /* libPods.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
1AE410B73DA5C3BD087ACDD7 /* Pods */ = {
isa = PBXGroup;
children = (
C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */,
088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
05E2128019D4DB510098F589 /* Sample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */;
buildPhases = (
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */,
05E2127D19D4DB510098F589 /* Sources */,
05E2127E19D4DB510098F589 /* Frameworks */,
05E2127F19D4DB510098F589 /* Resources */,
F012A6F39E0149F18F564F50 /* Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = Sample;
productName = Sample;
productReference = 05E2128119D4DB510098F589 /* Sample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
05E2127919D4DB510098F589 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0600;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
05E2128019D4DB510098F589 = {
CreatedOnToolsVersion = 6.0.1;
};
};
};
buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 05E2127819D4DB510098F589;
productRefGroup = 05E2128219D4DB510098F589 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
05E2128019D4DB510098F589 /* Sample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
05E2127F19D4DB510098F589 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
29E35EA01A2F9650007B4B17 /* logo.png in Resources */,
0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */,
6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */,
6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
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 /* Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
05E2127D19D4DB510098F589 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
29A7F3A51A3638DE00CF34F2 /* SlowpokeTextNode.m in Sources */,
29E35E9B1A2F8DB0007B4B17 /* SlowpokeImageNode.m in Sources */,
29E35E9E1A2F8DBC007B4B17 /* SlowpokeShareNode.m in Sources */,
05E2128D19D4DB510098F589 /* ViewController.m in Sources */,
05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */,
29E35EA31A2FD0E9007B4B17 /* PostNode.m in Sources */,
05E2128719D4DB510098F589 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
05E212A219D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
05E212A319D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
05E212A519D4DB510098F589 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
05E212A619D4DB510098F589 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
INFOPLIST_FILE = Sample/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 7.1;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A219D4DB510098F589 /* Debug */,
05E212A319D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
05E212A519D4DB510098F589 /* Debug */,
05E212A619D4DB510098F589 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 05E2127919D4DB510098F589 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:Sample.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1 @@
<?xml version='1.0' encoding='UTF-8'?><Workspace version='1.0'><FileRef location='group:Sample.xcodeproj'/><FileRef location='group:Pods/Pods.xcodeproj'/></Workspace>

View File

@ -0,0 +1,18 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@ -0,0 +1,27 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "AppDelegate.h"
#import "ViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[ViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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>
@interface PostNode : ASDisplayNode
@end

View File

@ -0,0 +1,88 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "PostNode.h"
#import "SlowpokeShareNode.h"
#import "SlowpokeTextNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@interface PostNode ()
{
SlowpokeTextNode *_textNode;
SlowpokeShareNode *_needyChildNode; // this node slows down display
}
@end
@implementation PostNode
// turn on to demo that the parent displays a placeholder even if it takes the longest
//+ (void)drawRect:(CGRect)bounds withParameters:(id<NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
//{
// usleep( (long)(1.2 * USEC_PER_SEC) ); // artificial delay of 1.2s
//
// // demonstrates that the parent node should also adhere to the placeholder
// [[UIColor colorWithWhite:0.95 alpha:1.0] setFill];
// UIRectFill(bounds);
//}
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_textNode = [[SlowpokeTextNode alloc] init];
_textNode.placeholderInsets = UIEdgeInsetsMake(3.0, 0.0, 3.0, 0.0);
_textNode.placeholderEnabled = YES;
NSString *text = @"Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh.";
NSDictionary *attributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:17.0] };
_textNode.attributedString = [[NSAttributedString alloc] initWithString:text attributes:attributes];
_needyChildNode = [[SlowpokeShareNode alloc] init];
_needyChildNode.opaque = NO;
[self addSubnode:_textNode];
[self addSubnode:_needyChildNode];
return self;
}
- (UIImage *)placeholderImage
{
CGSize size = self.calculatedSize;
UIGraphicsBeginImageContext(size);
[[UIColor colorWithWhite:0.9 alpha:1] setFill];
UIRectFill((CGRect){CGPointZero, size});
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
CGSize textSize = [_textNode measure:constrainedSize];
CGSize shareSize = [_needyChildNode measure:constrainedSize];
return CGSizeMake(constrainedSize.width, textSize.height + 10.0 + shareSize.height);
}
- (void)layout
{
CGSize textSize = _textNode.calculatedSize;
CGSize needyChildSize = _needyChildNode.calculatedSize;
_textNode.frame = (CGRect){CGPointZero, textSize};
_needyChildNode.frame = (CGRect){0.0, CGRectGetMaxY(_textNode.frame) + 10.0, needyChildSize};
}
@end

View File

@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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>
@interface SlowpokeImageNode : ASImageNode
@end

View File

@ -0,0 +1,65 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "SlowpokeImageNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
static CGFloat const kASDKLogoAspectRatio = 2.79;
@implementation SlowpokeImageNode
+ (UIImage *)displayWithParameters:(id<NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock
{
usleep( (long)(0.5 * USEC_PER_SEC) ); // artificial delay of 0.5s
return [super displayWithParameters:parameters isCancelled:isCancelledBlock];
}
- (instancetype)init
{
if (self = [super init]) {
self.placeholderEnabled = YES;
}
return self;
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
if (constrainedSize.width > 0.0) {
return CGSizeMake(constrainedSize.width, constrainedSize.width / kASDKLogoAspectRatio);
} else if (constrainedSize.height > 0.0) {
return CGSizeMake(constrainedSize.height * kASDKLogoAspectRatio, constrainedSize.height);
}
return CGSizeZero;
}
- (UIImage *)placeholderImage
{
CGSize size = self.calculatedSize;
UIGraphicsBeginImageContext(size);
[[UIColor colorWithWhite:0.9 alpha:1] setStroke];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointZero];
[path addLineToPoint:(CGPoint){size.width, size.height}];
[path stroke];
[path moveToPoint:(CGPoint){size.width, 0.0}];
[path addLineToPoint:(CGPoint){0.0, size.height}];
[path stroke];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end

View File

@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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>
@interface SlowpokeShareNode : ASControlNode
@end

View File

@ -0,0 +1,43 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "SlowpokeShareNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
static NSUInteger const kRingCount = 3;
static CGFloat const kRingStrokeWidth = 1.0;
static CGSize const kIconSize = (CGSize){ 60.0, 17.0 };
@implementation SlowpokeShareNode
+ (void)drawRect:(CGRect)bounds withParameters:(id<NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
{
usleep( (long)(0.8 * USEC_PER_SEC) ); // artificial delay of 0.8s
[[UIColor colorWithRed:0.f green:122/255.f blue:1.f alpha:1.f] setStroke];
for (NSUInteger i = 0; i < kRingCount; i++) {
CGFloat x = i * kIconSize.width / kRingCount;
CGRect frame = CGRectMake(x, 0.f, kIconSize.height, kIconSize.height);
CGRect strokeFrame = CGRectInset(frame, kRingStrokeWidth, kRingStrokeWidth);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeFrame cornerRadius:kIconSize.height / 2.f];
[path setLineWidth:kRingStrokeWidth];
[path stroke];
}
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
return kIconSize;
}
@end

View File

@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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>
@interface SlowpokeTextNode : ASTextNode
@end

View File

@ -0,0 +1,25 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "SlowpokeTextNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@implementation SlowpokeTextNode
+ (void)drawRect:(CGRect)bounds withParameters:(id<NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
{
usleep( (long)(1.0 * USEC_PER_SEC) ); // artificial delay of 1.0
[super drawRect:bounds withParameters:parameters isCancelled:isCancelledBlock isRasterizing:isRasterizing];
}
@end

View File

@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end

View File

@ -0,0 +1,105 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 "ViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "PostNode.h"
#import "SlowpokeImageNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@interface ViewController ()
{
PostNode *_postNode;
SlowpokeImageNode *_imageNode;
UIButton *_displayButton;
}
@end
@implementation ViewController
#pragma mark -
#pragma mark UIViewController
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_displayButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_displayButton setTitle:@"Display me!" forState:UIControlStateNormal];
[_displayButton addTarget:self action:@selector(onDisplayButton:) forControlEvents:UIControlEventTouchUpInside];
UIColor *tintBlue = [UIColor colorWithRed:0 green:122/255.0 blue:1.0 alpha:1.0];
[_displayButton setTitleColor:tintBlue forState:UIControlStateNormal];
[_displayButton setTitleColor:[tintBlue colorWithAlphaComponent:0.5] forState:UIControlStateHighlighted];
_displayButton.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:_displayButton];
}
- (void)viewWillLayoutSubviews
{
CGFloat padding = 20.0;
CGRect bounds = self.view.bounds;
CGFloat constrainedWidth = CGRectGetWidth(bounds);
CGSize constrainedSize = CGSizeMake(constrainedWidth - 2 * padding, CGFLOAT_MAX);
CGSize postSize = [_postNode measure:constrainedSize];
CGSize imageSize = [_imageNode measure:constrainedSize];
_imageNode.frame = (CGRect){padding, padding, imageSize};
_postNode.frame = (CGRect){padding, CGRectGetMaxY(_imageNode.frame) + 10.0, postSize};
CGFloat buttonHeight = 55.0;
_displayButton.frame = (CGRect){0.0, CGRectGetHeight(bounds) - buttonHeight, CGRectGetWidth(bounds), buttonHeight};
}
// this method is pretty gross and just for demonstration :]
- (void)createAndDisplayNodes
{
[_imageNode.view removeFromSuperview];
[_postNode.view removeFromSuperview];
// ASImageNode gets placeholders by default
_imageNode = [[SlowpokeImageNode alloc] init];
_imageNode.image = [UIImage imageNamed:@"logo"];
_postNode = [[PostNode alloc] init];
// change to NO to see text placeholders, change to YES to see the parent placeholder
// this placeholder will cover all subnodes while they are displaying, just a like a stage curtain!
_postNode.placeholderEnabled = NO;
[self.view addSubview:_imageNode.view];
[self.view addSubview:_postNode.view];
}
#pragma mark -
#pragma mark Actions
- (void)onDisplayButton:(id)sender
{
[self createAndDisplayNodes];
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

View File

@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* 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 <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}