Manually display nodes

Added a sample project that will demonstrate how to manually display nodes. Removed the UIWindow hack that coupled display of nodes with Core Animation transactions.
This commit is contained in:
Ryan Nystrom 2014-11-25 13:33:40 -08:00
parent ab46377af2
commit 8b0dbf7288
32 changed files with 1240 additions and 24 deletions

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ docs/htdocs
docs/.sass-cache docs/.sass-cache
*.swp *.swp
*.lock

View File

@ -294,6 +294,13 @@
*/ */
@property (nonatomic, assign) BOOL shouldRasterizeDescendants; @property (nonatomic, assign) BOOL shouldRasterizeDescendants;
/**
* @abstract Calls -setNeedsDisplay and -displayIfNeeded on the node's backing store and recursively calls -display on
* all subnodes forcing the entire node hierarchy to be displayed. This method must be called on the main thread but
* there are plans to allow this on any thread.
*/
- (void)display;
/** /**
* @abstract Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. * @abstract Display the node's view/layer immediately on the current thread, bypassing the background thread rendering.
*/ */

View File

@ -437,6 +437,27 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)())
_contentsScaleForDisplay = contentsScaleForDisplay; _contentsScaleForDisplay = contentsScaleForDisplay;
} }
- (void)display
{
ASDisplayNodeAssertMainThread();
// rendering a backing store requires a node be laid out
[self layout];
for (ASDisplayNode *node in self.subnodes) {
[node display];
}
CALayer *layer = [self isLayerBacked] ? self.layer : self.view.layer;
if (layer.contents) {
return;
}
[layer setNeedsDisplay];
[layer displayIfNeeded];
}
- (void)displayImmediately - (void)displayImmediately
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();

View File

@ -91,22 +91,6 @@
return sizingQueue; return sizingQueue;
} }
+ (UIView *)workingView
{
// we add nodes' views to this invisible window to start async rendering
static UIWindow *workingWindow = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
workingWindow = [[UIWindow alloc] initWithFrame:CGRectZero];
workingWindow.windowLevel = UIWindowLevelNormal - 1000;
workingWindow.userInteractionEnabled = NO;
workingWindow.clipsToBounds = YES;
workingWindow.hidden = YES;
});
return workingWindow;
}
#pragma mark - #pragma mark -
#pragma mark Helpers. #pragma mark Helpers.
@ -191,7 +175,7 @@ static BOOL ASRangeIsValid(NSRange range)
NSInteger index = [self indexForIndexPath:node.asyncdisplaykit_indexPath]; NSInteger index = [self indexForIndexPath:node.asyncdisplaykit_indexPath];
if (NSLocationInRange(index, _workingRange)) { if (NSLocationInRange(index, _workingRange)) {
// move the node's view to the working range area, so its rendering persists // move the node's view to the working range area, so its rendering persists
[self moveNodeToWorkingView:node]; [self moveNodeToWorkingRange:node];
} else { } else {
// this node isn't in the working range, remove it from the view hierarchy // this node isn't in the working range, remove it from the view hierarchy
[self removeNodeFromWorkingView:node]; [self removeNodeFromWorkingView:node];
@ -205,20 +189,22 @@ static BOOL ASRangeIsValid(NSRange range)
[node recursiveSetPreventOrCancelDisplay:YES]; [node recursiveSetPreventOrCancelDisplay:YES];
[node.view removeFromSuperview]; [node.view removeFromSuperview];
// since this class usually manages large or infinite data sets, the working range // since this class usually manages large or infinite data sets, the working range
// directly bounds memory usage by requiring redrawing any content that falls outside the range. // directly bounds memory usage by requiring redrawing any content that falls outside the range.
[node recursivelyReclaimMemory]; [node recursivelyReclaimMemory];
[_workingIndexPaths removeObject:node.asyncdisplaykit_indexPath]; [_workingIndexPaths removeObject:node.asyncdisplaykit_indexPath];
} }
- (void)moveNodeToWorkingView:(ASCellNode *)node - (void)moveNodeToWorkingRange:(ASCellNode *)node
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
ASDisplayNodeAssert(node, @"invalid argument"); ASDisplayNodeAssert(node, @"invalid argument");
[self moveNode:node toView:[ASRangeController workingView]]; // if node is in the working range it should not actively be in view
[node.view removeFromSuperview];
[_workingIndexPaths addObject:node.asyncdisplaykit_indexPath]; [_workingIndexPaths addObject:node.asyncdisplaykit_indexPath];
} }
@ -231,7 +217,7 @@ static BOOL ASRangeIsValid(NSRange range)
[CATransaction begin]; [CATransaction begin];
[view addSubview:node.view]; [view addSubview:node.view];
[CATransaction commit]; [CATransaction commit];
} }
@ -481,7 +467,7 @@ static NSRange ASCalculateWorkingRange(ASRangeTuningParameters params, ASScrollD
_nodeSizes, _nodeSizes,
[_delegate rangeControllerViewportSize:self]); [_delegate rangeControllerViewportSize:self]);
} }
[self setWorkingRange:workingRange]; [self setWorkingRange:workingRange];
} }
@ -513,7 +499,7 @@ static NSRange ASCalculateWorkingRange(ASRangeTuningParameters params, ASScrollD
// if a node in the working range is still sizing, the sizing logic will add it to the working range for us later // if a node in the working range is still sizing, the sizing logic will add it to the working range for us later
ASCellNode *node = [self sizedNodeForIndexPath:indexPath]; ASCellNode *node = [self sizedNodeForIndexPath:indexPath];
if (node) { if (node) {
[self moveNodeToWorkingView:node]; [self moveNodeToWorkingRange:node];
} else { } else {
ASDisplayNodeAssert(_sizedNodeCount != _totalNodeCount, @"logic error"); ASDisplayNodeAssert(_sizedNodeCount != _totalNodeCount, @"logic error");
} }
@ -577,6 +563,8 @@ static NSRange ASCalculateWorkingRange(ASRangeTuningParameters params, ASScrollD
for (NSIndexPath *indexPath in indexPaths) { for (NSIndexPath *indexPath in indexPaths) {
ASCellNode *node = _nodes[indexPath]; ASCellNode *node = _nodes[indexPath];
_nodeSizes[[self indexForIndexPath:indexPath]] = [NSValue valueWithCGSize:node.calculatedSize]; _nodeSizes[[self indexForIndexPath:indexPath]] = [NSValue valueWithCGSize:node.calculatedSize];
[node display];
} }
ASDisplayNodeAssert(_nodeSizes.count == _sizedNodeCount, @"logic error"); ASDisplayNodeAssert(_nodeSizes.count == _sizedNodeCount, @"logic error");

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,377 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
2930E8401A1FCA890032D7B3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2930E83F1A1FCA890032D7B3 /* main.m */; };
2930E8431A1FCA890032D7B3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 2930E8421A1FCA890032D7B3 /* AppDelegate.m */; };
2930E8451A1FCA890032D7B3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2930E8441A1FCA890032D7B3 /* Images.xcassets */; };
2930E8481A1FCA890032D7B3 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2930E8461A1FCA890032D7B3 /* LaunchScreen.xib */; };
2930E85F1A1FCABC0032D7B3 /* ExpensiveController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2930E85E1A1FCABC0032D7B3 /* ExpensiveController.m */; };
2930E8621A1FF18A0032D7B3 /* PostNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2930E8611A1FF18A0032D7B3 /* PostNode.m */; };
2930E8651A1FF58A0032D7B3 /* ShareNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2930E8641A1FF58A0032D7B3 /* ShareNode.m */; };
2983606D1A200AE50022B4F8 /* PlaceholderTextNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 2983606C1A200AE50022B4F8 /* PlaceholderTextNode.m */; };
29CF6DDF1A250F960031DF86 /* CheapController.m in Sources */ = {isa = PBXBuildFile; fileRef = 29CF6DDE1A250F960031DF86 /* CheapController.m */; };
7A7B2A305C39C4CA59331E10 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DABEBB92D527C244C86ADF06 /* libPods.a */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
2930E83A1A1FCA890032D7B3 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; };
2930E83E1A1FCA890032D7B3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2930E83F1A1FCA890032D7B3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
2930E8411A1FCA890032D7B3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
2930E8421A1FCA890032D7B3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
2930E8441A1FCA890032D7B3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
2930E8471A1FCA890032D7B3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
2930E85D1A1FCABC0032D7B3 /* ExpensiveController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExpensiveController.h; sourceTree = "<group>"; };
2930E85E1A1FCABC0032D7B3 /* ExpensiveController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExpensiveController.m; sourceTree = "<group>"; };
2930E8601A1FF18A0032D7B3 /* PostNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PostNode.h; sourceTree = "<group>"; };
2930E8611A1FF18A0032D7B3 /* PostNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PostNode.m; sourceTree = "<group>"; };
2930E8631A1FF58A0032D7B3 /* ShareNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareNode.h; sourceTree = "<group>"; };
2930E8641A1FF58A0032D7B3 /* ShareNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareNode.m; sourceTree = "<group>"; };
2983606B1A200AE50022B4F8 /* PlaceholderTextNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaceholderTextNode.h; sourceTree = "<group>"; };
2983606C1A200AE50022B4F8 /* PlaceholderTextNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaceholderTextNode.m; sourceTree = "<group>"; };
29CF6DDD1A250F960031DF86 /* CheapController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CheapController.h; sourceTree = "<group>"; };
29CF6DDE1A250F960031DF86 /* CheapController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CheapController.m; sourceTree = "<group>"; };
AC624CB0EACE6790C185F790 /* 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>"; };
BFA8076C256CAC9E6E153731 /* 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>"; };
DABEBB92D527C244C86ADF06 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
2930E8371A1FCA890032D7B3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7A7B2A305C39C4CA59331E10 /* libPods.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2930E8311A1FCA890032D7B3 = {
isa = PBXGroup;
children = (
2930E83C1A1FCA890032D7B3 /* Sample */,
2930E83B1A1FCA890032D7B3 /* Products */,
E4382CEF3F9728AD310690EE /* Pods */,
3AD89B2B22B71B86CDE10916 /* Frameworks */,
);
sourceTree = "<group>";
};
2930E83B1A1FCA890032D7B3 /* Products */ = {
isa = PBXGroup;
children = (
2930E83A1A1FCA890032D7B3 /* Sample.app */,
);
name = Products;
sourceTree = "<group>";
};
2930E83C1A1FCA890032D7B3 /* Sample */ = {
isa = PBXGroup;
children = (
2930E8411A1FCA890032D7B3 /* AppDelegate.h */,
2930E8421A1FCA890032D7B3 /* AppDelegate.m */,
2930E8441A1FCA890032D7B3 /* Images.xcassets */,
2930E8461A1FCA890032D7B3 /* LaunchScreen.xib */,
2930E83D1A1FCA890032D7B3 /* Supporting Files */,
2930E85D1A1FCABC0032D7B3 /* ExpensiveController.h */,
2930E85E1A1FCABC0032D7B3 /* ExpensiveController.m */,
29CF6DDD1A250F960031DF86 /* CheapController.h */,
29CF6DDE1A250F960031DF86 /* CheapController.m */,
2930E8601A1FF18A0032D7B3 /* PostNode.h */,
2930E8611A1FF18A0032D7B3 /* PostNode.m */,
2983606B1A200AE50022B4F8 /* PlaceholderTextNode.h */,
2983606C1A200AE50022B4F8 /* PlaceholderTextNode.m */,
2930E8631A1FF58A0032D7B3 /* ShareNode.h */,
2930E8641A1FF58A0032D7B3 /* ShareNode.m */,
);
path = Sample;
sourceTree = "<group>";
};
2930E83D1A1FCA890032D7B3 /* Supporting Files */ = {
isa = PBXGroup;
children = (
2930E83E1A1FCA890032D7B3 /* Info.plist */,
2930E83F1A1FCA890032D7B3 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
3AD89B2B22B71B86CDE10916 /* Frameworks */ = {
isa = PBXGroup;
children = (
DABEBB92D527C244C86ADF06 /* libPods.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
E4382CEF3F9728AD310690EE /* Pods */ = {
isa = PBXGroup;
children = (
BFA8076C256CAC9E6E153731 /* Pods.debug.xcconfig */,
AC624CB0EACE6790C185F790 /* Pods.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
2930E8391A1FCA890032D7B3 /* Sample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2930E8571A1FCA890032D7B3 /* Build configuration list for PBXNativeTarget "Sample" */;
buildPhases = (
21550E146D80CC7748448A11 /* Check Pods Manifest.lock */,
2930E8361A1FCA890032D7B3 /* Sources */,
2930E8371A1FCA890032D7B3 /* Frameworks */,
2930E8381A1FCA890032D7B3 /* Resources */,
15832E3E36BB198D0274923F /* Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = Sample;
productName = Sample;
productReference = 2930E83A1A1FCA890032D7B3 /* Sample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
2930E8321A1FCA890032D7B3 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = "Ryan Nystrom";
TargetAttributes = {
2930E8391A1FCA890032D7B3 = {
CreatedOnToolsVersion = 6.1;
};
};
};
buildConfigurationList = 2930E8351A1FCA890032D7B3 /* Build configuration list for PBXProject "Sample" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 2930E8311A1FCA890032D7B3;
productRefGroup = 2930E83B1A1FCA890032D7B3 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
2930E8391A1FCA890032D7B3 /* Sample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
2930E8381A1FCA890032D7B3 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2930E8481A1FCA890032D7B3 /* LaunchScreen.xib in Resources */,
2930E8451A1FCA890032D7B3 /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
15832E3E36BB198D0274923F /* 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;
};
21550E146D80CC7748448A11 /* 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;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
2930E8361A1FCA890032D7B3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2930E85F1A1FCABC0032D7B3 /* ExpensiveController.m in Sources */,
2930E8621A1FF18A0032D7B3 /* PostNode.m in Sources */,
29CF6DDF1A250F960031DF86 /* CheapController.m in Sources */,
2983606D1A200AE50022B4F8 /* PlaceholderTextNode.m in Sources */,
2930E8651A1FF58A0032D7B3 /* ShareNode.m in Sources */,
2930E8431A1FCA890032D7B3 /* AppDelegate.m in Sources */,
2930E8401A1FCA890032D7B3 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
2930E8461A1FCA890032D7B3 /* LaunchScreen.xib */ = {
isa = PBXVariantGroup;
children = (
2930E8471A1FCA890032D7B3 /* Base */,
);
name = LaunchScreen.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
2930E8551A1FCA890032D7B3 /* 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.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
2930E8561A1FCA890032D7B3 /* 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.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
2930E8581A1FCA890032D7B3 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BFA8076C256CAC9E6E153731 /* 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;
};
2930E8591A1FCA890032D7B3 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = AC624CB0EACE6790C185F790 /* 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 */
2930E8351A1FCA890032D7B3 /* Build configuration list for PBXProject "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2930E8551A1FCA890032D7B3 /* Debug */,
2930E8561A1FCA890032D7B3 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2930E8571A1FCA890032D7B3 /* Build configuration list for PBXNativeTarget "Sample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2930E8581A1FCA890032D7B3 /* Debug */,
2930E8591A1FCA890032D7B3 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 2930E8321A1FCA890032D7B3 /* 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,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Sample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

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>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@ -0,0 +1,48 @@
/* 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 "ExpensiveController.h"
#import "CheapController.h"
#define AS_DEMO_PRELOADING 1
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
CGRect screenBounds = [[UIScreen mainScreen] bounds];
UIWindow *window = [[UIWindow alloc] initWithFrame:screenBounds];
CheapController *cheap = [[CheapController alloc] init];
cheap.tabBarItem.title = @"Cheap";
cheap.tabBarItem.image = [UIImage imageNamed:@"cheap"];
ExpensiveController *expensive = [[ExpensiveController alloc] init];
expensive.tabBarItem.title = @"Expensive";
expensive.tabBarItem.image = [UIImage imageNamed:@"expensive"];
#if AS_DEMO_PRELOADING
[expensive preloadForSize:screenBounds.size];
#endif
UITabBarController *tab = [[UITabBarController alloc] init];
tab.viewControllers = @[cheap, expensive];
window.rootViewController = tab;
[window makeKeyAndVisible];
[self setWindow:window];
return YES;
}
@end

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6250" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6244"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Wait Wait, Don't Show Me!" textAlignment="center" lineBreakMode="middleTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="18" preferredMaxLayoutWidth="441" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="143" width="441" height="36"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="30"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="trailing" secondItem="kId-c2-rCX" secondAttribute="trailing" constant="19" id="VgG-Sf-JFm"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

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 CheapController : UIViewController
@end

View File

@ -0,0 +1,46 @@
/* 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 "CheapController.h"
@interface CheapController ()
{
UILabel *_cheapLabel;
}
@end
@implementation CheapController
- (instancetype)init
{
if (self = [super init]) {
_cheapLabel = [[UILabel alloc] init];
_cheapLabel.text = @"I'm a cheap date.";
[_cheapLabel sizeToFit];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:_cheapLabel];
}
- (void)viewDidLayoutSubviews
{
_cheapLabel.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
}
@end

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 ExpensiveController : UIViewController
- (void)preloadForSize:(CGSize)size;
@end

View File

@ -0,0 +1,123 @@
/* 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 "ExpensiveController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "PostNode.h"
static NSUInteger const kNumberOfPosts = 20;
@interface ExpensiveController () <ASTableViewDataSource, ASTableViewDelegate>
{
ASTableView *_tableView;
}
@end
@implementation ExpensiveController
- (instancetype)init
{
if (self = [super init]) {
_tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.asyncDataSource = self;
_tableView.asyncDelegate = self;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:_tableView];
if (self.tabBarController) {
UIEdgeInsets insets = _tableView.contentInset;
insets.bottom = CGRectGetHeight(self.tabBarController.tabBar.bounds);
_tableView.contentInset = insets;
_tableView.scrollIndicatorInsets = insets;
}
}
- (void)viewDidLayoutSubviews
{
CGRect bounds = self.view.bounds;
_tableView.frame = bounds;
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
#pragma mark -
#pragma mark Public Actions
- (void)preloadForSize:(CGSize)size
{
// without setting the frame the size is CGRectZero which will likely cause layout asserts to fail
_tableView.frame = (CGRect){ CGPointZero, size };
[_tableView reloadData];
}
#pragma mark -
#pragma mark Getters
+ (NSString *)randomPost
{
static NSArray *posts = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
posts = @[
@"Nulla vitae elit libero, a **pharetra augue**. Curabitur blandit tempus porttitor. Donec sed odio dui. Donec id elit non mi porta gravida at eget metus. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum.",
@"Donec id elit non mi porta gravida at **eget metus**. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. **Donec ullamcorper nulla** non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue. Donec id elit non mi porta gravida at eget metus.\nFusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Donec ullamcorper nulla non metus auctor fringilla.",
@"Nullam id dolor id nibh ultricies vehicula ut id **elit**. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Praesent commodo cursus magna, vel scelerisque nisl **consectetur et**.\nNullam id dolor id nibh ultricies vehicula ut id elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
@"Sed posuere consectetur est at lobortis. Etiam porta sem malesuada magna mollis euismod. Curabitur blandit tempus **porttitor**. Nullam id dolor id nibh ultricies vehicula ut id elit.\nCurabitur blandit tempus porttitor.",
@"Aenean lacinia bibendum nulla sed consectetur. Morbi leo risus, porta ac consectetur ac, **vestibulum at eros**. Vestibulum id ligula porta felis euismod semper. Nullam id dolor id nibh ultricies vehicula ut id elit.Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec ullamcorper nulla non metus auctor fringilla.",
@"Nullam quis risus eget urna mollis ornare vel eu leo.\nSed posuere consectetur est at lobortis. Fusce dapibus, tellus ac cursus **commodo**, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nulla vitae **elit libero**, a pharetra augue.",
];
});
NSUInteger randomIndex = arc4random() % posts.count;
return posts[randomIndex];
}
#pragma mark -
#pragma mark ASTableViewDataSource
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:-indexPath.row * 60 * 60 * 24];
NSString *text = [self.class randomPost];
PostNode *node = [[PostNode alloc] initWithDate:date text:text];
return node;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return kNumberOfPosts;
}
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}
@end

View File

@ -0,0 +1,68 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "cheap.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "cheap@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "cheap@3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x",
"filename" : "expensive.png"
},
{
"idiom" : "universal",
"scale" : "2x",
"filename" : "expensive@2x.png"
},
{
"idiom" : "universal",
"scale" : "3x",
"filename" : "expensive@3x.png"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,45 @@
<?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.whoisryannystrom.$(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>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</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 PlaceholderTextNode : ASTextNode
@end

View File

@ -0,0 +1,58 @@
/* 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 "PlaceholderTextNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@interface PlaceholderTextNode ()
{
CALayer *_placeholderLayer;
BOOL _displayFinished;
}
@end
@implementation PlaceholderTextNode
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_placeholderLayer = [CALayer layer];
_placeholderLayer.backgroundColor = [UIColor colorWithWhite:0.7f alpha:0.5f].CGColor;
return self;
}
- (void)willEnterHierarchy
{
[super willEnterHierarchy];
if (!_displayFinished) {
[self.layer addSublayer:_placeholderLayer];
}
}
- (void)layout
{
_placeholderLayer.frame = (CGRect){ CGPointZero, self.calculatedSize };
}
- (void)displayDidFinish
{
_displayFinished = YES;
[_placeholderLayer removeFromSuperlayer];
[super displayDidFinish];
}
@end

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 <AsyncDisplayKit/AsyncDisplayKit.h>
@interface PostNode : ASCellNode
- (instancetype)initWithDate:(NSDate *)date text:(NSString *)text;
@end

View File

@ -0,0 +1,138 @@
/* 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 <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import "ShareNode.h"
#import "PlaceholderTextNode.h"
static CGFloat const kTopPadding = 15.f;
static CGFloat const kOuterPadding = 10.f;
static CGFloat const kTextPadding = 10.f;
@interface PostNode ()
{
PlaceholderTextNode *_textNode;
PlaceholderTextNode *_dateNode;
ShareNode *_shareNode;
ASDisplayNode *_divider;
}
@end
@implementation PostNode
+ (NSDateFormatter *)dateFormatter
{
NSString * const formatterKey = @"dateFormatter";
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
NSDateFormatter *dateFormatter = threadDictionary[formatterKey];
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [NSLocale currentLocale];
dateFormatter.dateStyle = NSDateFormatterLongStyle;
dateFormatter.timeStyle = NSDateFormatterNoStyle;
threadDictionary[formatterKey] = dateFormatter;
}
return dateFormatter;
}
- (instancetype)initWithDate:(NSDate *)date text:(NSString *)text
{
if (!(self = [super init]))
return nil;
NSString *dateString = [[self.class dateFormatter] stringFromDate:date];
_dateNode = [[PlaceholderTextNode alloc] init];
_dateNode.attributedString = [[NSAttributedString alloc] initWithString:dateString attributes:[self dateTextStyle]];
[self addSubnode:_dateNode];
_textNode = [[PlaceholderTextNode alloc] init];
_textNode.attributedString = [self styledTextString:text];
[self addSubnode:_textNode];
_divider = [[ASDisplayNode alloc] init];
_divider.backgroundColor = [UIColor lightGrayColor];
[self addSubnode:_divider];
_shareNode = [[ShareNode alloc] init];
[self addSubnode:_shareNode];
return self;
}
// purposefully expensive for demo
- (NSAttributedString *)styledTextString:(NSString *)originalText
{
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:originalText attributes:[self textStyle]];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\*\\*[\\w\\s]+\\*\\*" options:kNilOptions error:nil];
[regex enumerateMatchesInString:originalText options:kNilOptions range:NSMakeRange(0,originalText.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange range = [result rangeAtIndex:0];
[attributedText addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Bold" size:16.0f] range:range];
}];
return attributedText;
}
- (NSDictionary *)dateTextStyle
{
UIFont *font = [UIFont fontWithName:@"HelveticaNeue-Bold" size:12.0f];
UIColor *color = [UIColor colorWithWhite:0.7f alpha:1.f];
return @{ NSFontAttributeName: font,
NSForegroundColorAttributeName: color };
}
- (NSDictionary *)textStyle
{
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:16.0f];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.paragraphSpacing = 0.5 * font.lineHeight;
style.hyphenationFactor = 1.0;
return @{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: style };
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
CGSize dateSize = [_dateNode measure:CGSizeMake((constrainedSize.width - 2 * kOuterPadding) / 2.f, constrainedSize.height)];
CGSize textSize = [_textNode measure:CGSizeMake(constrainedSize.width - 2 * kOuterPadding, constrainedSize.height)];
[_shareNode measure:constrainedSize];
return CGSizeMake(constrainedSize.width, dateSize.height + textSize.height + 2 * kTopPadding + kTextPadding);
}
- (void)layout
{
CGFloat pixelHeight = 1.0f / [[UIScreen mainScreen] scale];
_divider.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, pixelHeight);
CGSize dateSize = _dateNode.calculatedSize;
_dateNode.frame = CGRectMake(kOuterPadding, kTopPadding, dateSize.width, dateSize.height);
CGSize textSize = _textNode.calculatedSize;
_textNode.frame = CGRectMake(kOuterPadding, CGRectGetMaxY(_dateNode.frame) + kTextPadding, textSize.width, textSize.height);
CGSize iconSize = _shareNode.calculatedSize;
_shareNode.frame = CGRectMake(self.calculatedSize.width - kOuterPadding - iconSize.width, kTopPadding, iconSize.width, iconSize.width);
}
@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 ShareNode : ASControlNode
@end

View File

@ -0,0 +1,72 @@
/* 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 "ShareNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
static NSUInteger const kRingCount = 3;
static CGFloat const kRingStrokeWidth = 1.f;
static CGSize const kIconSize = (CGSize){ 40.f, 10.f };
@interface ShareNode ()
{
ASImageNode *_iconNode;
}
@end
@implementation ShareNode
+ (UIImage *)drawIcon
{
UIGraphicsBeginImageContextWithOptions(kIconSize, NO, [UIScreen mainScreen].scale);
[[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];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_iconNode = [[ASImageNode alloc] init];
_iconNode.image = [self.class drawIcon];
[self addSubnode:_iconNode];
return self;
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
return kIconSize;
}
- (void)layout
{
_iconNode.frame = (CGRect){ CGPointZero, self.calculatedSize };
}
@end

View File

@ -0,0 +1,19 @@
/* 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]));
}
}