diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj new file mode 100644 index 000000000..5927a1d96 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj @@ -0,0 +1,523 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 3A2362FB1E2D33A0007E08F1 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2362FA1E2D33A0007E08F1 /* Date.swift */; }; + 3A7A28D91E2F7410003E2B8D /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7A28D81E2F7410003E2B8D /* UIImage.swift */; }; + 3AB33F5E1E1F94530039F711 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F5D1E1F94530039F711 /* AppDelegate.swift */; }; + 3AB33F651E1F94530039F711 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3AB33F641E1F94530039F711 /* Assets.xcassets */; }; + 3AB33F681E1F94530039F711 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3AB33F661E1F94530039F711 /* LaunchScreen.storyboard */; }; + 3AB33F761E1F9C330039F711 /* PhotoFeedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F751E1F9C330039F711 /* PhotoFeedTableViewController.swift */; }; + 3AB33F781E1F9C400039F711 /* PhotoFeedTableNodeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F771E1F9C400039F711 /* PhotoFeedTableNodeController.swift */; }; + 3AB33F7B1E1F9E630039F711 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F7A1E1F9E630039F711 /* UIColor.swift */; }; + 3AB33F811E1FDE100039F711 /* Webservice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F801E1FDE100039F711 /* Webservice.swift */; }; + 3AB33F831E20E81E0039F711 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F821E20E81E0039F711 /* Constants.swift */; }; + 3AB33F861E20E9B10039F711 /* PhotoFeedModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F851E20E9B10039F711 /* PhotoFeedModel.swift */; }; + 3AB33F881E20ED460039F711 /* PhotoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F871E20ED460039F711 /* PhotoModel.swift */; }; + 3AB33F8C1E2106F30039F711 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F8B1E2106F30039F711 /* URL.swift */; }; + 3AB33F961E2269D40039F711 /* PopularPageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F951E2269D40039F711 /* PopularPageModel.swift */; }; + 3AB33F981E22A0080039F711 /* PX500Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F971E22A0080039F711 /* PX500Convenience.swift */; }; + 3AB33F9E1E22D9DB0039F711 /* PhotoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33F9D1E22D9DB0039F711 /* PhotoTableViewCell.swift */; }; + 3AB33FA21E230A160039F711 /* NetworkImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33FA11E230A160039F711 /* NetworkImageView.swift */; }; + 3AB33FA41E2337850039F711 /* PhotoTableNodeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB33FA31E2337850039F711 /* PhotoTableNodeCell.swift */; }; + 7E438240D2C4026931D60594 /* Pods_ASDKgram_Swift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D7D664E4FF432C4AE232A56 /* Pods_ASDKgram_Swift.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 019E984FADA258377FC6B2D8 /* Pods-ASDKgram-Swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ASDKgram-Swift.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift.debug.xcconfig"; sourceTree = ""; }; + 3A2362FA1E2D33A0007E08F1 /* Date.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; + 3A7A28D81E2F7410003E2B8D /* UIImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; + 3AB33F5A1E1F94520039F711 /* ASDKgram-Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ASDKgram-Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3AB33F5D1E1F94530039F711 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 3AB33F641E1F94530039F711 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 3AB33F671E1F94530039F711 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 3AB33F691E1F94530039F711 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 3AB33F751E1F9C330039F711 /* PhotoFeedTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoFeedTableViewController.swift; sourceTree = ""; }; + 3AB33F771E1F9C400039F711 /* PhotoFeedTableNodeController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoFeedTableNodeController.swift; sourceTree = ""; }; + 3AB33F7A1E1F9E630039F711 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; + 3AB33F801E1FDE100039F711 /* Webservice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Webservice.swift; sourceTree = ""; }; + 3AB33F821E20E81E0039F711 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 3AB33F851E20E9B10039F711 /* PhotoFeedModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoFeedModel.swift; sourceTree = ""; }; + 3AB33F871E20ED460039F711 /* PhotoModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoModel.swift; sourceTree = ""; }; + 3AB33F8B1E2106F30039F711 /* URL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; + 3AB33F951E2269D40039F711 /* PopularPageModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopularPageModel.swift; sourceTree = ""; }; + 3AB33F971E22A0080039F711 /* PX500Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PX500Convenience.swift; sourceTree = ""; }; + 3AB33F9D1E22D9DB0039F711 /* PhotoTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoTableViewCell.swift; sourceTree = ""; }; + 3AB33FA11E230A160039F711 /* NetworkImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageView.swift; sourceTree = ""; }; + 3AB33FA31E2337850039F711 /* PhotoTableNodeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoTableNodeCell.swift; sourceTree = ""; }; + 4D7D664E4FF432C4AE232A56 /* Pods_ASDKgram_Swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ASDKgram_Swift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A3A86E74A8C3F06D7688AACB /* Pods-ASDKgram-Swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ASDKgram-Swift.release.xcconfig"; path = "Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3AB33F571E1F94520039F711 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7E438240D2C4026931D60594 /* Pods_ASDKgram_Swift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3AB33F511E1F94520039F711 = { + isa = PBXGroup; + children = ( + 3AB33F5C1E1F94530039F711 /* ASDKgram-Swift */, + 3AB33F5B1E1F94520039F711 /* Products */, + 78A64CA59A49BE1637214DF1 /* Pods */, + A7DD645D70CF34C7CA3B1A8B /* Frameworks */, + ); + sourceTree = ""; + }; + 3AB33F5B1E1F94520039F711 /* Products */ = { + isa = PBXGroup; + children = ( + 3AB33F5A1E1F94520039F711 /* ASDKgram-Swift.app */, + ); + name = Products; + sourceTree = ""; + }; + 3AB33F5C1E1F94530039F711 /* ASDKgram-Swift */ = { + isa = PBXGroup; + children = ( + 3AB33F991E22CF160039F711 /* Views */, + 3AB33F841E20E98C0039F711 /* Model */, + 3AB33F7D1E1FDA890039F711 /* Client */, + 3AB33F791E1F9E4E0039F711 /* Extensions */, + 3AB33F721E1F9B650039F711 /* Controllers */, + 3AB33F5D1E1F94530039F711 /* AppDelegate.swift */, + 3AB33F821E20E81E0039F711 /* Constants.swift */, + 3AB33F641E1F94530039F711 /* Assets.xcassets */, + 3AB33F661E1F94530039F711 /* LaunchScreen.storyboard */, + 3AB33F691E1F94530039F711 /* Info.plist */, + ); + path = "ASDKgram-Swift"; + sourceTree = ""; + }; + 3AB33F721E1F9B650039F711 /* Controllers */ = { + isa = PBXGroup; + children = ( + 3AB33F741E1F9B9F0039F711 /* ASDK */, + 3AB33F731E1F9B950039F711 /* UIKit */, + ); + name = Controllers; + sourceTree = ""; + }; + 3AB33F731E1F9B950039F711 /* UIKit */ = { + isa = PBXGroup; + children = ( + 3AB33F751E1F9C330039F711 /* PhotoFeedTableViewController.swift */, + ); + name = UIKit; + sourceTree = ""; + }; + 3AB33F741E1F9B9F0039F711 /* ASDK */ = { + isa = PBXGroup; + children = ( + 3AB33F771E1F9C400039F711 /* PhotoFeedTableNodeController.swift */, + ); + name = ASDK; + sourceTree = ""; + }; + 3AB33F791E1F9E4E0039F711 /* Extensions */ = { + isa = PBXGroup; + children = ( + 3AB33F7A1E1F9E630039F711 /* UIColor.swift */, + 3AB33F8B1E2106F30039F711 /* URL.swift */, + 3A2362FA1E2D33A0007E08F1 /* Date.swift */, + 3A7A28D81E2F7410003E2B8D /* UIImage.swift */, + ); + name = Extensions; + sourceTree = ""; + }; + 3AB33F7D1E1FDA890039F711 /* Client */ = { + isa = PBXGroup; + children = ( + 3AB33F801E1FDE100039F711 /* Webservice.swift */, + 3AB33F971E22A0080039F711 /* PX500Convenience.swift */, + ); + name = Client; + sourceTree = ""; + }; + 3AB33F841E20E98C0039F711 /* Model */ = { + isa = PBXGroup; + children = ( + 3AB33F851E20E9B10039F711 /* PhotoFeedModel.swift */, + 3AB33F871E20ED460039F711 /* PhotoModel.swift */, + 3AB33F951E2269D40039F711 /* PopularPageModel.swift */, + ); + name = Model; + sourceTree = ""; + }; + 3AB33F991E22CF160039F711 /* Views */ = { + isa = PBXGroup; + children = ( + 3AB33F9B1E22CF3C0039F711 /* UIKit */, + 3AB33F9C1E22CF5C0039F711 /* ASDK */, + ); + name = Views; + sourceTree = ""; + }; + 3AB33F9B1E22CF3C0039F711 /* UIKit */ = { + isa = PBXGroup; + children = ( + 3AB33F9D1E22D9DB0039F711 /* PhotoTableViewCell.swift */, + 3AB33FA11E230A160039F711 /* NetworkImageView.swift */, + ); + name = UIKit; + sourceTree = ""; + }; + 3AB33F9C1E22CF5C0039F711 /* ASDK */ = { + isa = PBXGroup; + children = ( + 3AB33FA31E2337850039F711 /* PhotoTableNodeCell.swift */, + ); + name = ASDK; + sourceTree = ""; + }; + 78A64CA59A49BE1637214DF1 /* Pods */ = { + isa = PBXGroup; + children = ( + 019E984FADA258377FC6B2D8 /* Pods-ASDKgram-Swift.debug.xcconfig */, + A3A86E74A8C3F06D7688AACB /* Pods-ASDKgram-Swift.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + A7DD645D70CF34C7CA3B1A8B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 4D7D664E4FF432C4AE232A56 /* Pods_ASDKgram_Swift.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3AB33F591E1F94520039F711 /* ASDKgram-Swift */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3AB33F6C1E1F94530039F711 /* Build configuration list for PBXNativeTarget "ASDKgram-Swift" */; + buildPhases = ( + A5A729883237749EE5D2DB1C /* [CP] Check Pods Manifest.lock */, + 3AB33F561E1F94520039F711 /* Sources */, + 3AB33F571E1F94520039F711 /* Frameworks */, + 3AB33F581E1F94520039F711 /* Resources */, + 154783123A953C3AFB9805CF /* [CP] Embed Pods Frameworks */, + 07D25AC7E9C9518F14F0C929 /* [CP] Copy Pods Resources */, + 3A7BEDD71E254278005769D4 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "ASDKgram-Swift"; + productName = "ASDKgram-Swift"; + productReference = 3AB33F5A1E1F94520039F711 /* ASDKgram-Swift.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3AB33F521E1F94520039F711 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0820; + LastUpgradeCheck = 0820; + ORGANIZATIONNAME = "Calum Harris"; + TargetAttributes = { + 3AB33F591E1F94520039F711 = { + CreatedOnToolsVersion = 8.2; + DevelopmentTeam = B3H446T9U7; + LastSwiftMigration = 0820; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 3AB33F551E1F94520039F711 /* Build configuration list for PBXProject "ASDKgram-Swift" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 3AB33F511E1F94520039F711; + productRefGroup = 3AB33F5B1E1F94520039F711 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3AB33F591E1F94520039F711 /* ASDKgram-Swift */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3AB33F581E1F94520039F711 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AB33F681E1F94530039F711 /* LaunchScreen.storyboard in Resources */, + 3AB33F651E1F94530039F711 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 07D25AC7E9C9518F14F0C929 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 154783123A953C3AFB9805CF /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ASDKgram-Swift/Pods-ASDKgram-Swift-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3A7BEDD71E254278005769D4 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = ""; + }; + A5A729883237749EE5D2DB1C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] 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 # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3AB33F561E1F94520039F711 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3AB33F781E1F9C400039F711 /* PhotoFeedTableNodeController.swift in Sources */, + 3A2362FB1E2D33A0007E08F1 /* Date.swift in Sources */, + 3AB33F7B1E1F9E630039F711 /* UIColor.swift in Sources */, + 3AB33F981E22A0080039F711 /* PX500Convenience.swift in Sources */, + 3AB33FA41E2337850039F711 /* PhotoTableNodeCell.swift in Sources */, + 3AB33FA21E230A160039F711 /* NetworkImageView.swift in Sources */, + 3AB33F8C1E2106F30039F711 /* URL.swift in Sources */, + 3AB33F831E20E81E0039F711 /* Constants.swift in Sources */, + 3AB33F961E2269D40039F711 /* PopularPageModel.swift in Sources */, + 3A7A28D91E2F7410003E2B8D /* UIImage.swift in Sources */, + 3AB33F5E1E1F94530039F711 /* AppDelegate.swift in Sources */, + 3AB33F811E1FDE100039F711 /* Webservice.swift in Sources */, + 3AB33F9E1E22D9DB0039F711 /* PhotoTableViewCell.swift in Sources */, + 3AB33F861E20E9B10039F711 /* PhotoFeedModel.swift in Sources */, + 3AB33F881E20ED460039F711 /* PhotoModel.swift in Sources */, + 3AB33F761E1F9C330039F711 /* PhotoFeedTableViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 3AB33F661E1F94530039F711 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 3AB33F671E1F94530039F711 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 3AB33F6A1E1F94530039F711 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + 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_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + 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 = 10.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 3AB33F6B1E1F94530039F711 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + 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_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + 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 = 10.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 3AB33F6D1E1F94530039F711 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 019E984FADA258377FC6B2D8 /* Pods-ASDKgram-Swift.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = B3H446T9U7; + INFOPLIST_FILE = "ASDKgram-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.RenaldoMoon.ASDKgram-Swift"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 3AB33F6E1E1F94530039F711 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A3A86E74A8C3F06D7688AACB /* Pods-ASDKgram-Swift.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = B3H446T9U7; + INFOPLIST_FILE = "ASDKgram-Swift/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.RenaldoMoon.ASDKgram-Swift"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3AB33F551E1F94520039F711 /* Build configuration list for PBXProject "ASDKgram-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AB33F6A1E1F94530039F711 /* Debug */, + 3AB33F6B1E1F94530039F711 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3AB33F6C1E1F94530039F711 /* Build configuration list for PBXNativeTarget "ASDKgram-Swift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3AB33F6D1E1F94530039F711 /* Debug */, + 3AB33F6E1E1F94530039F711 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3AB33F521E1F94520039F711 /* Project object */; +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..96a0fb6de --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/AppDelegate.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/AppDelegate.swift new file mode 100644 index 000000000..886d28ca7 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/AppDelegate.swift @@ -0,0 +1,61 @@ +// +// AppDelegate.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 +import AsyncDisplayKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + + // UIKit Home Feed viewController & navController + + let UIKitNavController = UINavigationController(rootViewController: PhotoFeedTableViewController()) + UIKitNavController.tabBarItem.title = "UIKit" + + // ASDK Home Feed viewController & navController + + let ASDKNavController = UINavigationController(rootViewController: PhotoFeedTableNodeController()) + ASDKNavController.tabBarItem.title = "ASDK" + + // UITabBarController + + let tabBarController = UITabBarController() + tabBarController.viewControllers = [UIKitNavController, ASDKNavController] + tabBarController.selectedIndex = 1 + tabBarController.tabBar.tintColor = UIColor.mainBarTintColor() + + // Nav Bar appearance + + UINavigationBar.appearance().barTintColor = UIColor.mainBarTintColor() + + // UIWindow + + window = UIWindow() + window?.backgroundColor = .white + window?.rootViewController = tabBarController + window?.makeKeyAndVisible() + + return true + } + +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..1d060ed28 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,93 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "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" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "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" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/LaunchScreen.storyboard b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..c1191aaf8 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/Main.storyboard b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/Main.storyboard new file mode 100644 index 000000000..273375fc7 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Constants.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Constants.swift new file mode 100644 index 000000000..f85bb817f --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Constants.swift @@ -0,0 +1,45 @@ +// +// Constants +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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. +// +// swiftlint:disable nesting + +import UIKit + +struct Constants { + + struct PX500 { + struct URLS { + static let Host = "https://api.500px.com/v1/" + static let PopularEndpoint = "photos?feature=popular&exclude=Nude,People,Fashion&sort=rating&image_size=3&include_store=store_download&include_states=voted" + static let SearchEndpoint = "photos/search?geo=" //latitude,longitude,radius + static let UserEndpoint = "photos?user_id=" + static let ConsumerKey = "&consumer_key=Fi13GVb8g53sGvHICzlram7QkKOlSDmAmp9s9aqC" + } + } + + struct CellLayout { + static let FontSize: CGFloat = 14 + static let HeaderHeight: CGFloat = 50 + static let UserImageHeight: CGFloat = 30 + static let HorizontalBuffer: CGFloat = 10 + static let VerticalBuffer: CGFloat = 5 + static let InsetForAvatar = UIEdgeInsets(top: HorizontalBuffer, left: 0, bottom: HorizontalBuffer, right: HorizontalBuffer) + static let InsetForHeader = UIEdgeInsets(top: 0, left: HorizontalBuffer, bottom: 0, right: HorizontalBuffer) + static let InsetForFooter = UIEdgeInsets(top: VerticalBuffer, left: HorizontalBuffer, bottom: VerticalBuffer, right: HorizontalBuffer) + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Date.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Date.swift new file mode 100644 index 000000000..0d7cde2d3 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Date.swift @@ -0,0 +1,32 @@ +// +// Date.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 16/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 Foundation + +extension Date { + + static let iso8601Formatter: DateFormatter = { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + return formatter + }() +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Info.plist b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Info.plist new file mode 100644 index 000000000..c12df3b8a --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/NetworkImageView.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/NetworkImageView.swift new file mode 100644 index 000000000..aeb860d8a --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/NetworkImageView.swift @@ -0,0 +1,57 @@ +// +// NetworkImageView.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 09/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +let imageCache = NSCache() + +class NetworkImageView: UIImageView { + + var imageUrlString: String? + + func loadImageUsingUrlString(urlString: String) { + + imageUrlString = urlString + + let url = URL(string: urlString) + + image = nil + + if let imageFromCache = imageCache.object(forKey: urlString as NSString) { + self.image = imageFromCache + return + } + + URLSession.shared.dataTask(with: url!, completionHandler: { (data, respones, error) in + + if error != nil { + print(error!) + return + } + + DispatchQueue.main.async { + let imageToCache = UIImage(data: data!) + if self.imageUrlString == urlString { + self.image = imageToCache + } + imageCache.setObject(imageToCache!, forKey: urlString as NSString) + } + }).resume() + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PX500Convenience.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PX500Convenience.swift new file mode 100644 index 000000000..808a553d1 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PX500Convenience.swift @@ -0,0 +1,34 @@ +// +// PX500Convenience.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 08/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 Foundation + +func parsePopularPage(withURL: URL) -> Resource { + + let parse = Resource(url: withURL, parseJSON: { jsonData in + + guard let json = jsonData as? JSONDictionary, let photos = json["photos"] as? [JSONDictionary] else { return .failure(.errorParsingJSON) } + + guard let model = PopularPageModel(dictionary: json, photosArray: photos.flatMap(PhotoModel.init)) else { return .failure(.errorParsingJSON) } + + return .success(model) + }) + + return parse +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift new file mode 100644 index 000000000..9a7cd5202 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift @@ -0,0 +1,124 @@ +// +// PhotoFeedModel.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +final class PhotoFeedModel { + + public private(set) var photoFeedModelType: PhotoFeedModelType + public private(set) var photos: [PhotoModel] = [] + public private(set) var imageSize: CGSize + private var url: URL + private var ids: [Int] = [] + private var currentPage: Int = 0 + private var totalPages: Int = 0 + public private(set) var totalItems: Int = 0 + private var fetchPageInProgress: Bool = false + private var refreshFeedInProgress: Bool = false + + init(initWithPhotoFeedModelType: PhotoFeedModelType, requiredImageSize: CGSize) { + self.photoFeedModelType = initWithPhotoFeedModelType + self.imageSize = requiredImageSize + self.url = URL.URLForFeedModelType(feedModelType: initWithPhotoFeedModelType) + } + + var numberOfItemsInFeed: Int { + return photos.count + } + + // return in completion handler the number of additions and the status of internet connection + + func updateNewBatchOfPopularPhotos(additionsAndConnectionStatusCompletion: @escaping (Int, InternetStatus) -> ()) { + + guard !fetchPageInProgress else { return } + + fetchPageInProgress = true + fetchNextPageOfPopularPhotos(replaceData: false) { [unowned self] additions, errors in + self.fetchPageInProgress = false + + if let error = errors { + switch error { + case .noInternetConnection: + additionsAndConnectionStatusCompletion(0, .noConnection) + default: additionsAndConnectionStatusCompletion(0, .connected) + } + } else { + additionsAndConnectionStatusCompletion(additions, .connected) + } + } + } + + private func fetchNextPageOfPopularPhotos(replaceData: Bool, numberOfAdditionsCompletion: @escaping (Int, NetworkingErrors?) -> ()) { + + if currentPage == totalPages, currentPage != 0 { + return numberOfAdditionsCompletion(0, .customError("No pages left to parse")) + } + + var newPhotos: [PhotoModel] = [] + var newIDs: [Int] = [] + + let pageToFetch = currentPage + 1 + + let url = self.url.addImageParameterForClosestImageSizeAndpage(size: imageSize, page: pageToFetch) + + WebService().load(resource: parsePopularPage(withURL: url)) { [unowned self] result in + + switch result { + case .success(let popularPage): + self.totalItems = popularPage.totalNumberOfItems + self.totalPages = popularPage.totalPages + self.currentPage = popularPage.page + + for photo in popularPage.photos { + if !replaceData || !self.ids.contains(photo.photoID) { + newPhotos.append(photo) + newIDs.append(photo.photoID) + } + } + + DispatchQueue.main.async { + if replaceData { + self.photos = newPhotos + self.ids = newIDs + } else { + self.photos += newPhotos + self.ids += newIDs + } + + numberOfAdditionsCompletion(newPhotos.count, nil) + } + + case .failure(let fail): + print(fail) + numberOfAdditionsCompletion(0, fail) + } + } + } +} + +enum PhotoFeedModelType { + case photoFeedModelTypePopular + case photoFeedModelTypeLocation + case photoFeedModelTypeUserPhotos +} + +enum InternetStatus { + case connected + case noConnection +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableNodeController.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableNodeController.swift new file mode 100644 index 000000000..e8a0dce3c --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableNodeController.swift @@ -0,0 +1,112 @@ +// +// PhotoFeedTableNodeController.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +class PhotoFeedTableNodeController: ASViewController { + + var activityIndicator: UIActivityIndicatorView! + var photoFeed: PhotoFeedModel + + init() { + photoFeed = PhotoFeedModel(initWithPhotoFeedModelType: .photoFeedModelTypePopular, requiredImageSize: screenSizeForWidth) + super.init(node: ASTableNode()) + self.navigationItem.title = "ASDK" + + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupActivityIndicator() + node.allowsSelection = false + node.view.separatorStyle = .none + node.dataSource = self + node.delegate = self + node.view.leadingScreensForBatching = 2.5 + navigationController?.hidesBarsOnSwipe = true + } + + // helper functions + func setupActivityIndicator() { + let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) + self.activityIndicator = activityIndicator + let bounds = self.node.frame + var refreshRect = activityIndicator.frame + refreshRect.origin = CGPoint(x: (bounds.size.width - activityIndicator.frame.size.width) / 2.0, y: (bounds.size.height - activityIndicator.frame.size.height) / 2.0) + activityIndicator.frame = refreshRect + self.node.view.addSubview(activityIndicator) + } + + var screenSizeForWidth: CGSize = { + let screenRect = UIScreen.main.bounds + let screenScale = UIScreen.main.scale + return CGSize(width: screenRect.size.width * screenScale, height: screenRect.size.width * screenScale) + }() + + func fetchNewBatchWithContext(_ context: ASBatchContext?) { + activityIndicator.startAnimating() + photoFeed.updateNewBatchOfPopularPhotos() { additions, connectionStatus in + switch connectionStatus { + case .connected: + self.activityIndicator.stopAnimating() + self.addRowsIntoTableNode(newPhotoCount: additions) + context?.completeBatchFetching(true) + case .noConnection: + self.activityIndicator.stopAnimating() + if context != nil { + context!.completeBatchFetching(true) + } + break + } + } + } + + func addRowsIntoTableNode(newPhotoCount newPhotos: Int) { + let indexRange = (photoFeed.photos.count - newPhotos.. Int { + return photoFeed.numberOfItemsInFeed + } + + func tableNode(_ tableNode: ASTableNode, nodeBlockForRowAt indexPath: IndexPath) -> ASCellNodeBlock { + let photo = photoFeed.photos[indexPath.row] + let nodeBlock: ASCellNodeBlock = { _ in + return PhotoTableNodeCell(photoModel: photo) + } + return nodeBlock + } + + func shouldBatchFetchForCollectionNode(collectionNode: ASCollectionNode) -> Bool { + return true + } + + func tableNode(_ tableNode: ASTableNode, willBeginBatchFetchWith context: ASBatchContext) { + fetchNewBatchWithContext(context) + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableViewController.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableViewController.swift new file mode 100644 index 000000000..0e7cbef97 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedTableViewController.swift @@ -0,0 +1,121 @@ +// +// PhotoFeedTableViewController.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +class PhotoFeedTableViewController: UITableViewController { + + var activityIndicator: UIActivityIndicatorView! + var photoFeed: PhotoFeedModel + + init() { + photoFeed = PhotoFeedModel(initWithPhotoFeedModelType: .photoFeedModelTypePopular, requiredImageSize: screenSizeForWidth) + super.init(nibName: nil, bundle: nil) + self.navigationItem.title = "UIKit" + + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + setupActivityIndicator() + configureTableView() + fetchNewBatch() + navigationController?.hidesBarsOnSwipe = true + } + + func fetchNewBatch() { + activityIndicator.startAnimating() + photoFeed.updateNewBatchOfPopularPhotos() { additions, connectionStatus in + switch connectionStatus { + case .connected: + self.activityIndicator.stopAnimating() + self.addRowsIntoTableView(newPhotoCount: additions) + case .noConnection: + self.activityIndicator.stopAnimating() + break + } + } + } + + var screenSizeForWidth: CGSize = { + let screenRect = UIScreen.main.bounds + let screenScale = UIScreen.main.scale + return CGSize(width: screenRect.size.width * screenScale, height: screenRect.size.width * screenScale) + }() + + // helper functions + func setupActivityIndicator() { + let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray) + self.activityIndicator = activityIndicator + self.tableView.addSubview(activityIndicator) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: self.tableView.centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: self.tableView.centerYAnchor) + ]) + } + + func configureTableView() { + tableView.register(PhotoTableViewCell.self, forCellReuseIdentifier: "photoCell") + tableView.allowsSelection = false + tableView.rowHeight = UITableViewAutomaticDimension + tableView.separatorStyle = .none + } +} + +extension PhotoFeedTableViewController { + + func addRowsIntoTableView(newPhotoCount newPhotos: Int) { + + let indexRange = (photoFeed.photos.count - newPhotos.. Int { + return photoFeed.photos.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let cell = tableView.dequeueReusableCell(withIdentifier: "photoCell", for: indexPath) as? PhotoTableViewCell else { fatalError("Wrong cell type") } + cell.photoModel = photoFeed.photos[indexPath.row] + return cell + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return PhotoTableViewCell.height(for: photoFeed.photos[indexPath.row], withWidth: self.view.frame.size.width) + } + + override func scrollViewDidScroll(_ scrollView: UIScrollView) { + + let currentOffSetY = scrollView.contentOffset.y + let contentHeight = scrollView.contentSize.height + let screenHeight = UIScreen.main.bounds.size.height + let screenfullsBeforeBottom = (contentHeight - currentOffSetY) / screenHeight + if screenfullsBeforeBottom < 2.5 { + self.fetchNewBatch() + } + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoModel.swift new file mode 100644 index 000000000..e4070bb25 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoModel.swift @@ -0,0 +1,116 @@ +// +// PhotoModel.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +typealias JSONDictionary = [String : Any] + +struct PhotoModel { + + let url: String + let photoID: Int + let dateString: String + let descriptionText: String + let likesCount: Int + let ownerUserName: String + let ownerPicURL: String + + init?(dictionary: JSONDictionary) { + + guard let url = dictionary["image_url"] as? String, let date = dictionary["created_at"] as? String, let photoID = dictionary["id"] as? Int, let descriptionText = dictionary["name"] as? String, let likesCount = dictionary["positive_votes_count"] as? Int else { print("error parsing JSON within PhotoModel Init"); return nil } + + guard let user = dictionary["user"] as? JSONDictionary, let username = user["username"] as? String, let ownerPicURL = user["userpic_url"] as? String else { print("error parsing JSON within PhotoModel Init"); return nil } + + self.url = url + self.photoID = photoID + self.descriptionText = descriptionText + self.likesCount = likesCount + self.dateString = date + self.ownerUserName = username + self.ownerPicURL = ownerPicURL + } +} + +extension PhotoModel { + + // MARK: - Attributed Strings + + func attrStringForUserName(withSize size: CGFloat) -> NSAttributedString { + let attr = [ + NSForegroundColorAttributeName : UIColor.darkGray, + NSFontAttributeName: UIFont.boldSystemFont(ofSize: size) + ] + return NSAttributedString(string: self.ownerUserName, attributes: attr) + } + + func attrStringForDescription(withSize size: CGFloat) -> NSAttributedString { + let attr = [ + NSForegroundColorAttributeName : UIColor.darkGray, + NSFontAttributeName: UIFont.systemFont(ofSize: size) + ] + return NSAttributedString(string: self.descriptionText, attributes: attr) + } + + func attrStringLikes(withSize size: CGFloat) -> NSAttributedString { + + let formatter = NumberFormatter() + formatter.numberStyle = .decimal + let formattedLikesNumber: String? = formatter.string(from: NSNumber(value: self.likesCount)) + let likesString: String = "\(formattedLikesNumber!) Likes" + let textAttr = [NSForegroundColorAttributeName : UIColor.mainBarTintColor(), NSFontAttributeName: UIFont.systemFont(ofSize: size)] + let likesAttrString = NSAttributedString(string: likesString, attributes: textAttr) + + let heartAttr = [NSForegroundColorAttributeName : UIColor.red, NSFontAttributeName: UIFont.systemFont(ofSize: size)] + let heartAttrString = NSAttributedString(string: "♥︎ ", attributes: heartAttr) + + let combine = NSMutableAttributedString() + combine.append(heartAttrString) + combine.append(likesAttrString) + return combine + } + + func attrStringForTimeSinceString(withSize size: CGFloat) -> NSAttributedString { + + let attr = [ + NSForegroundColorAttributeName : UIColor.mainBarTintColor(), + NSFontAttributeName: UIFont.systemFont(ofSize: size) + ] + + let date = Date.iso8601Formatter.date(from: self.dateString)! + return NSAttributedString(string: timeStringSince(fromConverted: date), attributes: attr) + } + + private func timeStringSince(fromConverted date: Date) -> String { + let diffDates = NSCalendar.current.dateComponents([.day, .hour, .second], from: date, to: Date()) + + if let week = diffDates.day, week > 7 { + return "\(week / 7)w" + } else if let day = diffDates.day, day > 0 { + return "\(day)d" + } else if let hour = diffDates.hour, hour > 0 { + return "\(hour)h" + } else if let second = diffDates.second, second > 0 { + return "\(second)s" + } else if let zero = diffDates.second, zero == 0 { + return "1s" + } else { + return "ERROR" + } + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableNodeCell.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableNodeCell.swift new file mode 100644 index 000000000..56e02588b --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableNodeCell.swift @@ -0,0 +1,84 @@ +// +// PhotoTableNodeCell.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 09/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 Foundation +import AsyncDisplayKit + +class PhotoTableNodeCell: ASCellNode { + + let usernameLabel = ASTextNode() + let timeIntervalLabel = ASTextNode() + let photoLikesLabel = ASTextNode() + let photoDescriptionLabel = ASTextNode() + + let avatarImageNode: ASNetworkImageNode = { + let imageNode = ASNetworkImageNode() + imageNode.contentMode = .scaleAspectFill + imageNode.imageModificationBlock = ASImageNodeRoundBorderModificationBlock(0, nil) + return imageNode + }() + + let photoImageNode: ASNetworkImageNode = { + let imageNode = ASNetworkImageNode() + imageNode.contentMode = .scaleAspectFill + return imageNode + }() + + init(photoModel: PhotoModel) { + super.init() + self.photoImageNode.url = URL(string: photoModel.url) + self.avatarImageNode.url = URL(string: photoModel.ownerPicURL) + self.usernameLabel.attributedText = photoModel.attrStringForUserName(withSize: Constants.CellLayout.FontSize) + self.timeIntervalLabel.attributedText = photoModel.attrStringForTimeSinceString(withSize: Constants.CellLayout.FontSize) + self.photoLikesLabel.attributedText = photoModel.attrStringLikes(withSize: Constants.CellLayout.FontSize) + self.photoDescriptionLabel.attributedText = photoModel.attrStringForDescription(withSize: Constants.CellLayout.FontSize) + self.automaticallyManagesSubnodes = true + } + + override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec { + + // Header Stack + + var headerChildren: [ASLayoutElement] = [] + + let headerStack = ASStackLayoutSpec.horizontal() + headerStack.alignItems = .center + avatarImageNode.style.preferredSize = CGSize(width: Constants.CellLayout.UserImageHeight, height: Constants.CellLayout.UserImageHeight) + headerChildren.append(ASInsetLayoutSpec(insets: Constants.CellLayout.InsetForAvatar, child: avatarImageNode)) + usernameLabel.style.flexShrink = 1.0 + headerChildren.append(usernameLabel) + + let spacer = ASLayoutSpec() + spacer.style.flexGrow = 1.0 + headerChildren.append(spacer) + + timeIntervalLabel.style.spacingBefore = Constants.CellLayout.HorizontalBuffer + headerChildren.append(timeIntervalLabel) + + let footerStack = ASStackLayoutSpec.vertical() + footerStack.spacing = Constants.CellLayout.VerticalBuffer + footerStack.children = [photoLikesLabel, photoDescriptionLabel] + headerStack.children = headerChildren + + let verticalStack = ASStackLayoutSpec.vertical() + + verticalStack.children = [ASInsetLayoutSpec(insets: Constants.CellLayout.InsetForHeader, child: headerStack), ASRatioLayoutSpec(ratio: 1.0, child: photoImageNode), ASInsetLayoutSpec(insets: Constants.CellLayout.InsetForFooter, child: footerStack)] + + return verticalStack + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableViewCell.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableViewCell.swift new file mode 100644 index 000000000..b57beab9a --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoTableViewCell.swift @@ -0,0 +1,142 @@ +// +// PhotoTableViewCell.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 08/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +class PhotoTableViewCell: UITableViewCell { + + var photoModel: PhotoModel? { + didSet { + if let model = photoModel { + photoImageView.loadImageUsingUrlString(urlString: model.url) + avatarImageView.loadImageUsingUrlString(urlString: model.ownerPicURL) + photoLikesLabel.attributedText = model.attrStringLikes(withSize: Constants.CellLayout.FontSize) + usernameLabel.attributedText = model.attrStringForUserName(withSize: Constants.CellLayout.FontSize) + timeIntervalLabel.attributedText = model.attrStringForTimeSinceString(withSize: Constants.CellLayout.FontSize) + photoDescriptionLabel.attributedText = model.attrStringForDescription(withSize: Constants.CellLayout.FontSize) + photoDescriptionLabel.sizeToFit() + var rect = photoDescriptionLabel.frame + let availableWidth = self.bounds.size.width - Constants.CellLayout.HorizontalBuffer * 2 + rect.size = model.attrStringForDescription(withSize: Constants.CellLayout.FontSize).boundingRect(with: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size + photoDescriptionLabel.frame = rect + } + } + } + + let photoImageView: NetworkImageView = { + let imageView = NetworkImageView() + imageView.contentMode = .scaleAspectFill + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + let avatarImageView: NetworkImageView = { + let imageView = NetworkImageView() + imageView.contentMode = .scaleAspectFill + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.layer.cornerRadius = Constants.CellLayout.UserImageHeight / 2 + imageView.clipsToBounds = true + return imageView + }() + + let usernameLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let timeIntervalLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let photoLikesLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + let photoDescriptionLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.numberOfLines = 3 + return label + }() + + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + addSubview(photoImageView) + addSubview(avatarImageView) + addSubview(usernameLabel) + addSubview(timeIntervalLabel) + addSubview(photoLikesLabel) + addSubview(photoDescriptionLabel) + setupConstraints() + } + + func setupConstraints() { + + NSLayoutConstraint.activate ([ + //photoImageView + photoImageView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.CellLayout.HeaderHeight), + photoImageView.widthAnchor.constraint(equalTo: widthAnchor), + photoImageView.heightAnchor.constraint(equalTo: photoImageView.widthAnchor), + // avatarImageView + avatarImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: Constants.CellLayout.HorizontalBuffer), + avatarImageView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.CellLayout.HorizontalBuffer), + avatarImageView.heightAnchor.constraint(equalToConstant: Constants.CellLayout.UserImageHeight), + avatarImageView.widthAnchor.constraint(equalTo: avatarImageView.heightAnchor), + // usernameLabel + usernameLabel.leftAnchor.constraint(equalTo: avatarImageView.rightAnchor, constant: Constants.CellLayout.HorizontalBuffer), + usernameLabel.rightAnchor.constraint(equalTo: timeIntervalLabel.leftAnchor, constant: -Constants.CellLayout.HorizontalBuffer), + usernameLabel.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor), + // timeIntervalLabel + timeIntervalLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -Constants.CellLayout.HorizontalBuffer), + timeIntervalLabel.centerYAnchor.constraint(equalTo: avatarImageView.centerYAnchor), + // photoLikesLabel + photoLikesLabel.topAnchor.constraint(equalTo: photoImageView.bottomAnchor, constant: Constants.CellLayout.VerticalBuffer), + photoLikesLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: Constants.CellLayout.HorizontalBuffer), + // photoDescriptionLabel + photoDescriptionLabel.topAnchor.constraint(equalTo: photoLikesLabel.bottomAnchor, constant: Constants.CellLayout.VerticalBuffer), + photoDescriptionLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: Constants.CellLayout.HorizontalBuffer), + photoDescriptionLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: -Constants.CellLayout.HorizontalBuffer), + photoDescriptionLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.CellLayout.VerticalBuffer) + ]) + } + + class func height(for photo: PhotoModel, withWidth width: CGFloat) -> CGFloat { + let photoHeight = width + let font = UIFont.systemFont(ofSize: Constants.CellLayout.FontSize) + let likesHeight = round(font.lineHeight) + let descriptionAttrString = photo.attrStringForDescription(withSize: Constants.CellLayout.FontSize) + let availableWidth = width - Constants.CellLayout.HorizontalBuffer * 2 + let descriptionHeight = descriptionAttrString.boundingRect(with: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil).size.height + + return likesHeight + descriptionHeight + photoHeight + Constants.CellLayout.HeaderHeight + Constants.CellLayout.VerticalBuffer * 3 + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PopularPageModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PopularPageModel.swift new file mode 100644 index 000000000..8aca2445f --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PopularPageModel.swift @@ -0,0 +1,37 @@ +// +// PopularPageModel.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 08/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 Foundation + +class PopularPageModel: NSObject { + + let page: Int + let totalPages: Int + let totalNumberOfItems: Int + let photos: [PhotoModel] + + init?(dictionary: JSONDictionary, photosArray: [PhotoModel]) { + guard let page = dictionary["current_page"] as? Int, let totalPages = dictionary["total_pages"] as? Int, let totalItems = dictionary["total_items"] as? Int else { print("error parsing JSON within PhotoModel Init"); return nil } + + self.page = page + self.totalPages = totalPages + self.totalNumberOfItems = totalItems + self.photos = photosArray + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIColor.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIColor.swift new file mode 100644 index 000000000..5ab9d7e01 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIColor.swift @@ -0,0 +1,26 @@ +// +// UIColor.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +extension UIColor { + + class func mainBarTintColor() -> UIColor { + return UIColor(red: 69/255, green: 142/255, blue: 255/255, alpha: 1) + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIImage.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIImage.swift new file mode 100644 index 000000000..4eb233f37 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/UIImage.swift @@ -0,0 +1,60 @@ +// +// UIImage.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 18/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +// This extension was copied directly from LayoutSpecExamples-Swift. It is an example of how to create Precomoposed Alpha Corners. I have used the helper ASImageNodeRoundBorderModificationBlock:boarderWidth:boarderColor function in practice which does the same. + +extension UIImage { + + func makeCircularImage(size: CGSize, borderWidth width: CGFloat) -> UIImage { + // make a CGRect with the image's size + let circleRect = CGRect(origin: .zero, size: size) + + // begin the image context since we're not in a drawRect: + UIGraphicsBeginImageContextWithOptions(circleRect.size, false, 0) + + // create a UIBezierPath circle + let circle = UIBezierPath(roundedRect: circleRect, cornerRadius: circleRect.size.width * 0.5) + + // clip to the circle + circle.addClip() + + UIColor.white.set() + circle.fill() + + // draw the image in the circleRect *AFTER* the context is clipped + self.draw(in: circleRect) + + // create a border (for white background pictures) + if width > 0 { + circle.lineWidth = width + UIColor.white.set() + circle.stroke() + } + + // get an image from the image context + let roundedImage = UIGraphicsGetImageFromCurrentImageContext() + + // end the image context since we're not in a drawRect: + UIGraphicsEndImageContext() + + return roundedImage ?? self + } +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/URL.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/URL.swift new file mode 100644 index 000000000..596049815 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/URL.swift @@ -0,0 +1,67 @@ +// +// URL.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 07/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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 + +extension URL { + + static func URLForFeedModelType(feedModelType: PhotoFeedModelType) -> URL { + switch feedModelType { + case .photoFeedModelTypePopular: + return URL(string: assemble500PXURLString(endpoint: Constants.PX500.URLS.PopularEndpoint))! + + case .photoFeedModelTypeLocation: + return URL(string: assemble500PXURLString(endpoint: Constants.PX500.URLS.SearchEndpoint))! + + case .photoFeedModelTypeUserPhotos: + return URL(string: assemble500PXURLString(endpoint: Constants.PX500.URLS.UserEndpoint))! + } + } + + private static func assemble500PXURLString(endpoint: String) -> String { + return Constants.PX500.URLS.Host + endpoint + Constants.PX500.URLS.ConsumerKey + } + + mutating func addImageParameterForClosestImageSizeAndpage(size: CGSize, page: Int) -> URL { + + let imageParameterID: Int + + if size.height <= 70 { + imageParameterID = 1 + } else if size.height <= 100 { + imageParameterID = 100 + } else if size.height <= 140 { + imageParameterID = 2 + } else if size.height <= 200 { + imageParameterID = 200 + } else if size.height <= 280 { + imageParameterID = 3 + } else if size.height <= 400 { + imageParameterID = 400 + } else { + imageParameterID = 600 + } + + var urlString = self.absoluteString + urlString.append("&image_size=\(imageParameterID)&page=\(page)") + + return URL(string: urlString)! + } + +} diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift new file mode 100644 index 000000000..e2196208d --- /dev/null +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift @@ -0,0 +1,93 @@ +// +// Webservice.swift +// ASDKgram-Swift +// +// Created by Calum Harris on 06/01/2017. +// +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// +// 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. +// +// swiftlint:disable force_cast + +import UIKit + +final class WebService { + func load(resource: Resource, completion: @escaping (Result) -> ()) { + URLSession.shared.dataTask(with: resource.url) { data, response, error in + // Check for errors in responses. + let result = self.checkForNetworkErrors(data, response, error) + + switch result { + case .success(let data): + completion(resource.parse(data)) + case .failure(let error): + completion(.failure(error)) + } + }.resume() + } +} + +extension WebService { + + fileprivate func checkForNetworkErrors(_ data: Data?, _ response: URLResponse?, _ error: Error?) -> Result { + // Check for errors in responses. + guard error == nil else { + if (error as! NSError).domain == NSURLErrorDomain && ((error as! NSError).code == NSURLErrorNotConnectedToInternet || (error as! NSError).code == NSURLErrorTimedOut) { + return .failure(.noInternetConnection) + } else { + return .failure(.returnedError(error!)) + } + } + + guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else { + return .failure((.invalidStatusCode("Request returned status code other than 2xx \(response)"))) + } + + guard let data = data else { return .failure(.dataReturnedNil) } + + return .success(data) + } +} + +struct Resource { + let url: URL + let parse: (Data) -> Result +} + +extension Resource { + + init(url: URL, parseJSON: @escaping (Any) -> Result) { + self.url = url + self.parse = { data in + do { + let jsonData = try JSONSerialization.jsonObject(with: data, options: []) + return parseJSON(jsonData) + } catch { + fatalError("Error parsing data") + } + } + } +} + +enum Result { + case success(T) + case failure(NetworkingErrors) +} + +enum NetworkingErrors: Error { + case errorParsingJSON + case noInternetConnection + case dataReturnedNil + case returnedError(Error) + case invalidStatusCode(String) + case customError(String) +} diff --git a/examples_extra/ASDKgram-Swift/Podfile b/examples_extra/ASDKgram-Swift/Podfile new file mode 100644 index 000000000..9e6797087 --- /dev/null +++ b/examples_extra/ASDKgram-Swift/Podfile @@ -0,0 +1,8 @@ + +target 'ASDKgram-Swift' do + + use_frameworks! + + pod 'AsyncDisplayKit', '>= 2.0' + +end