├── Pugs.xcodeproj └── project.pbxproj ├── Pugs ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ImageCell.swift ├── ImagesController.swift ├── Info.plist ├── PugViewController.swift ├── PugsController.swift └── PugsViewController.swift ├── Readme.markdown └── Screenshot@2x.png /Pugs.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 21D251841BA73C1B00DD1F25 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D251831BA73C1B00DD1F25 /* AppDelegate.swift */; }; 11 | 21D251861BA73C1B00DD1F25 /* PugsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D251851BA73C1B00DD1F25 /* PugsViewController.swift */; }; 12 | 21D2518B1BA73C1B00DD1F25 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 21D2518A1BA73C1B00DD1F25 /* Assets.xcassets */; }; 13 | 21D2518E1BA73C1B00DD1F25 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 21D2518C1BA73C1B00DD1F25 /* LaunchScreen.storyboard */; }; 14 | 21D251A41BA73D3F00DD1F25 /* ImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D251A31BA73D3F00DD1F25 /* ImageCell.swift */; settings = {ASSET_TAGS = (); }; }; 15 | 21D251A61BA73E0300DD1F25 /* PugsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D251A51BA73E0300DD1F25 /* PugsController.swift */; settings = {ASSET_TAGS = (); }; }; 16 | 21D251B11BA749BD00DD1F25 /* PugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D251B01BA749BD00DD1F25 /* PugViewController.swift */; settings = {ASSET_TAGS = (); }; }; 17 | 21D251B71BA74FC000DD1F25 /* ImagesController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21D251B61BA74FC000DD1F25 /* ImagesController.swift */; settings = {ASSET_TAGS = (); }; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 21D251801BA73C1B00DD1F25 /* Pugs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Pugs.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 21D251831BA73C1B00DD1F25 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 21D251851BA73C1B00DD1F25 /* PugsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PugsViewController.swift; sourceTree = ""; }; 24 | 21D2518A1BA73C1B00DD1F25 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 21D2518D1BA73C1B00DD1F25 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 26 | 21D2518F1BA73C1B00DD1F25 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | 21D251A31BA73D3F00DD1F25 /* ImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCell.swift; sourceTree = ""; }; 28 | 21D251A51BA73E0300DD1F25 /* PugsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PugsController.swift; sourceTree = ""; }; 29 | 21D251B01BA749BD00DD1F25 /* PugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PugViewController.swift; sourceTree = ""; }; 30 | 21D251B61BA74FC000DD1F25 /* ImagesController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesController.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 21D2517D1BA73C1B00DD1F25 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 21D251771BA73C1B00DD1F25 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 21D251821BA73C1B00DD1F25 /* Pugs */, 48 | 21D251811BA73C1B00DD1F25 /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 21D251811BA73C1B00DD1F25 /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 21D251801BA73C1B00DD1F25 /* Pugs.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 21D251821BA73C1B00DD1F25 /* Pugs */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 21D251AA1BA7475000DD1F25 /* Application */, 64 | 21D251A91BA7474B00DD1F25 /* View Controllers */, 65 | 21D251A81BA7474800DD1F25 /* Controllers */, 66 | 21D251A71BA7474300DD1F25 /* Views */, 67 | 21D251AB1BA7475500DD1F25 /* Resources */, 68 | ); 69 | path = Pugs; 70 | sourceTree = ""; 71 | }; 72 | 21D251A71BA7474300DD1F25 /* Views */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 21D251A31BA73D3F00DD1F25 /* ImageCell.swift */, 76 | ); 77 | name = Views; 78 | sourceTree = ""; 79 | }; 80 | 21D251A81BA7474800DD1F25 /* Controllers */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 21D251A51BA73E0300DD1F25 /* PugsController.swift */, 84 | 21D251B61BA74FC000DD1F25 /* ImagesController.swift */, 85 | ); 86 | name = Controllers; 87 | sourceTree = ""; 88 | }; 89 | 21D251A91BA7474B00DD1F25 /* View Controllers */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 21D251851BA73C1B00DD1F25 /* PugsViewController.swift */, 93 | 21D251B01BA749BD00DD1F25 /* PugViewController.swift */, 94 | ); 95 | name = "View Controllers"; 96 | sourceTree = ""; 97 | }; 98 | 21D251AA1BA7475000DD1F25 /* Application */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 21D251831BA73C1B00DD1F25 /* AppDelegate.swift */, 102 | ); 103 | name = Application; 104 | sourceTree = ""; 105 | }; 106 | 21D251AB1BA7475500DD1F25 /* Resources */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 21D2518A1BA73C1B00DD1F25 /* Assets.xcassets */, 110 | 21D2518C1BA73C1B00DD1F25 /* LaunchScreen.storyboard */, 111 | 21D2518F1BA73C1B00DD1F25 /* Info.plist */, 112 | ); 113 | name = Resources; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXNativeTarget section */ 119 | 21D2517F1BA73C1B00DD1F25 /* Pugs */ = { 120 | isa = PBXNativeTarget; 121 | buildConfigurationList = 21D2519D1BA73C1B00DD1F25 /* Build configuration list for PBXNativeTarget "Pugs" */; 122 | buildPhases = ( 123 | 21D2517C1BA73C1B00DD1F25 /* Sources */, 124 | 21D2517D1BA73C1B00DD1F25 /* Frameworks */, 125 | 21D2517E1BA73C1B00DD1F25 /* Resources */, 126 | ); 127 | buildRules = ( 128 | ); 129 | dependencies = ( 130 | ); 131 | name = Pugs; 132 | productName = Pugs; 133 | productReference = 21D251801BA73C1B00DD1F25 /* Pugs.app */; 134 | productType = "com.apple.product-type.application"; 135 | }; 136 | /* End PBXNativeTarget section */ 137 | 138 | /* Begin PBXProject section */ 139 | 21D251781BA73C1B00DD1F25 /* Project object */ = { 140 | isa = PBXProject; 141 | attributes = { 142 | LastUpgradeCheck = 0700; 143 | ORGANIZATIONNAME = "Sam Soffes"; 144 | TargetAttributes = { 145 | 21D2517F1BA73C1B00DD1F25 = { 146 | CreatedOnToolsVersion = 7.0; 147 | }; 148 | }; 149 | }; 150 | buildConfigurationList = 21D2517B1BA73C1B00DD1F25 /* Build configuration list for PBXProject "Pugs" */; 151 | compatibilityVersion = "Xcode 3.2"; 152 | developmentRegion = English; 153 | hasScannedForEncodings = 0; 154 | knownRegions = ( 155 | en, 156 | Base, 157 | ); 158 | mainGroup = 21D251771BA73C1B00DD1F25; 159 | productRefGroup = 21D251811BA73C1B00DD1F25 /* Products */; 160 | projectDirPath = ""; 161 | projectRoot = ""; 162 | targets = ( 163 | 21D2517F1BA73C1B00DD1F25 /* Pugs */, 164 | ); 165 | }; 166 | /* End PBXProject section */ 167 | 168 | /* Begin PBXResourcesBuildPhase section */ 169 | 21D2517E1BA73C1B00DD1F25 /* Resources */ = { 170 | isa = PBXResourcesBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | 21D2518E1BA73C1B00DD1F25 /* LaunchScreen.storyboard in Resources */, 174 | 21D2518B1BA73C1B00DD1F25 /* Assets.xcassets in Resources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXResourcesBuildPhase section */ 179 | 180 | /* Begin PBXSourcesBuildPhase section */ 181 | 21D2517C1BA73C1B00DD1F25 /* Sources */ = { 182 | isa = PBXSourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | 21D251861BA73C1B00DD1F25 /* PugsViewController.swift in Sources */, 186 | 21D251841BA73C1B00DD1F25 /* AppDelegate.swift in Sources */, 187 | 21D251A61BA73E0300DD1F25 /* PugsController.swift in Sources */, 188 | 21D251B71BA74FC000DD1F25 /* ImagesController.swift in Sources */, 189 | 21D251A41BA73D3F00DD1F25 /* ImageCell.swift in Sources */, 190 | 21D251B11BA749BD00DD1F25 /* PugViewController.swift in Sources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXSourcesBuildPhase section */ 195 | 196 | /* Begin PBXVariantGroup section */ 197 | 21D2518C1BA73C1B00DD1F25 /* LaunchScreen.storyboard */ = { 198 | isa = PBXVariantGroup; 199 | children = ( 200 | 21D2518D1BA73C1B00DD1F25 /* Base */, 201 | ); 202 | name = LaunchScreen.storyboard; 203 | sourceTree = ""; 204 | }; 205 | /* End PBXVariantGroup section */ 206 | 207 | /* Begin XCBuildConfiguration section */ 208 | 21D2519B1BA73C1B00DD1F25 /* Debug */ = { 209 | isa = XCBuildConfiguration; 210 | buildSettings = { 211 | ALWAYS_SEARCH_USER_PATHS = NO; 212 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 213 | CLANG_CXX_LIBRARY = "libc++"; 214 | CLANG_ENABLE_MODULES = YES; 215 | CLANG_ENABLE_OBJC_ARC = YES; 216 | CLANG_WARN_BOOL_CONVERSION = YES; 217 | CLANG_WARN_CONSTANT_CONVERSION = YES; 218 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INT_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_UNREACHABLE_CODE = YES; 224 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 225 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 226 | COPY_PHASE_STRIP = NO; 227 | DEBUG_INFORMATION_FORMAT = dwarf; 228 | ENABLE_STRICT_OBJC_MSGSEND = YES; 229 | ENABLE_TESTABILITY = YES; 230 | GCC_C_LANGUAGE_STANDARD = gnu99; 231 | GCC_DYNAMIC_NO_PIC = NO; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_OPTIMIZATION_LEVEL = 0; 234 | GCC_PREPROCESSOR_DEFINITIONS = ( 235 | "DEBUG=1", 236 | "$(inherited)", 237 | ); 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 245 | MTL_ENABLE_DEBUG_INFO = YES; 246 | ONLY_ACTIVE_ARCH = YES; 247 | SDKROOT = iphoneos; 248 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 249 | }; 250 | name = Debug; 251 | }; 252 | 21D2519C1BA73C1B00DD1F25 /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 257 | CLANG_CXX_LIBRARY = "libc++"; 258 | CLANG_ENABLE_MODULES = YES; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INT_CONVERSION = YES; 266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu99; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 283 | MTL_ENABLE_DEBUG_INFO = NO; 284 | SDKROOT = iphoneos; 285 | VALIDATE_PRODUCT = YES; 286 | }; 287 | name = Release; 288 | }; 289 | 21D2519E1BA73C1B00DD1F25 /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 293 | FRAMEWORK_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "$(PROJECT_DIR)/Carthage/Build/iOS", 296 | ); 297 | INFOPLIST_FILE = Pugs/Info.plist; 298 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 299 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.Pugs; 300 | PRODUCT_NAME = "$(TARGET_NAME)"; 301 | }; 302 | name = Debug; 303 | }; 304 | 21D2519F1BA73C1B00DD1F25 /* Release */ = { 305 | isa = XCBuildConfiguration; 306 | buildSettings = { 307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 308 | FRAMEWORK_SEARCH_PATHS = ( 309 | "$(inherited)", 310 | "$(PROJECT_DIR)/Carthage/Build/iOS", 311 | ); 312 | INFOPLIST_FILE = Pugs/Info.plist; 313 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 314 | PRODUCT_BUNDLE_IDENTIFIER = com.samsoffes.Pugs; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | }; 317 | name = Release; 318 | }; 319 | /* End XCBuildConfiguration section */ 320 | 321 | /* Begin XCConfigurationList section */ 322 | 21D2517B1BA73C1B00DD1F25 /* Build configuration list for PBXProject "Pugs" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | 21D2519B1BA73C1B00DD1F25 /* Debug */, 326 | 21D2519C1BA73C1B00DD1F25 /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | 21D2519D1BA73C1B00DD1F25 /* Build configuration list for PBXNativeTarget "Pugs" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 21D2519E1BA73C1B00DD1F25 /* Debug */, 335 | 21D2519F1BA73C1B00DD1F25 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | /* End XCConfigurationList section */ 341 | }; 342 | rootObject = 21D251781BA73C1B00DD1F25 /* Project object */; 343 | } 344 | -------------------------------------------------------------------------------- /Pugs/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Pugs 4 | // 5 | // Created by Sam Soffes on 9/14/15. 6 | // Copyright © 2015 Sam Soffes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder { 13 | 14 | // MARK: - Properties 15 | 16 | var window: UIWindow? = { 17 | let window = UIWindow(frame: UIScreen.mainScreen().bounds) 18 | window.rootViewController = UINavigationController(rootViewController: PugsViewController()) 19 | return window 20 | }() 21 | } 22 | 23 | 24 | extension AppDelegate: UIApplicationDelegate { 25 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 26 | 27 | let navigationBar = UINavigationBar.appearance() 28 | navigationBar.barStyle = .Black 29 | navigationBar.tintColor = UIColor(white: 1, alpha: 0.6) 30 | 31 | window?.makeKeyAndVisible() 32 | return true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Pugs/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Pugs/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Pugs/ImageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCell.swift 3 | // Pugs 4 | // 5 | // Created by Sam Soffes on 9/14/15. 6 | // Copyright © 2015 Sam Soffes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ImageCellDelegate: class { 12 | func imageCell(imageCell: ImageCell, shouldShowImageURL imageURL: NSURL) 13 | func imageCell(imageCell: ImageCell, shouldCopyImageURL imageURL: NSURL) 14 | } 15 | 16 | class ImageCell: UICollectionViewCell { 17 | 18 | // MARK: - Properties 19 | 20 | weak var delegate: ImageCellDelegate? 21 | 22 | var imageURL: NSURL? { 23 | didSet { 24 | imageView.image = nil 25 | guard let URL = imageURL else { return } 26 | 27 | ImagesController.sharedController.fetch(URL) { [weak self] image in 28 | if let imageURL = self?.imageURL where imageURL == URL { 29 | self?.imageView.image = image 30 | } 31 | } 32 | } 33 | } 34 | 35 | private let imageView: UIImageView = { 36 | let view = UIImageView() 37 | view.translatesAutoresizingMaskIntoConstraints = false 38 | view.contentMode = .ScaleAspectFill 39 | view.clipsToBounds = true 40 | return view 41 | }() 42 | 43 | 44 | // MARK: - Initializers 45 | 46 | override init(frame: CGRect) { 47 | super.init(frame: frame) 48 | 49 | contentView.backgroundColor = UIColor(white: 0.1, alpha: 1) 50 | contentView.addSubview(imageView) 51 | 52 | NSLayoutConstraint.activateConstraints([ 53 | NSLayoutConstraint(item: imageView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 0), 54 | NSLayoutConstraint(item: imageView, attribute: .Trailing, relatedBy: .Equal, toItem: contentView, attribute: .Trailing, multiplier: 1, constant: 0), 55 | NSLayoutConstraint(item: imageView, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1, constant: 0), 56 | NSLayoutConstraint(item: imageView, attribute: .Leading, relatedBy: .Equal, toItem: contentView, attribute: .Leading, multiplier: 1, constant: 0) 57 | ]) 58 | 59 | contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "tap:")) 60 | contentView.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: "longPress:")) 61 | } 62 | 63 | required init?(coder aDecoder: NSCoder) { 64 | fatalError("init(coder:) has not been implemented") 65 | } 66 | 67 | 68 | // MARK: - Actions 69 | 70 | @objc private func tap(sender: AnyObject?) { 71 | guard let imageURL = imageURL else { return } 72 | delegate?.imageCell(self, shouldShowImageURL: imageURL) 73 | } 74 | 75 | @objc private func longPress(sender: AnyObject?) { 76 | guard let imageURL = imageURL else { return } 77 | delegate?.imageCell(self, shouldCopyImageURL: imageURL) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Pugs/ImagesController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagesController.swift 3 | // Pugs 4 | // 5 | // Created by Sam Soffes on 9/14/15. 6 | // Copyright © 2015 Sam Soffes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let sharedImagesController = ImagesController() 12 | 13 | class ImagesController { 14 | 15 | // MARK: - Types 16 | 17 | typealias Completion = UIImage? -> Void 18 | 19 | 20 | // MARK: - Properties 21 | 22 | static var sharedController: ImagesController { 23 | return sharedImagesController 24 | } 25 | 26 | private let cache = NSCache() 27 | private var fetching = [NSURL: [Completion]]() 28 | 29 | 30 | // MARK: - Fetching 31 | 32 | func fetch(imageURL: NSURL, completion: Completion) { 33 | if var completions = fetching[imageURL] { 34 | completions.append(completion) 35 | fetching[imageURL] = completions 36 | return 37 | } 38 | 39 | fetching[imageURL] = [completion] 40 | 41 | let key = imageURL.absoluteString 42 | if let cachedImage = cache.objectForKey(key) as? UIImage { 43 | dispatch_async(dispatch_get_main_queue()) { [weak self] in 44 | self?.fetching[imageURL]?.forEach { $0(cachedImage) } 45 | self?.fetching[imageURL] = nil 46 | } 47 | return 48 | } 49 | 50 | NSURLSession.sharedSession().dataTaskWithRequest(NSURLRequest(URL: imageURL)) { [weak self] data, _, _ in 51 | let image = data.flatMap { UIImage(data: $0) } 52 | 53 | if let image = image { 54 | self?.cache.setObject(image, forKey: key) 55 | } 56 | 57 | dispatch_async(dispatch_get_main_queue()) { 58 | self?.fetching[imageURL]?.forEach { $0(image) } 59 | self?.fetching[imageURL] = nil 60 | } 61 | }.resume() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Pugs/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIStatusBarStyle 39 | UIStatusBarStyleLightContent 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Pugs/PugViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PugViewController.swift 3 | // Pugs 4 | // 5 | // Created by Sam Soffes on 9/14/15. 6 | // Copyright © 2015 Sam Soffes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PugViewController: UIViewController { 12 | 13 | // MARK: - Properties 14 | 15 | let imageURL: NSURL 16 | 17 | private let imageView: UIImageView = { 18 | let view = UIImageView() 19 | view.translatesAutoresizingMaskIntoConstraints = false 20 | view.contentMode = .ScaleAspectFit 21 | view.clipsToBounds = true 22 | return view 23 | }() 24 | 25 | 26 | // MARK: - Initializers 27 | 28 | init(imageURL: NSURL) { 29 | self.imageURL = imageURL 30 | super.init(nibName: nil, bundle: nil) 31 | } 32 | 33 | required init?(coder aDecoder: NSCoder) { 34 | fatalError("init(coder:) has not been implemented") 35 | } 36 | 37 | 38 | // MARK: - UIViewController 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | 43 | title = "Pug" 44 | navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: "close:") 45 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Action, target: self, action: "share:") 46 | 47 | view.backgroundColor = .blackColor() 48 | view.addSubview(imageView) 49 | 50 | NSLayoutConstraint.activateConstraints([ 51 | NSLayoutConstraint(item: imageView, attribute: .Top, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Top, multiplier: 1, constant: 0), 52 | NSLayoutConstraint(item: imageView, attribute: .Trailing, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0), 53 | NSLayoutConstraint(item: imageView, attribute: .Bottom, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0), 54 | NSLayoutConstraint(item: imageView, attribute: .Leading, relatedBy: .LessThanOrEqual, toItem: view, attribute: .Leading, multiplier: 1, constant: 0), 55 | NSLayoutConstraint(item: imageView, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 0), 56 | NSLayoutConstraint(item: imageView, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0) 57 | ]) 58 | 59 | NSURLSession.sharedSession().dataTaskWithRequest(NSURLRequest(URL: imageURL)) { [weak self] data, _, _ in 60 | guard let data = data else { return } 61 | dispatch_async(dispatch_get_main_queue()) { 62 | self?.imageView.image = UIImage(data: data) 63 | } 64 | }.resume() 65 | } 66 | 67 | 68 | // MARK: - Actions 69 | 70 | @objc private func close(sender: AnyObject?) { 71 | dismissViewControllerAnimated(true, completion: nil) 72 | } 73 | 74 | @objc private func share(sender: AnyObject?) { 75 | let viewController = UIActivityViewController(activityItems: [imageURL], applicationActivities: nil) 76 | presentViewController(viewController, animated: true, completion: nil) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Pugs/PugsController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PugsController.swift 3 | // Pugs 4 | // 5 | // Created by Sam Soffes on 9/14/15. 6 | // Copyright © 2015 Sam Soffes. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol PugsControllerDelegate: class { 12 | func pugsController(controller: PugsController, didAddImageURLs imageURLs: [NSURL]) 13 | } 14 | 15 | class PugsController { 16 | 17 | // MARK: - Properties 18 | 19 | weak var delegate: PugsControllerDelegate? 20 | 21 | var imageURLs = [NSURL]() 22 | 23 | private(set) var loading = false 24 | 25 | private let session: NSURLSession 26 | 27 | 28 | // MARK: - Initializers 29 | 30 | init(delegate: PugsControllerDelegate? = nil, session: NSURLSession = NSURLSession.sharedSession()) { 31 | self.delegate = delegate 32 | self.session = session 33 | } 34 | 35 | 36 | // MARK: - Fetching 37 | 38 | func fetch() { 39 | if loading { 40 | return 41 | } 42 | 43 | guard let URL = NSURL(string: "https://pugme.herokuapp.com/bomb?count=50") else { return } 44 | loading = true 45 | 46 | let request = NSURLRequest(URL: URL) 47 | session.dataTaskWithRequest(request) { [weak self] data, _, _ in 48 | guard let data = data, 49 | json = try? NSJSONSerialization.JSONObjectWithData(data, options: []), 50 | dictionary = json as? [String: AnyObject], 51 | strings = dictionary["pugs"] as? [String] 52 | else { 53 | dispatch_async(dispatch_get_main_queue()) { 54 | self?.loading = false 55 | } 56 | return 57 | } 58 | 59 | // Sanitize and convert to URLs 60 | let URLs = strings.flatMap { $0.componentsSeparatedByString("\"").first }.flatMap { NSURL(string: $0) } 61 | 62 | guard let controller = self else { return } 63 | dispatch_async(dispatch_get_main_queue()) { 64 | controller.imageURLs += URLs 65 | controller.loading = false 66 | controller.delegate?.pugsController(controller, didAddImageURLs: URLs) 67 | } 68 | }.resume() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Pugs/PugsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PugsViewController.swift 3 | // Pugs 4 | // 5 | // Created by Sam Soffes on 9/14/15. 6 | // Copyright © 2015 Sam Soffes. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PugsViewController: UICollectionViewController { 12 | 13 | // MARK: - Properties 14 | 15 | let pugsController = PugsController() 16 | 17 | 18 | // MARK: - Initializers 19 | 20 | convenience init() { 21 | let layout = UICollectionViewFlowLayout() 22 | layout.minimumLineSpacing = 1 23 | layout.minimumInteritemSpacing = 1 24 | self.init(collectionViewLayout: layout) 25 | } 26 | 27 | 28 | // MARK: - UIViewController 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | title = "Pugs" 34 | view.backgroundColor = .blackColor() 35 | 36 | collectionView?.registerClass(ImageCell.self, forCellWithReuseIdentifier: "Image") 37 | 38 | pugsController.delegate = self 39 | pugsController.fetch() 40 | } 41 | 42 | override func viewDidLayoutSubviews() { 43 | super.viewDidLayoutSubviews() 44 | let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout 45 | let dimension = round((view.bounds.width - 3) / 4) 46 | 47 | layout?.itemSize = CGSize(width: dimension, height: dimension) 48 | } 49 | 50 | 51 | // MARK: - UICollectionViewDataSource 52 | 53 | override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 54 | return pugsController.imageURLs.count 55 | } 56 | 57 | override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 58 | guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Image", forIndexPath: indexPath) as? ImageCell else { 59 | return UICollectionViewCell() 60 | } 61 | 62 | cell.imageURL = pugsController.imageURLs[indexPath.row] 63 | cell.delegate = self 64 | 65 | return cell 66 | } 67 | 68 | 69 | // MARK: - UICollectionViewDelegate 70 | 71 | override func scrollViewDidScroll(scrollView: UIScrollView) { 72 | // If within half a screen of the bottom, load more. 73 | guard scrollView.contentOffset.y + scrollView.bounds.height > scrollView.contentSize.height - view.bounds.height / 2 else { return } 74 | 75 | pugsController.fetch() 76 | } 77 | } 78 | 79 | 80 | extension PugsViewController: PugsControllerDelegate { 81 | func pugsController(controller: PugsController, didAddImageURLs imageURLs: [NSURL]) { 82 | let end = controller.imageURLs.count 83 | let start = end - imageURLs.count 84 | 85 | var indexPaths = [NSIndexPath]() 86 | for i in start.. 6 | 7 | ## Building 8 | 9 | Xcode 7 is required. There are no external dependencies. 10 | 11 | 12 | ## Features 13 | 14 | * Gets pug photos from pugbomb API 15 | * Tap to view 16 | * Long-press to copy the URL 17 | 18 | 19 | ## Future Things 20 | 21 | Given more time, these things would be nice: 22 | 23 | * Caching 24 | * Swipe left and right 25 | * GIF support 26 | * App icon 27 | * Custom presentation controller for the modal 28 | * Gesture to dismiss the image back into the grid 29 | -------------------------------------------------------------------------------- /Screenshot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soffes/Pugs/20b06c8cb2e0623be93c75b9d3fe6b3435f6df0b/Screenshot@2x.png --------------------------------------------------------------------------------