diff --git a/examples/CatDealsCollectionView/Podfile b/examples/CatDealsCollectionView/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/CatDealsCollectionView/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..1810a8ebbf --- /dev/null +++ b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,405 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 25FDEC911BF31EE700CEB123 /* ItemNode.m */; }; + 7A83848E1C34359D002CDD08 /* ItemViewModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A83848D1C34359D002CDD08 /* ItemViewModel.m */; }; + 7A8384941C343680002CDD08 /* BlurbNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A8384921C343680002CDD08 /* BlurbNode.m */; }; + 7A8384971C344057002CDD08 /* ItemStyles.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A8384961C344057002CDD08 /* ItemStyles.m */; }; + 7ACD5F891C415B7500E7BE16 /* LoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD5F881C415B7500E7BE16 /* LoadingNode.m */; }; + 7ACD5F961C4847C000E7BE16 /* PlaceholderNetworkImageNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */; }; + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */; }; + AC3C4A641A11F47200143C57 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A631A11F47200143C57 /* main.m */; }; + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A661A11F47200143C57 /* AppDelegate.m */; }; + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; + FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F02BAF78E68BC56FD8C161B7 /* libPods.a */; }; + FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 25FDEC901BF31EE700CEB123 /* ItemNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemNode.h; sourceTree = ""; }; + 25FDEC911BF31EE700CEB123 /* ItemNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemNode.m; sourceTree = ""; }; + 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; + 7A83848C1C34359D002CDD08 /* ItemViewModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemViewModel.h; sourceTree = ""; }; + 7A83848D1C34359D002CDD08 /* ItemViewModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemViewModel.m; sourceTree = ""; }; + 7A8384911C343680002CDD08 /* BlurbNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlurbNode.h; sourceTree = ""; }; + 7A8384921C343680002CDD08 /* BlurbNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlurbNode.m; sourceTree = ""; }; + 7A8384951C344057002CDD08 /* ItemStyles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ItemStyles.h; sourceTree = ""; }; + 7A8384961C344057002CDD08 /* ItemStyles.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ItemStyles.m; sourceTree = ""; }; + 7ACD5F871C415B7500E7BE16 /* LoadingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadingNode.h; sourceTree = ""; }; + 7ACD5F881C415B7500E7BE16 /* LoadingNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoadingNode.m; sourceTree = ""; }; + 7ACD5F941C4847C000E7BE16 /* PlaceholderNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlaceholderNetworkImageNode.h; sourceTree = ""; }; + 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PlaceholderNetworkImageNode.m; sourceTree = ""; }; + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Launchboard.storyboard; sourceTree = ""; }; + AC3C4A5E1A11F47200143C57 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + AC3C4A621A11F47200143C57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AC3C4A631A11F47200143C57 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + AC3C4A651A11F47200143C57 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + AC3C4A661A11F47200143C57 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + AC3C4A681A11F47200143C57 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + AC3C4A691A11F47200143C57 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + F02BAF78E68BC56FD8C161B7 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PresentingViewController.h; sourceTree = ""; }; + FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PresentingViewController.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + AC3C4A5B1A11F47200143C57 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 90A2B9C5397C46134C8A793B /* Pods */ = { + isa = PBXGroup; + children = ( + 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */, + CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + AC3C4A551A11F47200143C57 = { + isa = PBXGroup; + children = ( + AC3C4A601A11F47200143C57 /* Sample */, + AC3C4A5F1A11F47200143C57 /* Products */, + 90A2B9C5397C46134C8A793B /* Pods */, + D6E38FF0CB18E3F55CF06437 /* Frameworks */, + ); + sourceTree = ""; + }; + AC3C4A5F1A11F47200143C57 /* Products */ = { + isa = PBXGroup; + children = ( + AC3C4A5E1A11F47200143C57 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + AC3C4A601A11F47200143C57 /* Sample */ = { + isa = PBXGroup; + children = ( + AC3C4A651A11F47200143C57 /* AppDelegate.h */, + AC3C4A661A11F47200143C57 /* AppDelegate.m */, + AC3C4A681A11F47200143C57 /* ViewController.h */, + AC3C4A691A11F47200143C57 /* ViewController.m */, + 7ACD5F941C4847C000E7BE16 /* PlaceholderNetworkImageNode.h */, + 7ACD5F951C4847C000E7BE16 /* PlaceholderNetworkImageNode.m */, + FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */, + FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */, + AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, + AC3C4A611A11F47200143C57 /* Supporting Files */, + 25FDEC901BF31EE700CEB123 /* ItemNode.h */, + 25FDEC911BF31EE700CEB123 /* ItemNode.m */, + 7A8384951C344057002CDD08 /* ItemStyles.h */, + 7A8384961C344057002CDD08 /* ItemStyles.m */, + 7A83848C1C34359D002CDD08 /* ItemViewModel.h */, + 7A83848D1C34359D002CDD08 /* ItemViewModel.m */, + 7A8384911C343680002CDD08 /* BlurbNode.h */, + 7A8384921C343680002CDD08 /* BlurbNode.m */, + 7ACD5F871C415B7500E7BE16 /* LoadingNode.h */, + 7ACD5F881C415B7500E7BE16 /* LoadingNode.m */, + ); + indentWidth = 2; + path = Sample; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + AC3C4A611A11F47200143C57 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + AC3C4A621A11F47200143C57 /* Info.plist */, + AC3C4A631A11F47200143C57 /* main.m */, + 9BA2CEA01BB2579C00D18414 /* Launchboard.storyboard */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D6E38FF0CB18E3F55CF06437 /* Frameworks */ = { + isa = PBXGroup; + children = ( + F02BAF78E68BC56FD8C161B7 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + AC3C4A5D1A11F47200143C57 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */, + AC3C4A5A1A11F47200143C57 /* Sources */, + AC3C4A5B1A11F47200143C57 /* Frameworks */, + AC3C4A5C1A11F47200143C57 /* Resources */, + A6902C454C7661D0D277AC62 /* Copy Pods Resources */, + B4CD33E927E6F4EE5DD6CCF0 /* Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = AC3C4A5E1A11F47200143C57 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + AC3C4A561A11F47200143C57 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + AC3C4A5D1A11F47200143C57 = { + CreatedOnToolsVersion = 6.1; + }; + }; + }; + buildConfigurationList = AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = AC3C4A551A11F47200143C57; + productRefGroup = AC3C4A5F1A11F47200143C57 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + AC3C4A5D1A11F47200143C57 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + AC3C4A5C1A11F47200143C57 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BA2CEA11BB2579C00D18414 /* Launchboard.storyboard in Resources */, + AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + A6902C454C7661D0D277AC62 /* 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; + }; + B4CD33E927E6F4EE5DD6CCF0 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + F868CFBB21824CC9521B6588 /* 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 */ + AC3C4A5A1A11F47200143C57 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */, + 7ACD5F891C415B7500E7BE16 /* LoadingNode.m in Sources */, + AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, + 7A8384971C344057002CDD08 /* ItemStyles.m in Sources */, + FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */, + 7A8384941C343680002CDD08 /* BlurbNode.m in Sources */, + 7A83848E1C34359D002CDD08 /* ItemViewModel.m in Sources */, + AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, + 7ACD5F961C4847C000E7BE16 /* PlaceholderNetworkImageNode.m in Sources */, + AC3C4A641A11F47200143C57 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + AC3C4A7F1A11F47200143C57 /* 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; + }; + AC3C4A801A11F47200143C57 /* 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; + }; + AC3C4A821A11F47200143C57 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2DBAEE96397BB913350C4530 /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + AC3C4A831A11F47200143C57 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + AC3C4A591A11F47200143C57 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A7F1A11F47200143C57 /* Debug */, + AC3C4A801A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + AC3C4A811A11F47200143C57 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AC3C4A821A11F47200143C57 /* Debug */, + AC3C4A831A11F47200143C57 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = AC3C4A561A11F47200143C57 /* Project object */; +} diff --git a/examples/CatDealsCollectionView/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/CatDealsCollectionView/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/CatDealsCollectionView/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/CatDealsCollectionView/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..f49edc75d6 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/CatDealsCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples/CatDealsCollectionView/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples/CatDealsCollectionView/Sample/AppDelegate.h b/examples/CatDealsCollectionView/Sample/AppDelegate.h new file mode 100644 index 0000000000..80b7fb3d50 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/AppDelegate.h @@ -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 + +#define SIMULATE_WEB_RESPONSE 0 + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/CatDealsCollectionView/Sample/AppDelegate.m b/examples/CatDealsCollectionView/Sample/AppDelegate.m new file mode 100644 index 0000000000..adab692baf --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/AppDelegate.m @@ -0,0 +1,51 @@ +/* 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 "PresentingViewController.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 = [[UINavigationController alloc] init]; + + [self pushNewViewControllerAnimated:NO]; + + [self.window makeKeyAndVisible]; + + return YES; +} + +- (void)pushNewViewControllerAnimated:(BOOL)animated +{ + UINavigationController *navController = (UINavigationController *)self.window.rootViewController; + +#if SIMULATE_WEB_RESPONSE + UIViewController *viewController = [[PresentingViewController alloc] init]; +#else + UIViewController *viewController = [[ViewController alloc] init]; + viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Another Copy" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)]; +#endif + + [navController pushViewController:viewController animated:animated]; +} + +- (void)pushNewViewController +{ + [self pushNewViewControllerAnimated:YES]; +} + +@end diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.h b/examples/CatDealsCollectionView/Sample/BlurbNode.h new file mode 100644 index 0000000000..efe58505e1 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.h @@ -0,0 +1,21 @@ +/* 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 + +/** + * Simple node that displays a placekitten.com attribution. + */ +@interface BlurbNode : ASCellNode + ++ (CGFloat)desiredHeightForWidth:(CGFloat)width; + +@end diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.m b/examples/CatDealsCollectionView/Sample/BlurbNode.m new file mode 100644 index 0000000000..68dcf866bc --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.m @@ -0,0 +1,112 @@ +/* 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 "BlurbNode.h" + +#import +#import + +#import +#import + +static CGFloat kFixedHeight = 75.0f; +static CGFloat kTextPadding = 10.0f; + +@interface BlurbNode () +{ + ASTextNode *_textNode; +} + +@end + + +@implementation BlurbNode + +#pragma mark - +#pragma mark ASCellNode. + ++ (CGFloat)desiredHeightForWidth:(CGFloat)width { + return kFixedHeight; +} + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + self.backgroundColor = [UIColor lightGrayColor]; + // create a text node + _textNode = [[ASTextNode alloc] init]; + _textNode.maximumNumberOfLines = 2; + + // configure the node to support tappable links + _textNode.delegate = self; + _textNode.userInteractionEnabled = YES; + + // generate an attributed string using the custom link attribute specified above + NSString *blurb = @"Kittens courtesy lorempixel.com \U0001F638 \nTitles courtesy of catipsum.com"; + NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:blurb]; + [string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Light" size:16.0f] range:NSMakeRange(0, blurb.length)]; + [string addAttributes:@{ + NSLinkAttributeName: [NSURL URLWithString:@"http://lorempixel.com/"], + NSForegroundColorAttributeName: [UIColor blueColor], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), + } + range:[blurb rangeOfString:@"lorempixel.com"]]; + [string addAttributes:@{ + NSLinkAttributeName: [NSURL URLWithString:@"http://www.catipsum.com/"], + NSForegroundColorAttributeName: [UIColor blueColor], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), + } range:[blurb rangeOfString:@"catipsum.com"]]; + _textNode.attributedString = string; + + // add it as a subnode, and we're done + [self addSubnode:_textNode]; + + return self; +} + +- (void)didLoad +{ + // enable highlighting now that self.layer has loaded -- see ASHighlightOverlayLayer.h + self.layer.as_allowsHighlightDrawing = YES; + + [super didLoad]; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASCenterLayoutSpec *centerSpec = [[ASCenterLayoutSpec alloc] init]; + centerSpec.centeringOptions = ASCenterLayoutSpecCenteringX; + centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionMinimumY; + centerSpec.child = _textNode; + + UIEdgeInsets padding =UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding); + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:padding child:centerSpec]; +} + + +#pragma mark - +#pragma mark ASTextNodeDelegate methods. + +- (BOOL)textNode:(ASTextNode *)richTextNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point +{ + // opt into link highlighting -- tap and hold the link to try it! must enable highlighting on a layer, see -didLoad + return YES; +} + +- (void)textNode:(ASTextNode *)richTextNode tappedLinkAttribute:(NSString *)attribute value:(NSURL *)URL atPoint:(CGPoint)point textRange:(NSRange)textRange +{ + // the node tapped a link, open it + [[UIApplication sharedApplication] openURL:URL]; +} + +@end diff --git a/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json b/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 0000000000..f0fce54771 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Contents.json @@ -0,0 +1,39 @@ +{ + "images" : [ + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default-568h@2x.png", + "minimum-system-version" : "7.0", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "scale" : "1x", + "orientation" : "portrait" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "orientation" : "portrait" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "filename" : "Default-568h@2x.png", + "subtype" : "retina4", + "scale" : "2x" + }, + { + "orientation" : "portrait", + "idiom" : "iphone", + "minimum-system-version" : "7.0", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png new file mode 100644 index 0000000000..1547a98454 Binary files /dev/null and b/examples/CatDealsCollectionView/Sample/Images.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ diff --git a/examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/Contents.json b/examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/Contents.json new file mode 100644 index 0000000000..a9e3a5b11b --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "cat_face.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/cat_face.png b/examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/cat_face.png new file mode 100644 index 0000000000..ee4407212c Binary files /dev/null and b/examples/CatDealsCollectionView/Sample/Images.xcassets/cat_face.imageset/cat_face.png differ diff --git a/examples/CatDealsCollectionView/Sample/Info.plist b/examples/CatDealsCollectionView/Sample/Info.plist new file mode 100644 index 0000000000..8c57e7a83d --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/Info.plist @@ -0,0 +1,59 @@ + + + + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + http://lorempixel.com + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIcons + + CFBundleIcons~ipad + + CFBundleIdentifier + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + Launchboard + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.h b/examples/CatDealsCollectionView/Sample/ItemNode.h new file mode 100644 index 0000000000..eee5dc5dc9 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ItemNode.h @@ -0,0 +1,21 @@ +/* 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 +#import "ItemViewModel.h" + +@interface ItemNode : ASCellNode + +- initWithViewModel:(ItemViewModel *)viewModel; ++ (CGSize)sizeForWidth:(CGFloat)width; ++ (CGSize)preferredViewSize; + +@end diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m new file mode 100644 index 0000000000..15608a7620 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -0,0 +1,361 @@ +/* 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 "ItemNode.h" +#import "ItemStyles.h" +#import "PlaceholderNetworkImageNode.h" + +const CGFloat kFixedLabelsAreaHeight = 96.0; +const CGFloat kDesignWidth = 320.0; +const CGFloat kDesignHeight = 299.0; +const CGFloat kBadgeHeight = 34.0; +const CGFloat kSoldOutGBHeight = 50.0; + +@interface ItemNode() + +@property (nonatomic, strong) ItemViewModel *viewModel; + +@property (nonatomic, strong) PlaceholderNetworkImageNode *dealImageView; + +@property (nonatomic, strong) ASTextNode *titleLabel; +@property (nonatomic, strong) ASTextNode *firstInfoLabel; +@property (nonatomic, strong) ASTextNode *distanceLabel; +@property (nonatomic, strong) ASTextNode *secondInfoLabel; +@property (nonatomic, strong) ASTextNode *originalPriceLabel; +@property (nonatomic, strong) ASTextNode *finalPriceLabel; +@property (nonatomic, strong) ASTextNode *soldOutLabelFlat; +@property (nonatomic, strong) ASDisplayNode *soldOutLabelBackground; +@property (nonatomic, strong) ASDisplayNode *soldOutOverlay; +@property (nonatomic, strong) ASTextNode *badge; + +@end + +@implementation ItemNode + +- (instancetype)initWithViewModel:(ItemViewModel *)viewModel +{ + self = [super init]; + if (self != nil) { + _viewModel = viewModel; + [self setup]; + [self updateLabels]; + [self updateBackgroundColor]; + + } + return self; +} + ++ (BOOL)isRTL { + return [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; +} + +- (void)setup { + self.dealImageView = [[PlaceholderNetworkImageNode alloc] init]; + self.dealImageView.delegate = self; + self.dealImageView.placeholderEnabled = YES; + self.dealImageView.placeholderImageOverride = [ItemStyles placeholderImage]; + self.dealImageView.defaultImage = [ItemStyles placeholderImage]; + self.dealImageView.contentMode = UIViewContentModeScaleToFill; + self.dealImageView.placeholderFadeDuration = 0.0; + self.dealImageView.layerBacked = YES; + + self.titleLabel = [[ASTextNode alloc] init]; + self.titleLabel.maximumNumberOfLines = 2; + self.titleLabel.alignSelf = ASStackLayoutAlignSelfStart; + self.titleLabel.flexGrow = YES; + self.titleLabel.layerBacked = YES; + + self.firstInfoLabel = [[ASTextNode alloc] init]; + self.firstInfoLabel.maximumNumberOfLines = 1; + self.firstInfoLabel.layerBacked = YES; + + self.secondInfoLabel = [[ASTextNode alloc] init]; + self.secondInfoLabel.maximumNumberOfLines = 1; + self.secondInfoLabel.layerBacked = YES; + + self.distanceLabel = [[ASTextNode alloc] init]; + self.distanceLabel.maximumNumberOfLines = 1; + self.distanceLabel.layerBacked = YES; + + self.originalPriceLabel = [[ASTextNode alloc] init]; + self.originalPriceLabel.maximumNumberOfLines = 1; + self.originalPriceLabel.layerBacked = YES; + + self.finalPriceLabel = [[ASTextNode alloc] init]; + self.finalPriceLabel.maximumNumberOfLines = 1; + self.finalPriceLabel.layerBacked = YES; + + self.badge = [[ASTextNode alloc] init]; + self.badge.hidden = YES; + self.badge.layerBacked = YES; + + self.soldOutLabelFlat = [[ASTextNode alloc] init]; + self.soldOutLabelFlat.layerBacked = YES; + + self.soldOutLabelBackground = [[ASDisplayNode alloc] init]; + self.soldOutLabelBackground.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kSoldOutGBHeight))); + self.soldOutLabelBackground.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9]; + self.soldOutLabelBackground.flexGrow = YES; + self.soldOutLabelBackground.layerBacked = YES; + + self.soldOutOverlay = [[ASDisplayNode alloc] init]; + self.soldOutOverlay.flexGrow = YES; + self.soldOutOverlay.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5]; + self.soldOutOverlay.layerBacked = YES; + + [self addSubnode:self.dealImageView]; + [self addSubnode:self.titleLabel]; + [self addSubnode:self.firstInfoLabel]; + [self addSubnode:self.secondInfoLabel]; + [self addSubnode:self.originalPriceLabel]; + [self addSubnode:self.finalPriceLabel]; + [self addSubnode:self.distanceLabel]; + [self addSubnode:self.badge]; + + [self addSubnode:self.soldOutLabelBackground]; + [self addSubnode:self.soldOutLabelFlat]; + [self addSubnode:self.soldOutOverlay]; + self.soldOutOverlay.hidden = YES; + self.soldOutLabelBackground.hidden = YES; + self.soldOutLabelFlat.hidden = YES; + + [self addSubnode:self.soldOutOverlay]; + + if ([ItemNode isRTL]) { + self.titleLabel.alignSelf = ASStackLayoutAlignSelfEnd; + self.firstInfoLabel.alignSelf = ASStackLayoutAlignSelfEnd; + self.distanceLabel.alignSelf = ASStackLayoutAlignSelfEnd; + self.secondInfoLabel.alignSelf = ASStackLayoutAlignSelfEnd; + self.originalPriceLabel.alignSelf = ASStackLayoutAlignSelfStart; + self.finalPriceLabel.alignSelf = ASStackLayoutAlignSelfStart; + } else { + self.firstInfoLabel.alignSelf = ASStackLayoutAlignSelfStart; + self.distanceLabel.alignSelf = ASStackLayoutAlignSelfStart; + self.secondInfoLabel.alignSelf = ASStackLayoutAlignSelfStart; + self.originalPriceLabel.alignSelf = ASStackLayoutAlignSelfEnd; + self.finalPriceLabel.alignSelf = ASStackLayoutAlignSelfEnd; + } +} + +- (void)updateLabels { + // Set Title text + if (self.viewModel.titleText) { + self.titleLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.titleText attributes:[ItemStyles titleStyle]]; + } + if (self.viewModel.firstInfoText) { + self.firstInfoLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.firstInfoText attributes:[ItemStyles subtitleStyle]]; + } + + if (self.viewModel.secondInfoText) { + self.secondInfoLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.secondInfoText attributes:[ItemStyles secondInfoStyle]]; + } + if (self.viewModel.originalPriceText) { + self.originalPriceLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.originalPriceText attributes:[ItemStyles originalPriceStyle]]; + } + if (self.viewModel.finalPriceText) { + self.finalPriceLabel.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.finalPriceText attributes:[ItemStyles finalPriceStyle]]; + } + if (self.viewModel.distanceLabelText) { + NSString *format = [ItemNode isRTL] ? @"%@ •" : @"• %@"; + NSString *distanceText = [NSString stringWithFormat:format, self.viewModel.distanceLabelText]; + + self.distanceLabel.attributedString = [[NSAttributedString alloc] initWithString:distanceText attributes:[ItemStyles distanceStyle]]; + } + + BOOL isSoldOut = self.viewModel.soldOutText != nil; + + if (isSoldOut) { + NSString *soldOutText = self.viewModel.soldOutText; + self.soldOutLabelFlat.attributedString = [[NSAttributedString alloc] initWithString:soldOutText attributes:[ItemStyles soldOutStyle]]; + } + self.soldOutOverlay.hidden = !isSoldOut; + self.soldOutLabelFlat.hidden = !isSoldOut; + self.soldOutLabelBackground.hidden = !isSoldOut; + + BOOL hasBadge = self.viewModel.badgeText != nil; + if (hasBadge) { + self.badge.attributedString = [[NSAttributedString alloc] initWithString:self.viewModel.badgeText attributes:[ItemStyles badgeStyle]]; + self.badge.backgroundColor = [ItemStyles badgeColor]; + } + self.badge.hidden = !hasBadge; +} + +- (void)updateBackgroundColor +{ + if (self.highlighted) { + self.backgroundColor = [[UIColor grayColor] colorWithAlphaComponent:0.3]; + } else if (self.selected) { + self.backgroundColor = [UIColor lightGrayColor]; + } else { + self.backgroundColor = [UIColor whiteColor]; + } +} + +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image { +} + +- (void)setSelected:(BOOL)selected +{ + [super setSelected:selected]; + [self updateBackgroundColor]; +} + +- (void)setHighlighted:(BOOL)highlighted +{ + [super setHighlighted:highlighted]; + [self updateBackgroundColor]; +} + +#pragma mark - superclass + +- (void)displayWillStart { + [super displayWillStart]; + [self fetchData]; +} + +- (void)fetchData { + [super fetchData]; + if (self.viewModel) { + [self loadImage]; + } +} + + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { + + ASLayoutSpec *textSpec = [self textSpec]; + ASLayoutSpec *imageSpec = [self imageSpecWithSize:constrainedSize]; + ASOverlayLayoutSpec *soldOutOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imageSpec overlay:[self soldOutLabelSpec]]; + + NSArray *stackChildren = @[soldOutOverImage, textSpec]; + + ASStackLayoutSpec *mainStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch children:stackChildren]; + + ASOverlayLayoutSpec *soldOutOverlay = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:mainStack overlay:self.soldOutOverlay]; + + return soldOutOverlay; +} + +- (ASLayoutSpec *)textSpec { + CGFloat kInsetHorizontal = 16.0; + CGFloat kInsetTop = 6.0; + CGFloat kInsetBottom = 0.0; + + UIEdgeInsets textInsets = UIEdgeInsetsMake(kInsetTop, kInsetHorizontal, kInsetBottom, kInsetHorizontal); + + ASLayoutSpec *verticalSpacer = [[ASLayoutSpec alloc] init]; + verticalSpacer.flexGrow = YES; + + ASLayoutSpec *horizontalSpacer1 = [[ASLayoutSpec alloc] init]; + horizontalSpacer1.flexGrow = YES; + + ASLayoutSpec *horizontalSpacer2 = [[ASLayoutSpec alloc] init]; + horizontalSpacer2.flexGrow = YES; + + NSArray *info1Children = @[self.firstInfoLabel, self.distanceLabel, horizontalSpacer1, self.originalPriceLabel]; + NSArray *info2Children = @[self.secondInfoLabel, horizontalSpacer2, self.finalPriceLabel]; + if ([ItemNode isRTL]) { + info1Children = [[info1Children reverseObjectEnumerator] allObjects]; + info2Children = [[info2Children reverseObjectEnumerator] allObjects]; + } + + ASStackLayoutSpec *info1Stack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:1.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsBaselineLast children:info1Children]; + + ASStackLayoutSpec *info2Stack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentCenter alignItems:ASStackLayoutAlignItemsBaselineLast children:info2Children]; + + ASStackLayoutSpec *textStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical spacing:0.0 justifyContent:ASStackLayoutJustifyContentEnd alignItems:ASStackLayoutAlignItemsStretch children:@[self.titleLabel, verticalSpacer, info1Stack, info2Stack]]; + + ASInsetLayoutSpec *textWrapper = [ASInsetLayoutSpec insetLayoutSpecWithInsets:textInsets child:textStack]; + textWrapper.flexGrow = YES; + + return textWrapper; +} + +- (ASLayoutSpec *)imageSpecWithSize:(ASSizeRange)constrainedSize { + CGFloat imageRatio = [self imageRatioFromSize:constrainedSize.max]; + + ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:self.dealImageView]; + + self.badge.layoutPosition = CGPointMake(0, constrainedSize.max.height - kFixedLabelsAreaHeight - kBadgeHeight); + self.badge.sizeRange = ASRelativeSizeRangeMake(ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(0), ASRelativeDimensionMakeWithPoints(kBadgeHeight)), ASRelativeSizeMake(ASRelativeDimensionMakeWithPercent(1), ASRelativeDimensionMakeWithPoints(kBadgeHeight))); + ASStaticLayoutSpec *badgePosition = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.badge]]; + + ASOverlayLayoutSpec *badgeOverImage = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imagePlace overlay:badgePosition]; + badgeOverImage.flexGrow = YES; + + return badgeOverImage; +} + +- (ASLayoutSpec *)soldOutLabelSpec { + ASCenterLayoutSpec *centerSoldOutLabel = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionMinimumXY child:self.soldOutLabelFlat]; + ASStaticLayoutSpec *soldOutBG = [ASStaticLayoutSpec staticLayoutSpecWithChildren:@[self.soldOutLabelBackground]]; + ASCenterLayoutSpec *centerSoldOut = [ASCenterLayoutSpec centerLayoutSpecWithCenteringOptions:ASCenterLayoutSpecCenteringXY sizingOptions:ASCenterLayoutSpecSizingOptionDefault child:soldOutBG]; + ASBackgroundLayoutSpec *soldOutLabelOverBackground = [ASBackgroundLayoutSpec backgroundLayoutSpecWithChild:centerSoldOutLabel background:centerSoldOut]; + return soldOutLabelOverBackground; +} + + ++ (CGSize)sizeForWidth:(CGFloat)width { + CGFloat height = [self scaledHeightForPreferredSize:[self preferredViewSize] scaledWidth:width]; + return CGSizeMake(width, height); +} + + ++ (CGSize)preferredViewSize { + return CGSizeMake(kDesignWidth, kDesignHeight); +} + ++ (CGFloat)scaledHeightForPreferredSize:(CGSize)preferredSize scaledWidth:(CGFloat)scaledWidth { + CGFloat scale = scaledWidth / kDesignWidth; + CGFloat scaledHeight = ceilf(scale * (kDesignHeight - kFixedLabelsAreaHeight)) + kFixedLabelsAreaHeight; + + return scaledHeight; +} + +#pragma mark - view operations + +- (CGFloat)imageRatioFromSize:(CGSize)size { + CGFloat imageHeight = size.height - kFixedLabelsAreaHeight; + CGFloat imageRatio = imageHeight / size.width; + + return imageRatio; +} + +- (CGSize)imageSize { + if (!CGSizeEqualToSize(self.dealImageView.frame.size, CGSizeZero)) { + return self.dealImageView.frame.size; + } else if (!CGSizeEqualToSize(self.calculatedSize, CGSizeZero)) { + CGFloat imageRatio = [self imageRatioFromSize:self.calculatedSize]; + CGFloat imageWidth = self.calculatedSize.width; + return CGSizeMake(imageWidth, imageRatio * imageWidth); + } else { + return CGSizeZero; + } +} + +- (void)loadImage { + CGSize imageSize = [self imageSize]; + if (CGSizeEqualToSize(CGSizeZero, imageSize)) { + return; + } + + NSURL *url = [self.viewModel imageURLWithSize:imageSize]; + + // if we're trying to set the deal image to what it already was, skip the work + if ([[url absoluteString] isEqualToString:[self.dealImageView.URL absoluteString]]) { + return; + } + + // Clear the flag that says we've loaded our image + [self.dealImageView setURL:url]; +} + +@end diff --git a/examples/CatDealsCollectionView/Sample/ItemStyles.h b/examples/CatDealsCollectionView/Sample/ItemStyles.h new file mode 100644 index 0000000000..67879f3925 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ItemStyles.h @@ -0,0 +1,23 @@ +// +// ItemStyles.h +// Sample +// +// Created by Samuel Stow on 12/30/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import +#import + +@interface ItemStyles : NSObject ++ (NSDictionary *)titleStyle; ++ (NSDictionary *)subtitleStyle; ++ (NSDictionary *)distanceStyle; ++ (NSDictionary *)secondInfoStyle; ++ (NSDictionary *)originalPriceStyle; ++ (NSDictionary *)finalPriceStyle; ++ (NSDictionary *)soldOutStyle; ++ (NSDictionary *)badgeStyle; ++ (UIColor *)badgeColor; ++ (UIImage *)placeholderImage; +@end diff --git a/examples/CatDealsCollectionView/Sample/ItemStyles.m b/examples/CatDealsCollectionView/Sample/ItemStyles.m new file mode 100644 index 0000000000..3b39068452 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ItemStyles.m @@ -0,0 +1,93 @@ +// +// ItemStyles.m +// Sample +// +// Created by Samuel Stow on 12/30/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ItemStyles.h" + +const CGFloat kTitleFontSize = 20.0; +const CGFloat kInfoFontSize = 14.0; + +UIColor *kTitleColor; +UIColor *kInfoColor; +UIColor *kFinalPriceColor; +UIFont *kTitleFont; +UIFont *kInfoFont; + +@implementation ItemStyles + ++ (void)initialize { + if (self == [ItemStyles class]) { + kTitleColor = [UIColor darkGrayColor]; + kInfoColor = [UIColor grayColor]; + kFinalPriceColor = [UIColor greenColor]; + kTitleFont = [UIFont boldSystemFontOfSize:kTitleFontSize]; + kInfoFont = [UIFont systemFontOfSize:kInfoFontSize]; + } +} + ++ (NSDictionary *)titleStyle { + // Title Label + return @{ NSFontAttributeName:kTitleFont, + NSForegroundColorAttributeName:kTitleColor }; +} + ++ (NSDictionary *)subtitleStyle { + // First Subtitle + return @{ NSFontAttributeName:kInfoFont, + NSForegroundColorAttributeName:kInfoColor }; +} + ++ (NSDictionary *)distanceStyle { + // Distance Label + return @{ NSFontAttributeName:kInfoFont, + NSForegroundColorAttributeName:kInfoColor}; +} + ++ (NSDictionary *)secondInfoStyle { + // Second Subtitle + return @{ NSFontAttributeName:kInfoFont, + NSForegroundColorAttributeName:kInfoColor}; +} + ++ (NSDictionary *)originalPriceStyle { + // Original price + return @{ NSFontAttributeName:kInfoFont, + NSForegroundColorAttributeName:kInfoColor, + NSStrikethroughStyleAttributeName:@(NSUnderlineStyleSingle)}; +} + ++ (NSDictionary *)finalPriceStyle { + // Discounted / Claimable price label + return @{ NSFontAttributeName:kTitleFont, + NSForegroundColorAttributeName:kFinalPriceColor}; +} + ++ (NSDictionary *)soldOutStyle { + // Setup Sold Out Label + return @{ NSFontAttributeName:kTitleFont, + NSForegroundColorAttributeName:kTitleColor}; +} + ++ (NSDictionary *)badgeStyle { + // Setup Sold Out Label + return @{ NSFontAttributeName:kTitleFont, + NSForegroundColorAttributeName:[UIColor whiteColor]}; +} + ++ (UIColor *)badgeColor { + return [[UIColor purpleColor] colorWithAlphaComponent:0.4]; +} + ++ (UIImage *)placeholderImage { + static UIImage *__catFace = nil; + if (!__catFace) { + __catFace = [UIImage imageNamed:@"cat_face"]; + } + return __catFace; +} + +@end diff --git a/examples/CatDealsCollectionView/Sample/ItemViewModel.h b/examples/CatDealsCollectionView/Sample/ItemViewModel.h new file mode 100644 index 0000000000..1d0403331c --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ItemViewModel.h @@ -0,0 +1,27 @@ +// +// GPDealViewModel.h +// Groupon +// +// Created by Samuel Stow on 12/29/15. +// Copyright © 2015 Groupon Inc. All rights reserved. +// + +#import +#import + +@interface ItemViewModel : NSObject + ++ (instancetype)randomItem; + +@property (nonatomic, copy) NSString *titleText; +@property (nonatomic, copy) NSString *firstInfoText; +@property (nonatomic, copy) NSString *secondInfoText; +@property (nonatomic, copy) NSString *originalPriceText; +@property (nonatomic, copy) NSString *finalPriceText; +@property (nonatomic, copy) NSString *soldOutText; +@property (nonatomic, copy) NSString *distanceLabelText; +@property (nonatomic, copy) NSString *badgeText; + +- (NSURL *)imageURLWithSize:(CGSize)size; + +@end diff --git a/examples/CatDealsCollectionView/Sample/ItemViewModel.m b/examples/CatDealsCollectionView/Sample/ItemViewModel.m new file mode 100644 index 0000000000..6ad4c1d13e --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ItemViewModel.m @@ -0,0 +1,99 @@ +// +// GPDealViewModel.m +// Groupon +// +// Created by Samuel Stow on 12/29/15. +// Copyright © 2015 Groupon Inc. All rights reserved. +// + +#import "ItemViewModel.h" + +NSArray *titles; +NSArray *firstInfos; +NSArray *badges; + +@interface ItemViewModel() + +@property (nonatomic, assign) NSInteger catNumber; +@property (nonatomic, assign) NSInteger labelNumber; + +@end + +@implementation ItemViewModel + ++ (instancetype)randomItem { + return [[ItemViewModel alloc] init]; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _titleText = [self randomObjectFromArray:titles]; + _firstInfoText = [self randomObjectFromArray:firstInfos]; + _secondInfoText = [NSString stringWithFormat:@"%zd+ bought", [self randomNumberInRange:5 to:6000]]; + _originalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:40 to:90]]; + _finalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:5 to:30]]; + BOOL isSoldOut = arc4random() % 5 == 0; + _soldOutText = isSoldOut ? @"SOLD OUT" : nil; + _distanceLabelText = [NSString stringWithFormat:@"%zd mi", [self randomNumberInRange:1 to:20]]; + BOOL isBadged = arc4random() % 2 == 0; + if (isBadged) { + _badgeText = [self randomObjectFromArray:badges]; + } + _catNumber = [self randomNumberInRange:1 to:10]; + _labelNumber = [self randomNumberInRange:1 to:10000]; + + } + return self; +} + +- (NSURL *)imageURLWithSize:(CGSize)size { + NSString *imageText = [NSString stringWithFormat:@"Fun cat pic %zd", self.labelNumber]; + NSString *urlString = [NSString stringWithFormat:@"http://lorempixel.com/%zd/%zd/cats/%zd/%@", + (NSInteger)roundl(size.width), + (NSInteger)roundl(size.height), self.catNumber, imageText]; + urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + + return [NSURL URLWithString:urlString]; +} + +// titles courtesy of http://www.catipsum.com/ ++ (void)initialize { + titles = @[@"Leave fur on owners clothes intrigued by the shower", + @"Meowwww", + @"Immediately regret falling into bathtub stare out the window", + @"Jump launch to pounce upon little yarn mouse, bare fangs at toy run hide in litter box until treats are fed", + @"Sleep nap", + @"Lick butt", + @"Chase laser lick arm hair present belly, scratch hand when stroked"]; + firstInfos = @[@"Kitty Shop", + @"Cat's r us", + @"Fantastic Felines", + @"The Cat Shop", + @"Cat in a hat", + @"Cat-tastic" + ]; + + badges = @[@"ADORABLE", + @"BOUNCES", + @"HATES CUCUMBERS", + @"SCRATCHY" + ]; +} + + +- (id)randomObjectFromArray:(NSArray *)strings +{ + u_int32_t ipsumCount = (u_int32_t)[strings count]; + u_int32_t location = arc4random_uniform(ipsumCount); + + return strings[location]; +} + +- (uint32_t)randomNumberInRange:(uint32_t)start to:(uint32_t)end { + + return start + arc4random_uniform(end - start); +} + + +@end diff --git a/examples/CatDealsCollectionView/Sample/Launchboard.storyboard b/examples/CatDealsCollectionView/Sample/Launchboard.storyboard new file mode 100644 index 0000000000..673e0f7e68 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/Launchboard.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.h b/examples/CatDealsCollectionView/Sample/LoadingNode.h new file mode 100644 index 0000000000..87c182c7a9 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.h @@ -0,0 +1,15 @@ +// +// LoadingNode.h +// Sample +// +// Created by Samuel Stow on 1/9/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface LoadingNode : ASCellNode + ++ (CGFloat)desiredHeightForWidth:(CGFloat)width; + +@end diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.m b/examples/CatDealsCollectionView/Sample/LoadingNode.m new file mode 100644 index 0000000000..1bb3977b9a --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.m @@ -0,0 +1,68 @@ +// +// LoadingNode.m +// Sample +// +// Created by Samuel Stow on 1/9/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "LoadingNode.h" +#import +#import + +#import +#import + +static CGFloat kFixedHeight = 200.0f; + +@interface LoadingNode () +{ + ASDisplayNode *_loadingSpinner; +} + +@end + +@implementation LoadingNode + + +#pragma mark - +#pragma mark ASCellNode. + ++ (CGFloat)desiredHeightForWidth:(CGFloat)width { + return kFixedHeight; +} + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _loadingSpinner = [[ASDisplayNode alloc] initWithViewBlock:^UIView * _Nonnull{ + UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; + [spinner startAnimating]; + return spinner; + }]; + _loadingSpinner.preferredFrameSize = CGSizeMake(50, 50); + + + // add it as a subnode, and we're done + [self addSubnode:_loadingSpinner]; + + return self; +} + +- (void)layout { + [super layout]; +} + +- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize +{ + ASCenterLayoutSpec *centerSpec = [[ASCenterLayoutSpec alloc] init]; + centerSpec.centeringOptions = ASCenterLayoutSpecCenteringXY; + centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionDefault; + centerSpec.child = _loadingSpinner; + + return centerSpec; +} + +@end \ No newline at end of file diff --git a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h new file mode 100644 index 0000000000..e9018a3784 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h @@ -0,0 +1,15 @@ +// +// PlacholderNetworkImageNode.h +// Sample +// +// Created by Samuel Stow on 1/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import + +@interface PlaceholderNetworkImageNode : ASNetworkImageNode + +@property (nonatomic, strong) UIImage *placeholderImageOverride; + +@end diff --git a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m new file mode 100644 index 0000000000..c68607f8bb --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m @@ -0,0 +1,18 @@ +// +// PlacholderNetworkImageNode.m +// Sample +// +// Created by Samuel Stow on 1/14/16. +// Copyright © 2016 Facebook. All rights reserved. +// + +#import "PlaceholderNetworkImageNode.h" + +@implementation PlaceholderNetworkImageNode + +- (UIImage *)placeholderImage { + return self.placeholderImageOverride; +} + + +@end diff --git a/examples/CatDealsCollectionView/Sample/PresentingViewController.h b/examples/CatDealsCollectionView/Sample/PresentingViewController.h new file mode 100644 index 0000000000..bd2308fab3 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/PresentingViewController.h @@ -0,0 +1,13 @@ +// +// PresentingViewController.h +// Sample +// +// Created by Tom King on 12/23/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import + +@interface PresentingViewController : UIViewController + +@end diff --git a/examples/CatDealsCollectionView/Sample/PresentingViewController.m b/examples/CatDealsCollectionView/Sample/PresentingViewController.m new file mode 100644 index 0000000000..49c65e6906 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/PresentingViewController.m @@ -0,0 +1,30 @@ +// +// PresentingViewController.m +// Sample +// +// Created by Tom King on 12/23/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "PresentingViewController.h" +#import "ViewController.h" + +@interface PresentingViewController () + +@end + +@implementation PresentingViewController + +- (void)viewDidLoad +{ + [super viewDidLoad]; + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Details" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)]; +} + +- (void)pushNewViewController +{ + ViewController *controller = [[ViewController alloc] init]; + [self.navigationController pushViewController:controller animated:true]; +} + +@end diff --git a/examples/CatDealsCollectionView/Sample/ViewController.h b/examples/CatDealsCollectionView/Sample/ViewController.h new file mode 100644 index 0000000000..d0e9200d88 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ViewController.h @@ -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 + +@interface ViewController : UIViewController + +@end diff --git a/examples/CatDealsCollectionView/Sample/ViewController.m b/examples/CatDealsCollectionView/Sample/ViewController.m new file mode 100644 index 0000000000..31a1e35435 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/ViewController.m @@ -0,0 +1,244 @@ +/* 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 +#import "ItemNode.h" +#import "BlurbNode.h" +#import "LoadingNode.h" + +static const NSTimeInterval kWebResponseDelay = 1.0; +static const BOOL kSimulateWebResponse = YES; +static const NSInteger kBatchSize = 20; + +static const CGFloat kHorizontalSectionPadding = 10.0f; +static const CGFloat kVerticalSectionPadding = 20.0f; + +@interface ViewController () +{ + ASCollectionView *_collectionView; + NSMutableArray *_data; +} + +@end + + +@implementation ViewController + +#pragma mark - +#pragma mark UIViewController. + +- (instancetype)init +{ + self = [super init]; + + if (self) { + + self.title = @"Cat Deals"; + + UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; + + _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; + _collectionView.asyncDataSource = self; + _collectionView.asyncDelegate = self; + _collectionView.backgroundColor = [UIColor grayColor]; + _collectionView.leadingScreensForBatching = 2; + + ASRangeTuningParameters fetchDataTuning; + fetchDataTuning.leadingBufferScreenfuls = 2; + fetchDataTuning.trailingBufferScreenfuls = 1; + [_collectionView setTuningParameters:fetchDataTuning forRangeType:ASLayoutRangeTypeFetchData]; + + ASRangeTuningParameters preRenderTuning; + preRenderTuning.leadingBufferScreenfuls = 1; + preRenderTuning.trailingBufferScreenfuls = 0.5; + [_collectionView setTuningParameters:preRenderTuning forRangeType:ASLayoutRangeTypeDisplay]; + + [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; + [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; + + _data = [[NSMutableArray alloc] init]; + + self.navigationItem.leftItemsSupplementBackButton = YES; + self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)]; + } + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubview:_collectionView]; + [self fetchMoreCatsWithCompletion:nil]; +} + +- (void)fetchMoreCatsWithCompletion:(void (^)(BOOL))completion { + if (kSimulateWebResponse) { + __weak typeof(self) weakSelf = self; + void(^mockWebService)() = ^{ + NSLog(@"ViewController \"got data from a web service\""); + ViewController *strongSelf = weakSelf; + if (strongSelf != nil) + { + NSLog(@"ViewController is not nil"); + [strongSelf appendMoreItems:kBatchSize completion:completion]; + NSLog(@"ViewController finished updating collectionView"); + } + else { + NSLog(@"ViewController is nil - won't update collectionView"); + } + }; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kWebResponseDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService); + } else { + [self appendMoreItems:kBatchSize completion:completion]; + } +} + +- (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))completion { + NSArray *newData = [self getMoreData:numberOfNewItems]; + dispatch_async(dispatch_get_main_queue(), ^{ + [_collectionView performBatchUpdates:^{ + [_data addObjectsFromArray:newData]; + NSArray *addedIndexPaths = [self indexPathsForObjects:newData]; + [_collectionView insertItemsAtIndexPaths:addedIndexPaths]; + } completion:completion]; + }); +} + +- (NSArray *)getMoreData:(NSInteger)count { + NSMutableArray *data = [NSMutableArray array]; + for (int i = 0; i < count; i++) { + [data addObject:[ItemViewModel randomItem]]; + } + return data; +} + +- (NSArray *)indexPathsForObjects:(NSArray *)data { + NSMutableArray *indexPaths = [NSMutableArray array]; + NSInteger section = 0; + for (ItemViewModel *viewModel in data) { + NSInteger item = [_data indexOfObject:viewModel]; + NSAssert(item < [_data count] && item != NSNotFound, @"Item should be in _data"); + [indexPaths addObject:[NSIndexPath indexPathForItem:item inSection:section]]; + } + return indexPaths; +} + +- (void)viewWillLayoutSubviews +{ + _collectionView.frame = self.view.bounds; +} + + +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { + [_collectionView.collectionViewLayout invalidateLayout]; +} + +- (BOOL)prefersStatusBarHidden +{ + return YES; +} + +- (void)reloadTapped +{ + [_collectionView reloadData]; +} + +#pragma mark - +#pragma mark ASCollectionView data source. + +- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + ItemViewModel *viewModel = _data[indexPath.item]; + return [[ItemNode alloc] initWithViewModel:viewModel]; +} + +- (ASCellNode *)collectionView:(UICollectionView *)collectionView nodeForSupplementaryElementOfKind:(nonnull NSString *)kind atIndexPath:(nonnull NSIndexPath *)indexPath { + if ([kind isEqualToString:UICollectionElementKindSectionHeader] && indexPath.section == 0) { + return [[BlurbNode alloc] init]; + } else if ([kind isEqualToString:UICollectionElementKindSectionFooter] && indexPath.section == 0) { + return [[LoadingNode alloc] init]; + } + return nil; +} + +- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section { + if (section == 0) { + CGFloat width = CGRectGetWidth(self.view.frame) - 2 * kHorizontalSectionPadding; + return CGSizeMake(width, [BlurbNode desiredHeightForWidth:width]); + } + return CGSizeZero; +} + +- (CGSize)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section { + if (section == 0) { + CGFloat width = CGRectGetWidth(self.view.frame); + return CGSizeMake(width, [LoadingNode desiredHeightForWidth:width]); + } + return CGSizeZero; +} + +- (ASSizeRange)collectionView:(ASCollectionView *)collectionView constrainedSizeForNodeAtIndexPath:(NSIndexPath *)indexPath { + CGFloat collectionViewWidth = CGRectGetWidth(self.view.frame) - 2 * kHorizontalSectionPadding; + CGFloat oneItemWidth = [ItemNode preferredViewSize].width; + NSInteger numColumns = floor(collectionViewWidth / oneItemWidth); + // Number of columns should be at least 1 + numColumns = MAX(1, numColumns); + + CGFloat totalSpaceBetweenColumns = (numColumns - 1) * kHorizontalSectionPadding; + CGFloat itemWidth = ((collectionViewWidth - totalSpaceBetweenColumns) / numColumns); + CGSize itemSize = [ItemNode sizeForWidth:itemWidth]; + return ASSizeRangeMake(itemSize, itemSize); +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + return [_data count]; +} + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView +{ + return 1; +} + +- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView +{ + // lock the data source + // The data source should not be change until it is unlocked. +} + +- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView +{ + // unlock the data source to enable data source updating. +} + +- (void)collectionView:(UICollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context +{ + NSLog(@"fetch additional content"); + [self fetchMoreCatsWithCompletion:^(BOOL finished){ + [context completeBatchFetching:YES]; + }]; +} + +- (UIEdgeInsets)collectionView:(ASCollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { + return UIEdgeInsetsMake(kVerticalSectionPadding, kHorizontalSectionPadding, kVerticalSectionPadding, kHorizontalSectionPadding); +} + +-(void)dealloc +{ + NSLog(@"ViewController is deallocing"); +} + +@end diff --git a/examples/CatDealsCollectionView/Sample/main.m b/examples/CatDealsCollectionView/Sample/main.m new file mode 100644 index 0000000000..592423d8f6 --- /dev/null +++ b/examples/CatDealsCollectionView/Sample/main.m @@ -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 +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata b/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..7b5a2f3050 --- /dev/null +++ b/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + +