├── .gitignore ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── TCPhotos500px.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── TCPhotos500px.xcworkspace └── contents.xcworkspacedata ├── TCPhotos500px ├── Classes │ ├── AppDelegate │ │ ├── TCAppDelegate.h │ │ └── TCAppDelegate.m │ ├── Controllers │ │ ├── TCCategoryListViewController.h │ │ ├── TCCategoryListViewController.m │ │ ├── TCPhotoModalViewController.h │ │ ├── TCPhotoModalViewController.m │ │ ├── TCThumbnailsViewController.h │ │ └── TCThumbnailsViewController.m │ ├── Models │ │ ├── TCPhoto.h │ │ ├── TCPhoto.m │ │ ├── TCPhotoStream.h │ │ ├── TCPhotoStream.m │ │ ├── TCPhotoStreamCategory.h │ │ ├── TCPhotoStreamCategory.m │ │ ├── TCPhotoStreamCategoryList.h │ │ ├── TCPhotoStreamCategoryList.m │ │ ├── TCPhotoStreamPage.h │ │ └── TCPhotoStreamPage.m │ └── Views │ │ ├── TCCategoryCell.h │ │ ├── TCCategoryCell.m │ │ ├── TCPhotoCell.h │ │ └── TCPhotoCell.m ├── Resources │ ├── Images │ │ ├── Icon-72.png │ │ └── Icon-72@2x.png │ └── Storyboards │ │ └── en.lproj │ │ └── MainStoryboard.storyboard ├── TCPhotos500px-Info.plist ├── TCPhotos500px-Prefix.pch ├── en.lproj │ └── InfoPlist.strings └── main.m └── Vendor └── DescriptionBuilder ├── DescriptionBuilder.h └── DescriptionBuilder.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | 19 | #CocoaPods 20 | Pods -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Lee Tze Cheun (http://leetzecheun.wordpress.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '6.0' 2 | pod '500px-iOS-api', '~> 1.0.3' 3 | pod 'SDWebImage', '~> 3.3' 4 | pod 'SVPullToRefresh', '~> 0.4.1' 5 | pod 'MBProgressHUD', '~> 0.7' 6 | 7 | # FlatUIKit has not updated their podspec yet. So, we're temporarily using 'head'. 8 | pod 'FlatUIKit', :head -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - 500px-iOS-api (1.0.3) 3 | - FlatUIKit (HEAD based on 1.2) 4 | - libwebp (0.3.0-rc7): 5 | - libwebp/dec 6 | - libwebp/dsp 7 | - libwebp/utils 8 | - libwebp/webp 9 | - libwebp/dec (0.3.0-rc7) 10 | - libwebp/dsp (0.3.0-rc7) 11 | - libwebp/utils (0.3.0-rc7) 12 | - libwebp/webp (0.3.0-rc7) 13 | - MBProgressHUD (0.7) 14 | - SDWebImage (3.3): 15 | - libwebp 16 | - SDWebImage/MapKit 17 | - SDWebImage/MapKit (3.3): 18 | - libwebp 19 | - SVPullToRefresh (0.4.1) 20 | 21 | DEPENDENCIES: 22 | - 500px-iOS-api (~> 1.0.3) 23 | - FlatUIKit (HEAD) 24 | - MBProgressHUD (~> 0.7) 25 | - SDWebImage (~> 3.3) 26 | - SVPullToRefresh (~> 0.4.1) 27 | 28 | SPEC CHECKSUMS: 29 | 500px-iOS-api: 720d14d188a03e92006ad1af5e65f9da6330b2a1 30 | FlatUIKit: 43b2acaa6f4ef57539b51b00e4ce62d506e33e11 31 | libwebp: 40517fa1fd3716eded73b4696c45fc0b66e4b414 32 | MBProgressHUD: 1fd8591a2216d5dec6de24e09187bb03bcbe9946 33 | SDWebImage: 79a8bac9ccf85b269100b294f7b43684dab8bf8a 34 | SVPullToRefresh: 61a0e4bd12bd6f8e3465909810b0fbeb1a28d5f2 35 | 36 | COCOAPODS: 0.22.3 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #TCPhotos500px 2 | 3 | TCPhotos500px is a sample iPad app that uses 500px's API to explore its beautiful photo streams. 4 | 5 | It displays the thumbnails from the photo stream in a standard `UICollectionView`. When a thumbnail is selected, it will zoom in to display the photo in an overlay modal view. Dismissing the photo will zoom it back out to the thumbnail. 6 | 7 |
8 | ![Thumbnails Screenshot](http://tclee.github.io/TCPhotos500px/images/Screenshot1.png "Thumbnails Collection View") 9 | 10 | ![Photo Modal Overlay Screenshot](http://tclee.github.io/TCPhotos500px/images/Screenshot2.png "Photo Modal Overlay View") 11 | 12 | ###Features 13 | * Asynchronously fetches a page of photos only when needed. 14 | * Custom overlay modal view to display the large size photo, instead of opening in full screen. 15 | * Tap-to-dismiss gesture for overlay modal view. 16 | * Custom zoom in and out transition animation for presenting and dismissing the overlay modal view. 17 | * "Pull-to-Refresh" to get the latest photos from 500px's photo stream. 18 | * Minimalist UI built using FlatUIKit. 19 | * Supports all device orientations. 20 | * Uses iOS 6's Auto Layout features to make it easier to transition to iOS 7. 21 | * Full source code provided and project files are organised clearly in a MVC structure. 22 | 23 | ###How to Build 24 |
25 |
Build Requirements
26 |
Xcode 4.6 or later, iOS 6.0 SDK or later, CocoaPods
27 |
Runtime Requirements
28 |
iOS 6.0 or later, iPad only
29 |
30 | 31 | 1. Install [CocoaPods](http://cocoapods.org/) (if you have not done so yet). 32 | 2. Run from the Terminal: ```pod install``` 33 | 3. Open ```TCPhotos500px.xcworkspace``` 34 | 35 | ###Open Source Libraries Used 36 | * 500px API - [https://github.com/500px/500px-iOS-api](https://github.com/500px/500px-iOS-api) 37 | * SDWebImage - [https://github.com/rs/SDWebImage](https://github.com/rs/SDWebImage) 38 | * MBProgressHUD - [https://github.com/jdg/MBProgressHUD](https://github.com/jdg/MBProgressHUD) 39 | * SVPullToRefresh - [https://github.com/samvermette/SVPullToRefresh](https://github.com/samvermette/SVPullToRefresh) 40 | * FlatUIKit - [https://github.com/Grouper/FlatUIKit](https://github.com/Grouper/FlatUIKit) 41 | 42 | ###See Also 43 | * 500px for Developers - [http://developers.500px.com/](http://developers.500px.com/) 44 | * 500px API Docs - [https://github.com/500px/api-documentation](https://github.com/500px/api-documentation) 45 | 46 | ###License 47 | This project's source code is provided for educational purposes only. See the LICENSE file for more info. 48 | -------------------------------------------------------------------------------- /TCPhotos500px.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 813BDD9D17925B9F0076E1A2 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 813BDD9C17925B9F0076E1A2 /* UIKit.framework */; }; 11 | 813BDD9F17925B9F0076E1A2 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 813BDD9E17925B9F0076E1A2 /* Foundation.framework */; }; 12 | 813BDDA117925B9F0076E1A2 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 813BDDA017925B9F0076E1A2 /* CoreGraphics.framework */; }; 13 | 813BDDA717925B9F0076E1A2 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 813BDDA517925B9F0076E1A2 /* InfoPlist.strings */; }; 14 | 813BDDA917925B9F0076E1A2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 813BDDA817925B9F0076E1A2 /* main.m */; }; 15 | 8148303C17AF4BAE00280DDB /* Icon-72.png in Resources */ = {isa = PBXBuildFile; fileRef = 8148303A17AF4BAE00280DDB /* Icon-72.png */; }; 16 | 8148303D17AF4BAE00280DDB /* Icon-72@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8148303B17AF4BAE00280DDB /* Icon-72@2x.png */; }; 17 | 819DF5EF17A666400004A5B3 /* TCPhotoModalViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 819DF5EE17A666400004A5B3 /* TCPhotoModalViewController.m */; }; 18 | 81DFAF59179D18180006371A /* TCAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF40179D18180006371A /* TCAppDelegate.m */; }; 19 | 81DFAF5A179D18180006371A /* TCCategoryListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF43179D18180006371A /* TCCategoryListViewController.m */; }; 20 | 81DFAF5B179D18180006371A /* TCThumbnailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF45179D18180006371A /* TCThumbnailsViewController.m */; }; 21 | 81DFAF5D179D18180006371A /* TCPhoto.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF4B179D18180006371A /* TCPhoto.m */; }; 22 | 81DFAF5E179D18180006371A /* TCPhotoStream.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF4D179D18180006371A /* TCPhotoStream.m */; }; 23 | 81DFAF5F179D18180006371A /* TCPhotoStreamCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF4F179D18180006371A /* TCPhotoStreamCategory.m */; }; 24 | 81DFAF60179D18180006371A /* TCPhotoStreamCategoryList.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF51179D18180006371A /* TCPhotoStreamCategoryList.m */; }; 25 | 81DFAF61179D18180006371A /* TCPhotoStreamPage.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF53179D18180006371A /* TCPhotoStreamPage.m */; }; 26 | 81DFAF62179D18180006371A /* TCCategoryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF56179D18180006371A /* TCCategoryCell.m */; }; 27 | 81DFAF63179D18180006371A /* TCPhotoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF58179D18180006371A /* TCPhotoCell.m */; }; 28 | 81DFAF68179D1CA60006371A /* DescriptionBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 81DFAF67179D1CA60006371A /* DescriptionBuilder.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 29 | 81DFAF72179D1E7E0006371A /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81DFAF70179D1E7E0006371A /* MainStoryboard.storyboard */; }; 30 | D716B54DF63E45988A0C8480 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A0CCE50FF24F4FCF9D9B234C /* libPods.a */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 7D4F4BA01244436B83C44033 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = SOURCE_ROOT; }; 35 | 813BDD9917925B9F0076E1A2 /* TCPhotos500px.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TCPhotos500px.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 813BDD9C17925B9F0076E1A2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 37 | 813BDD9E17925B9F0076E1A2 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 38 | 813BDDA017925B9F0076E1A2 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 39 | 813BDDA417925B9F0076E1A2 /* TCPhotos500px-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TCPhotos500px-Info.plist"; sourceTree = ""; }; 40 | 813BDDA617925B9F0076E1A2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 41 | 813BDDA817925B9F0076E1A2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 42 | 813BDDAA17925B9F0076E1A2 /* TCPhotos500px-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TCPhotos500px-Prefix.pch"; sourceTree = ""; }; 43 | 8148303A17AF4BAE00280DDB /* Icon-72.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72.png"; sourceTree = ""; }; 44 | 8148303B17AF4BAE00280DDB /* Icon-72@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-72@2x.png"; sourceTree = ""; }; 45 | 819DF5ED17A666400004A5B3 /* TCPhotoModalViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPhotoModalViewController.h; sourceTree = ""; }; 46 | 819DF5EE17A666400004A5B3 /* TCPhotoModalViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPhotoModalViewController.m; sourceTree = ""; }; 47 | 81DFAF3F179D18180006371A /* TCAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCAppDelegate.h; sourceTree = ""; }; 48 | 81DFAF40179D18180006371A /* TCAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCAppDelegate.m; sourceTree = ""; }; 49 | 81DFAF42179D18180006371A /* TCCategoryListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCCategoryListViewController.h; sourceTree = ""; }; 50 | 81DFAF43179D18180006371A /* TCCategoryListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCCategoryListViewController.m; sourceTree = ""; }; 51 | 81DFAF44179D18180006371A /* TCThumbnailsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCThumbnailsViewController.h; sourceTree = ""; }; 52 | 81DFAF45179D18180006371A /* TCThumbnailsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCThumbnailsViewController.m; sourceTree = ""; }; 53 | 81DFAF4A179D18180006371A /* TCPhoto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPhoto.h; sourceTree = ""; }; 54 | 81DFAF4B179D18180006371A /* TCPhoto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPhoto.m; sourceTree = ""; }; 55 | 81DFAF4C179D18180006371A /* TCPhotoStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPhotoStream.h; sourceTree = ""; }; 56 | 81DFAF4D179D18180006371A /* TCPhotoStream.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPhotoStream.m; sourceTree = ""; }; 57 | 81DFAF4E179D18180006371A /* TCPhotoStreamCategory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPhotoStreamCategory.h; sourceTree = ""; }; 58 | 81DFAF4F179D18180006371A /* TCPhotoStreamCategory.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPhotoStreamCategory.m; sourceTree = ""; }; 59 | 81DFAF50179D18180006371A /* TCPhotoStreamCategoryList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPhotoStreamCategoryList.h; sourceTree = ""; }; 60 | 81DFAF51179D18180006371A /* TCPhotoStreamCategoryList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPhotoStreamCategoryList.m; sourceTree = ""; }; 61 | 81DFAF52179D18180006371A /* TCPhotoStreamPage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPhotoStreamPage.h; sourceTree = ""; }; 62 | 81DFAF53179D18180006371A /* TCPhotoStreamPage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPhotoStreamPage.m; sourceTree = ""; }; 63 | 81DFAF55179D18180006371A /* TCCategoryCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCCategoryCell.h; sourceTree = ""; }; 64 | 81DFAF56179D18180006371A /* TCCategoryCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCCategoryCell.m; sourceTree = ""; }; 65 | 81DFAF57179D18180006371A /* TCPhotoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCPhotoCell.h; sourceTree = ""; }; 66 | 81DFAF58179D18180006371A /* TCPhotoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCPhotoCell.m; sourceTree = ""; }; 67 | 81DFAF66179D1CA60006371A /* DescriptionBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DescriptionBuilder.h; sourceTree = ""; }; 68 | 81DFAF67179D1CA60006371A /* DescriptionBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DescriptionBuilder.m; sourceTree = ""; }; 69 | 81DFAF71179D1E7E0006371A /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard.storyboard; sourceTree = ""; }; 70 | A0CCE50FF24F4FCF9D9B234C /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 71 | /* End PBXFileReference section */ 72 | 73 | /* Begin PBXFrameworksBuildPhase section */ 74 | 813BDD9617925B9F0076E1A2 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | 813BDD9D17925B9F0076E1A2 /* UIKit.framework in Frameworks */, 79 | 813BDD9F17925B9F0076E1A2 /* Foundation.framework in Frameworks */, 80 | 813BDDA117925B9F0076E1A2 /* CoreGraphics.framework in Frameworks */, 81 | D716B54DF63E45988A0C8480 /* libPods.a in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 813BDD9017925B9F0076E1A2 = { 89 | isa = PBXGroup; 90 | children = ( 91 | 813BDDA217925B9F0076E1A2 /* TCPhotos500px */, 92 | 81DFAF64179D1CA60006371A /* Vendor */, 93 | 813BDD9B17925B9F0076E1A2 /* Frameworks */, 94 | 813BDD9A17925B9F0076E1A2 /* Products */, 95 | 7D4F4BA01244436B83C44033 /* Pods.xcconfig */, 96 | ); 97 | sourceTree = ""; 98 | }; 99 | 813BDD9A17925B9F0076E1A2 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 813BDD9917925B9F0076E1A2 /* TCPhotos500px.app */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | 813BDD9B17925B9F0076E1A2 /* Frameworks */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 813BDD9C17925B9F0076E1A2 /* UIKit.framework */, 111 | 813BDD9E17925B9F0076E1A2 /* Foundation.framework */, 112 | 813BDDA017925B9F0076E1A2 /* CoreGraphics.framework */, 113 | A0CCE50FF24F4FCF9D9B234C /* libPods.a */, 114 | ); 115 | name = Frameworks; 116 | sourceTree = ""; 117 | }; 118 | 813BDDA217925B9F0076E1A2 /* TCPhotos500px */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 81DFAF3D179D18180006371A /* Classes */, 122 | 81DFAF6D179D1E7E0006371A /* Resources */, 123 | 813BDDA317925B9F0076E1A2 /* Supporting Files */, 124 | ); 125 | path = TCPhotos500px; 126 | sourceTree = ""; 127 | }; 128 | 813BDDA317925B9F0076E1A2 /* Supporting Files */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 813BDDA417925B9F0076E1A2 /* TCPhotos500px-Info.plist */, 132 | 813BDDA517925B9F0076E1A2 /* InfoPlist.strings */, 133 | 813BDDA817925B9F0076E1A2 /* main.m */, 134 | 813BDDAA17925B9F0076E1A2 /* TCPhotos500px-Prefix.pch */, 135 | ); 136 | name = "Supporting Files"; 137 | sourceTree = ""; 138 | }; 139 | 8148303917AF4BAE00280DDB /* Images */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 8148303A17AF4BAE00280DDB /* Icon-72.png */, 143 | 8148303B17AF4BAE00280DDB /* Icon-72@2x.png */, 144 | ); 145 | path = Images; 146 | sourceTree = ""; 147 | }; 148 | 81DFAF3D179D18180006371A /* Classes */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | 81DFAF3E179D18180006371A /* AppDelegate */, 152 | 81DFAF41179D18180006371A /* Controllers */, 153 | 81DFAF49179D18180006371A /* Models */, 154 | 81DFAF54179D18180006371A /* Views */, 155 | ); 156 | path = Classes; 157 | sourceTree = ""; 158 | }; 159 | 81DFAF3E179D18180006371A /* AppDelegate */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 81DFAF3F179D18180006371A /* TCAppDelegate.h */, 163 | 81DFAF40179D18180006371A /* TCAppDelegate.m */, 164 | ); 165 | path = AppDelegate; 166 | sourceTree = ""; 167 | }; 168 | 81DFAF41179D18180006371A /* Controllers */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 81DFAF42179D18180006371A /* TCCategoryListViewController.h */, 172 | 81DFAF43179D18180006371A /* TCCategoryListViewController.m */, 173 | 81DFAF44179D18180006371A /* TCThumbnailsViewController.h */, 174 | 81DFAF45179D18180006371A /* TCThumbnailsViewController.m */, 175 | 819DF5ED17A666400004A5B3 /* TCPhotoModalViewController.h */, 176 | 819DF5EE17A666400004A5B3 /* TCPhotoModalViewController.m */, 177 | ); 178 | path = Controllers; 179 | sourceTree = ""; 180 | }; 181 | 81DFAF49179D18180006371A /* Models */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | 81DFAF4A179D18180006371A /* TCPhoto.h */, 185 | 81DFAF4B179D18180006371A /* TCPhoto.m */, 186 | 81DFAF4C179D18180006371A /* TCPhotoStream.h */, 187 | 81DFAF4D179D18180006371A /* TCPhotoStream.m */, 188 | 81DFAF4E179D18180006371A /* TCPhotoStreamCategory.h */, 189 | 81DFAF4F179D18180006371A /* TCPhotoStreamCategory.m */, 190 | 81DFAF50179D18180006371A /* TCPhotoStreamCategoryList.h */, 191 | 81DFAF51179D18180006371A /* TCPhotoStreamCategoryList.m */, 192 | 81DFAF52179D18180006371A /* TCPhotoStreamPage.h */, 193 | 81DFAF53179D18180006371A /* TCPhotoStreamPage.m */, 194 | ); 195 | path = Models; 196 | sourceTree = ""; 197 | }; 198 | 81DFAF54179D18180006371A /* Views */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | 81DFAF55179D18180006371A /* TCCategoryCell.h */, 202 | 81DFAF56179D18180006371A /* TCCategoryCell.m */, 203 | 81DFAF57179D18180006371A /* TCPhotoCell.h */, 204 | 81DFAF58179D18180006371A /* TCPhotoCell.m */, 205 | ); 206 | path = Views; 207 | sourceTree = ""; 208 | }; 209 | 81DFAF64179D1CA60006371A /* Vendor */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | 81DFAF65179D1CA60006371A /* DescriptionBuilder */, 213 | ); 214 | path = Vendor; 215 | sourceTree = ""; 216 | }; 217 | 81DFAF65179D1CA60006371A /* DescriptionBuilder */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | 81DFAF66179D1CA60006371A /* DescriptionBuilder.h */, 221 | 81DFAF67179D1CA60006371A /* DescriptionBuilder.m */, 222 | ); 223 | path = DescriptionBuilder; 224 | sourceTree = ""; 225 | }; 226 | 81DFAF6D179D1E7E0006371A /* Resources */ = { 227 | isa = PBXGroup; 228 | children = ( 229 | 8148303917AF4BAE00280DDB /* Images */, 230 | 81DFAF6F179D1E7E0006371A /* Storyboards */, 231 | ); 232 | path = Resources; 233 | sourceTree = ""; 234 | }; 235 | 81DFAF6F179D1E7E0006371A /* Storyboards */ = { 236 | isa = PBXGroup; 237 | children = ( 238 | 81DFAF70179D1E7E0006371A /* MainStoryboard.storyboard */, 239 | ); 240 | path = Storyboards; 241 | sourceTree = ""; 242 | }; 243 | /* End PBXGroup section */ 244 | 245 | /* Begin PBXNativeTarget section */ 246 | 813BDD9817925B9F0076E1A2 /* TCPhotos500px */ = { 247 | isa = PBXNativeTarget; 248 | buildConfigurationList = 813BDDBC17925B9F0076E1A2 /* Build configuration list for PBXNativeTarget "TCPhotos500px" */; 249 | buildPhases = ( 250 | 590EF83DCA014CBAAAA8F08A /* Check Pods Manifest.lock */, 251 | 813BDD9517925B9F0076E1A2 /* Sources */, 252 | 813BDD9617925B9F0076E1A2 /* Frameworks */, 253 | 813BDD9717925B9F0076E1A2 /* Resources */, 254 | 2BFEC9D25BF04F508937DDB5 /* Copy Pods Resources */, 255 | ); 256 | buildRules = ( 257 | ); 258 | dependencies = ( 259 | ); 260 | name = TCPhotos500px; 261 | productName = TCPhotos500px; 262 | productReference = 813BDD9917925B9F0076E1A2 /* TCPhotos500px.app */; 263 | productType = "com.apple.product-type.application"; 264 | }; 265 | /* End PBXNativeTarget section */ 266 | 267 | /* Begin PBXProject section */ 268 | 813BDD9117925B9F0076E1A2 /* Project object */ = { 269 | isa = PBXProject; 270 | attributes = { 271 | CLASSPREFIX = TC; 272 | LastUpgradeCheck = 0460; 273 | ORGANIZATIONNAME = "Lee Tze Cheun"; 274 | }; 275 | buildConfigurationList = 813BDD9417925B9F0076E1A2 /* Build configuration list for PBXProject "TCPhotos500px" */; 276 | compatibilityVersion = "Xcode 3.2"; 277 | developmentRegion = English; 278 | hasScannedForEncodings = 0; 279 | knownRegions = ( 280 | en, 281 | ); 282 | mainGroup = 813BDD9017925B9F0076E1A2; 283 | productRefGroup = 813BDD9A17925B9F0076E1A2 /* Products */; 284 | projectDirPath = ""; 285 | projectRoot = ""; 286 | targets = ( 287 | 813BDD9817925B9F0076E1A2 /* TCPhotos500px */, 288 | ); 289 | }; 290 | /* End PBXProject section */ 291 | 292 | /* Begin PBXResourcesBuildPhase section */ 293 | 813BDD9717925B9F0076E1A2 /* Resources */ = { 294 | isa = PBXResourcesBuildPhase; 295 | buildActionMask = 2147483647; 296 | files = ( 297 | 813BDDA717925B9F0076E1A2 /* InfoPlist.strings in Resources */, 298 | 81DFAF72179D1E7E0006371A /* MainStoryboard.storyboard in Resources */, 299 | 8148303C17AF4BAE00280DDB /* Icon-72.png in Resources */, 300 | 8148303D17AF4BAE00280DDB /* Icon-72@2x.png in Resources */, 301 | ); 302 | runOnlyForDeploymentPostprocessing = 0; 303 | }; 304 | /* End PBXResourcesBuildPhase section */ 305 | 306 | /* Begin PBXShellScriptBuildPhase section */ 307 | 2BFEC9D25BF04F508937DDB5 /* Copy Pods Resources */ = { 308 | isa = PBXShellScriptBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | ); 312 | inputPaths = ( 313 | ); 314 | name = "Copy Pods Resources"; 315 | outputPaths = ( 316 | ); 317 | runOnlyForDeploymentPostprocessing = 0; 318 | shellPath = /bin/sh; 319 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 320 | showEnvVarsInLog = 0; 321 | }; 322 | 590EF83DCA014CBAAAA8F08A /* Check Pods Manifest.lock */ = { 323 | isa = PBXShellScriptBuildPhase; 324 | buildActionMask = 2147483647; 325 | files = ( 326 | ); 327 | inputPaths = ( 328 | ); 329 | name = "Check Pods Manifest.lock"; 330 | outputPaths = ( 331 | ); 332 | runOnlyForDeploymentPostprocessing = 0; 333 | shellPath = /bin/sh; 334 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 335 | showEnvVarsInLog = 0; 336 | }; 337 | /* End PBXShellScriptBuildPhase section */ 338 | 339 | /* Begin PBXSourcesBuildPhase section */ 340 | 813BDD9517925B9F0076E1A2 /* Sources */ = { 341 | isa = PBXSourcesBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | 813BDDA917925B9F0076E1A2 /* main.m in Sources */, 345 | 81DFAF59179D18180006371A /* TCAppDelegate.m in Sources */, 346 | 81DFAF5A179D18180006371A /* TCCategoryListViewController.m in Sources */, 347 | 81DFAF5B179D18180006371A /* TCThumbnailsViewController.m in Sources */, 348 | 81DFAF5D179D18180006371A /* TCPhoto.m in Sources */, 349 | 81DFAF5E179D18180006371A /* TCPhotoStream.m in Sources */, 350 | 81DFAF5F179D18180006371A /* TCPhotoStreamCategory.m in Sources */, 351 | 81DFAF60179D18180006371A /* TCPhotoStreamCategoryList.m in Sources */, 352 | 81DFAF61179D18180006371A /* TCPhotoStreamPage.m in Sources */, 353 | 81DFAF62179D18180006371A /* TCCategoryCell.m in Sources */, 354 | 81DFAF63179D18180006371A /* TCPhotoCell.m in Sources */, 355 | 81DFAF68179D1CA60006371A /* DescriptionBuilder.m in Sources */, 356 | 819DF5EF17A666400004A5B3 /* TCPhotoModalViewController.m in Sources */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | /* End PBXSourcesBuildPhase section */ 361 | 362 | /* Begin PBXVariantGroup section */ 363 | 813BDDA517925B9F0076E1A2 /* InfoPlist.strings */ = { 364 | isa = PBXVariantGroup; 365 | children = ( 366 | 813BDDA617925B9F0076E1A2 /* en */, 367 | ); 368 | name = InfoPlist.strings; 369 | sourceTree = ""; 370 | }; 371 | 81DFAF70179D1E7E0006371A /* MainStoryboard.storyboard */ = { 372 | isa = PBXVariantGroup; 373 | children = ( 374 | 81DFAF71179D1E7E0006371A /* en */, 375 | ); 376 | name = MainStoryboard.storyboard; 377 | sourceTree = ""; 378 | }; 379 | /* End PBXVariantGroup section */ 380 | 381 | /* Begin XCBuildConfiguration section */ 382 | 813BDDBA17925B9F0076E1A2 /* Debug */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | ALWAYS_SEARCH_USER_PATHS = NO; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_OBJC_ARC = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_EMPTY_BODY = YES; 391 | CLANG_WARN_ENUM_CONVERSION = YES; 392 | CLANG_WARN_INT_CONVERSION = YES; 393 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 394 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 395 | COPY_PHASE_STRIP = NO; 396 | GCC_C_LANGUAGE_STANDARD = gnu99; 397 | GCC_DYNAMIC_NO_PIC = NO; 398 | GCC_OPTIMIZATION_LEVEL = 0; 399 | GCC_PREPROCESSOR_DEFINITIONS = ( 400 | "DEBUG=1", 401 | "$(inherited)", 402 | ); 403 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 406 | GCC_WARN_UNUSED_VARIABLE = YES; 407 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 408 | ONLY_ACTIVE_ARCH = YES; 409 | SDKROOT = iphoneos; 410 | TARGETED_DEVICE_FAMILY = 2; 411 | }; 412 | name = Debug; 413 | }; 414 | 813BDDBB17925B9F0076E1A2 /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_SEARCH_USER_PATHS = NO; 418 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 419 | CLANG_CXX_LIBRARY = "libc++"; 420 | CLANG_ENABLE_OBJC_ARC = YES; 421 | CLANG_WARN_CONSTANT_CONVERSION = YES; 422 | CLANG_WARN_EMPTY_BODY = YES; 423 | CLANG_WARN_ENUM_CONVERSION = YES; 424 | CLANG_WARN_INT_CONVERSION = YES; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 427 | COPY_PHASE_STRIP = YES; 428 | GCC_C_LANGUAGE_STANDARD = gnu99; 429 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 430 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 431 | GCC_WARN_UNUSED_VARIABLE = YES; 432 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 433 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 434 | SDKROOT = iphoneos; 435 | TARGETED_DEVICE_FAMILY = 2; 436 | VALIDATE_PRODUCT = YES; 437 | }; 438 | name = Release; 439 | }; 440 | 813BDDBD17925B9F0076E1A2 /* Debug */ = { 441 | isa = XCBuildConfiguration; 442 | baseConfigurationReference = 7D4F4BA01244436B83C44033 /* Pods.xcconfig */; 443 | buildSettings = { 444 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 445 | GCC_PREFIX_HEADER = "TCPhotos500px/TCPhotos500px-Prefix.pch"; 446 | INFOPLIST_FILE = "TCPhotos500px/TCPhotos500px-Info.plist"; 447 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 448 | PRODUCT_NAME = "$(TARGET_NAME)"; 449 | WRAPPER_EXTENSION = app; 450 | }; 451 | name = Debug; 452 | }; 453 | 813BDDBE17925B9F0076E1A2 /* Release */ = { 454 | isa = XCBuildConfiguration; 455 | baseConfigurationReference = 7D4F4BA01244436B83C44033 /* Pods.xcconfig */; 456 | buildSettings = { 457 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 458 | GCC_PREFIX_HEADER = "TCPhotos500px/TCPhotos500px-Prefix.pch"; 459 | INFOPLIST_FILE = "TCPhotos500px/TCPhotos500px-Info.plist"; 460 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 461 | PRODUCT_NAME = "$(TARGET_NAME)"; 462 | WRAPPER_EXTENSION = app; 463 | }; 464 | name = Release; 465 | }; 466 | /* End XCBuildConfiguration section */ 467 | 468 | /* Begin XCConfigurationList section */ 469 | 813BDD9417925B9F0076E1A2 /* Build configuration list for PBXProject "TCPhotos500px" */ = { 470 | isa = XCConfigurationList; 471 | buildConfigurations = ( 472 | 813BDDBA17925B9F0076E1A2 /* Debug */, 473 | 813BDDBB17925B9F0076E1A2 /* Release */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | defaultConfigurationName = Release; 477 | }; 478 | 813BDDBC17925B9F0076E1A2 /* Build configuration list for PBXNativeTarget "TCPhotos500px" */ = { 479 | isa = XCConfigurationList; 480 | buildConfigurations = ( 481 | 813BDDBD17925B9F0076E1A2 /* Debug */, 482 | 813BDDBE17925B9F0076E1A2 /* Release */, 483 | ); 484 | defaultConfigurationIsVisible = 0; 485 | defaultConfigurationName = Release; 486 | }; 487 | /* End XCConfigurationList section */ 488 | }; 489 | rootObject = 813BDD9117925B9F0076E1A2 /* Project object */; 490 | } 491 | -------------------------------------------------------------------------------- /TCPhotos500px.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TCPhotos500px.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/AppDelegate/TCAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCAppDelegate.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/14/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TCAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/AppDelegate/TCAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCAppDelegate.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/14/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCAppDelegate.h" 10 | 11 | @implementation TCAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Only need the consumer key in our app because we only access public photos. 16 | [PXRequest setConsumerKey:@"J6iaaiJkAk30W3rQkH7vcfaJU3iGLEHPFPfF1K2T" 17 | consumerSecret:nil]; 18 | 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application 24 | { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 27 | } 28 | 29 | - (void)applicationDidEnterBackground:(UIApplication *)application 30 | { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application 36 | { 37 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | - (void)applicationDidBecomeActive:(UIApplication *)application 41 | { 42 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 43 | } 44 | 45 | - (void)applicationWillTerminate:(UIApplication *)application 46 | { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Controllers/TCCategoryListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCCategoryListViewController.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/20/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TCCategoryListViewController; 12 | @class TCPhotoStreamCategory; 13 | 14 | @protocol TCCategoryListViewControllerDelegate 15 | 16 | @required 17 | - (void)categoryListViewController:(TCCategoryListViewController *)categoryListViewController 18 | didSelectCategory:(TCPhotoStreamCategory *)category; 19 | @end 20 | 21 | @interface TCCategoryListViewController : UITableViewController 22 | 23 | @property(nonatomic, weak) id delegate; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Controllers/TCCategoryListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCCategoryListViewController.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/20/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCCategoryListViewController.h" 10 | #import "TCPhotoStreamCategoryList.h" 11 | #import "TCPhotoStreamCategory.h" 12 | #import "TCCategoryCell.h" 13 | 14 | // FlatUIKit 15 | #import "UIColor+FlatUI.h" 16 | 17 | @interface TCCategoryListViewController () 18 | 19 | @end 20 | 21 | #pragma mark - 22 | 23 | @implementation TCCategoryListViewController 24 | 25 | #pragma mark - View Events 26 | 27 | - (void)viewDidLoad 28 | { 29 | [super viewDidLoad]; 30 | 31 | self.tableView.backgroundColor = [UIColor cloudsColor]; 32 | } 33 | 34 | - (void)viewWillAppear:(BOOL)animated 35 | { 36 | [super viewWillAppear:animated]; 37 | 38 | // When this view is re-displayed in the popover, we will need to 39 | // scroll to the previously selected category to make it visible. 40 | NSUInteger selectedIndex = [[TCPhotoStreamCategoryList defaultList] indexOfSelectedCategory]; 41 | [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex inSection:0] 42 | atScrollPosition:UITableViewScrollPositionNone animated:NO]; 43 | } 44 | 45 | #pragma mark - Memory Management 46 | 47 | - (void)didReceiveMemoryWarning 48 | { 49 | [super didReceiveMemoryWarning]; 50 | 51 | // Dispose of any resources that can be recreated. 52 | [[TCPhotoStreamCategoryList defaultList] removeAllCategories]; 53 | } 54 | 55 | #pragma mark - Table View Data Source 56 | 57 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 58 | { 59 | return [[TCPhotoStreamCategoryList defaultList] categoryCount]; 60 | } 61 | 62 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 63 | { 64 | static NSString *CellIdentifier = @"CategoryCell"; 65 | TCCategoryCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]; 66 | 67 | TCPhotoStreamCategory *category = [[TCPhotoStreamCategoryList defaultList] categoryAtIndex:indexPath.row]; 68 | [cell setCategory:category]; 69 | return cell; 70 | } 71 | 72 | #pragma mark - Table View Delegate 73 | 74 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 75 | { 76 | TCPhotoStreamCategoryList *categoryList = [TCPhotoStreamCategoryList defaultList]; 77 | UITableViewCell *cell = nil; 78 | NSUInteger selectedIndex = [categoryList indexOfSelectedCategory]; 79 | 80 | // If user is selecting an already selected category, we do nothing. 81 | if (selectedIndex == indexPath.row) { 82 | // Cross fade the selection to let user know their touch was registered. 83 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 84 | return; 85 | } 86 | 87 | // Deselect currently selected category. 88 | cell = [tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedIndex inSection:0]]; 89 | [cell setSelected:NO animated:YES]; 90 | 91 | // Select new category. 92 | [categoryList selectCategoryAtIndex:indexPath.row]; 93 | cell = [tableView cellForRowAtIndexPath:indexPath]; 94 | [cell setSelected:YES animated:YES]; 95 | 96 | // Notify the delegate that a category has been selected from the table view. 97 | [self.delegate categoryListViewController:self 98 | didSelectCategory:[categoryList categoryAtIndex:indexPath.row]]; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Controllers/TCPhotoModalViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoModalViewController.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/29/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TCPhoto; 12 | 13 | /* 14 | Displays a large size version of the photo in an overlay modal view. 15 | */ 16 | @interface TCPhotoModalViewController : UIViewController 17 | 18 | /* 19 | Presents this modal view controller. 20 | It's view will be added as window's subview, so that it will sit above all other views. 21 | */ 22 | - (void)presentWithWindow:(UIWindow *)window photo:(TCPhoto *)photo sender:(UIView *)sender; 23 | 24 | /* 25 | Dismisses this modal view controller. 26 | */ 27 | - (void)dismiss; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Controllers/TCPhotoModalViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoModalViewController.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/29/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhotoModalViewController.h" 10 | #import "TCPhoto.h" 11 | 12 | #import "MBProgressHUD.h" 13 | 14 | @interface TCPhotoModalViewController () 15 | 16 | @property (weak, nonatomic) IBOutlet UIView *contentView; 17 | @property (weak, nonatomic) IBOutlet UIView *dimView; 18 | 19 | @property (weak, nonatomic) IBOutlet UIImageView *imageView; 20 | @property (weak, nonatomic) IBOutlet UILabel *photoTitleLabel; 21 | @property (weak, nonatomic) IBOutlet UILabel *userFullNameLabel; 22 | 23 | // Width and Height layout constraints will be adjusted dynamically to best aspect fit the photo. 24 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *widthLayoutConstraint; 25 | @property (weak, nonatomic) IBOutlet NSLayoutConstraint *heightLayoutConstraint; 26 | 27 | // Top and leading layout constraints are used for animation purposes. 28 | @property (nonatomic, strong) NSLayoutConstraint *topLayoutConstraint; 29 | @property (nonatomic, strong) NSLayoutConstraint *leadingLayoutConstraint; 30 | 31 | // Center the content view horizontally and vertically within its superview. 32 | // This outlet is a strong reference because it will be removed and added dynamically. 33 | // If we have declared it weak, it will be nil out when we remove these constraints. 34 | @property (nonatomic, strong) IBOutlet NSLayoutConstraint *horizontalCenterLayoutConstraint; 35 | @property (nonatomic, strong) IBOutlet NSLayoutConstraint *verticalCenterLayoutConstraint; 36 | 37 | // Tap anywhere on the photo modal view to dismiss it. 38 | @property (nonatomic, strong, readonly) UITapGestureRecognizer *tapToDismissGestureRecognizer; 39 | 40 | // Window -> Root View Controller -> View 41 | // We will match our view's transform, bounds and center to the root view. 42 | @property (nonatomic, weak) UIView *rootView; 43 | 44 | // The source view that triggered this modal view controller to be presented. 45 | // We need this view's rect when we perform the present and dismiss animation. 46 | @property (nonatomic, strong) UIView *sender; 47 | 48 | @property (nonatomic, strong) TCPhoto *photo; 49 | 50 | @end 51 | 52 | // Constants for the animation duration. 53 | static NSTimeInterval const kResizeAnimationDuration = 0.5f; 54 | static NSTimeInterval const kPresentAndDismissAnimationDuration = 1.0f; 55 | 56 | @implementation TCPhotoModalViewController 57 | 58 | @synthesize tapToDismissGestureRecognizer = _tapToDismissGestureRecognizer; 59 | 60 | #pragma mark - Lazy Properties 61 | 62 | // Tap to Dismiss Modal View - http://stackoverflow.com/a/6180584 63 | - (UITapGestureRecognizer *)tapToDismissGestureRecognizer 64 | { 65 | if (!_tapToDismissGestureRecognizer) { 66 | _tapToDismissGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapToDismiss:)]; 67 | _tapToDismissGestureRecognizer.numberOfTapsRequired = 1; 68 | 69 | // So the user can still interact with controls in the view. 70 | _tapToDismissGestureRecognizer.cancelsTouchesInView = NO; 71 | } 72 | return _tapToDismissGestureRecognizer; 73 | } 74 | 75 | #pragma mark - View Rotation Events 76 | 77 | // When the root view controller's view runs its rotation animation, we will also 78 | // match its rotation animation for a smoother transition. 79 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 80 | { 81 | [UIView animateWithDuration:duration animations:^{ 82 | [self synchronizeWithRootView]; 83 | 84 | // Resize view to aspect fit photo as rotation changes the view's bounds. 85 | [self sizeToAspectFitPhotoAnimated:NO]; 86 | 87 | // We need to re-layout the views, otherwise our views will be out of place. 88 | [self.view layoutIfNeeded]; 89 | }]; 90 | } 91 | 92 | #pragma mark - Synchronize Transform, Bounds and Center with Root View 93 | 94 | /* 95 | Rotate a UIView that is added to a UIWindow: 96 | [Good] Modifies transform and frame - http://stackoverflow.com/a/4960988 97 | [Better] Modifies transform and bounds/center - http://stackoverflow.com/a/14855914 98 | */ 99 | 100 | // We need to call this method to ensure that our view is always in sync with the 101 | // root view controller's view. This is because our view is added to a UIWindow. 102 | - (void)synchronizeWithRootView 103 | { 104 | self.view.transform = self.rootView.transform; 105 | 106 | // Transform invalidates the frame, so use bounds and center instead. 107 | self.view.bounds = self.rootView.bounds; 108 | self.view.center = self.rootView.center; 109 | } 110 | 111 | #pragma mark - Present and Dismiss Modal View Controller 112 | 113 | - (void)presentWithWindow:(UIWindow *)window photo:(TCPhoto *)photo sender:(UIView *)sender 114 | { 115 | // We must synchronize our view's transform, bounds and center with the root view 116 | // controller's view. This is because our view is directly added to a window. 117 | self.rootView = window.rootViewController.view; 118 | [self synchronizeWithRootView]; 119 | 120 | // Add our view as window's subview, so that it sits above all other views. 121 | [window addSubview:self.view]; 122 | 123 | // Initially, display the thumbnail of the photo. 124 | self.photo = photo; 125 | [self displayThumbnail]; 126 | 127 | // We need a reference to the sender to animate from and to. The sender's frame 128 | // can change when the view is rotated, so we cannot cache the sender's frame only. 129 | self.sender = sender; 130 | 131 | // Perform the present modal view controller animation. 132 | [self performPresentAnimation]; 133 | } 134 | 135 | - (void)dismiss 136 | { 137 | // End layout constraints for the dismiss animation. 138 | [self setDismissAnimationEndLayoutConstraints]; 139 | 140 | [UIView animateWithDuration:kPresentAndDismissAnimationDuration animations:^{ 141 | // Fade out the dim view and the content view. 142 | self.dimView.alpha = 0.0f; 143 | self.contentView.alpha = 0.0f; 144 | 145 | // Tell the view to perform layout, so that constraints changes will be animated. 146 | [self.view layoutIfNeeded]; 147 | } completion:^(BOOL finished) { 148 | // Reset the layout constraints for the next presentation. 149 | [self resetLayoutConstraints]; 150 | 151 | [self.view removeFromSuperview]; 152 | }]; 153 | } 154 | 155 | - (void)handleTapToDismiss:(UITapGestureRecognizer *)sender 156 | { 157 | if (UIGestureRecognizerStateRecognized == sender.state) { 158 | [self.view removeGestureRecognizer:sender]; 159 | [self dismiss]; 160 | } 161 | } 162 | 163 | #pragma mark - Present and Dismiss Animations 164 | 165 | /* 166 | Present modal view controller animation. 167 | */ 168 | - (void)performPresentAnimation 169 | { 170 | // Start animation layout constraints. 171 | [self setPresentAnimationStartLayoutConstraints]; 172 | 173 | // Tell the view to perform layout immediately as our constraints have changed. 174 | [self.view layoutIfNeeded]; 175 | 176 | // End animation layout constraints. 177 | [self setPresentAnimationEndLayoutConstraints]; 178 | 179 | // Dimming view and content view will have a fade-in animation. 180 | self.dimView.alpha = 0.0f; 181 | self.contentView.alpha = 0.0f; 182 | 183 | // Perform the animation. 184 | [UIView animateWithDuration:kPresentAndDismissAnimationDuration animations:^{ 185 | self.dimView.alpha = 0.6f; 186 | self.contentView.alpha = 1.0f; 187 | 188 | // Tell the view to perform layout, so that constraints changes will be animated. 189 | [self.view layoutIfNeeded]; 190 | } completion:^(BOOL finished) { 191 | // Add tap to dismiss gesture only after the animation is completed. 192 | // Otherwise, user can dismiss the view in the middle of an animation. 193 | [self.view addGestureRecognizer:self.tapToDismissGestureRecognizer]; 194 | 195 | // First animation completed. Chain the second animation to 196 | // load and display photo. 197 | [self displayPhoto]; 198 | }]; 199 | } 200 | 201 | /* 202 | Creates a NSLayoutAttributeTop or NSLayoutAttributeLeading layout constraint with 203 | the given constant. 204 | 205 | We create a new top or leading layout constraint rather than just changing the constant 206 | because when the view is rotated the top and leading constraint becomes invalid. 207 | Instead, we have to create the constraint using the Visual Format Language. 208 | 209 | Reference: 210 | NSInternalInconsistencyException, Reason: "Autolayout doesn't support crossing rotational bounds transforms with edge layout constraints, such as right, left, top, bottom..." 211 | http://stackoverflow.com/q/15139909 212 | */ 213 | - (NSLayoutConstraint *)constraintWithAttribute:(NSLayoutAttribute)attribute 214 | constant:(CGFloat)constant 215 | { 216 | NSParameterAssert(NSLayoutAttributeTop == attribute || NSLayoutAttributeLeading == attribute); 217 | 218 | UIView *contentView = self.contentView; 219 | NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView); 220 | 221 | NSString *orientation = (attribute == NSLayoutAttributeTop) ? @"V" : @"H"; 222 | NSString *format = [[NSString alloc] initWithFormat:@"%@:|-(%.2f)-[contentView]", orientation, constant]; 223 | 224 | NSArray *layoutConstraints = [NSLayoutConstraint constraintsWithVisualFormat:format 225 | options:0 226 | metrics:nil 227 | views:viewsDictionary]; 228 | 229 | NSAssert([layoutConstraints count] == 1, @"There should only be one layout constraint in the array when creating the constraints array from VFL."); 230 | return layoutConstraints[0]; 231 | } 232 | 233 | /* 234 | Start layout constraints for the present animation. 235 | */ 236 | - (void)setPresentAnimationStartLayoutConstraints 237 | { 238 | // Use the sender's rect to determine the start point and size for the content view. 239 | // It is possible that the sender's rect will be modified during rotation, so we will 240 | // always have to ask for the most up-to-date sender's rect. 241 | CGRect senderRect = [self.view convertRect:self.sender.bounds fromView:self.sender]; 242 | 243 | // Remove the center layout constraints temporarily. We're going to use top and 244 | // leading constraints to set the content view's origin (x, y). 245 | [self.view removeConstraints:@[self.horizontalCenterLayoutConstraint, 246 | self.verticalCenterLayoutConstraint]]; 247 | 248 | // Create and add the top and leading layout constraints based on sender's origin (x, y). 249 | self.leadingLayoutConstraint = [self constraintWithAttribute:NSLayoutAttributeLeading 250 | constant:senderRect.origin.x]; 251 | self.topLayoutConstraint = [self constraintWithAttribute:NSLayoutAttributeTop 252 | constant:senderRect.origin.y]; 253 | [self.view addConstraints:@[self.topLayoutConstraint, 254 | self.leadingLayoutConstraint]]; 255 | 256 | // Make the content view the same size as the sender. 257 | self.widthLayoutConstraint.constant = senderRect.size.width; 258 | self.heightLayoutConstraint.constant = senderRect.size.height; 259 | 260 | // Let the view know that we've modified the constraints. 261 | [self.view setNeedsUpdateConstraints]; 262 | } 263 | 264 | /* 265 | End layout constraints for the present animation. 266 | */ 267 | - (void)setPresentAnimationEndLayoutConstraints 268 | { 269 | // Content view's default size before photo is loaded. It will be resized to 270 | // aspect fit the photo when the photo has been loaded. 271 | self.widthLayoutConstraint.constant = 450.0f; 272 | self.heightLayoutConstraint.constant = 450.0f; 273 | 274 | // Content view will be centered horizontally and vertically within its superview. 275 | [self.view removeConstraints:@[self.topLayoutConstraint, 276 | self.leadingLayoutConstraint]]; 277 | [self.view addConstraints:@[self.verticalCenterLayoutConstraint, 278 | self.horizontalCenterLayoutConstraint]]; 279 | 280 | // Let the view know that we've modified the constraints. 281 | [self.view setNeedsUpdateConstraints]; 282 | } 283 | 284 | /* 285 | Start layout constraints for the dismiss animation is the same as end layout 286 | constraints for the present animation. So, there's nothing to do here. 287 | */ 288 | 289 | /* 290 | End layout constraints for the dismiss animation. 291 | */ 292 | - (void)setDismissAnimationEndLayoutConstraints 293 | { 294 | // The dismiss animation is the reverse of the present animation. 295 | // So the start of the present animation is the end of the dismiss animation. 296 | [self setPresentAnimationStartLayoutConstraints]; 297 | } 298 | 299 | /* 300 | Reset the layout constraints for the next presentation. 301 | This method is called when the dismiss animation has completed. 302 | */ 303 | - (void)resetLayoutConstraints 304 | { 305 | [self.view removeConstraints:@[self.topLayoutConstraint, 306 | self.leadingLayoutConstraint]]; 307 | 308 | [self.view addConstraints:@[self.horizontalCenterLayoutConstraint, 309 | self.verticalCenterLayoutConstraint]]; 310 | } 311 | 312 | 313 | #pragma mark - Display Photo Details on View 314 | 315 | // Display the photo's thumbnail on the image view. 316 | - (void)displayThumbnail 317 | { 318 | // Photo title and user's full name. 319 | self.photoTitleLabel.text = self.photo.title; 320 | self.userFullNameLabel.text = self.photo.userFullName; 321 | 322 | UIImage *thumbnail = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:[self.photo.thumbnailURL absoluteString]]; 323 | self.imageView.image = thumbnail; 324 | } 325 | 326 | // Display the photo model's contents on the view. 327 | - (void)displayPhoto 328 | { 329 | UIImage *photoImage = [[SDImageCache sharedImageCache] imageFromMemoryCacheForKey:[self.photo.photoURL absoluteString]]; 330 | 331 | // If photo is in memory cache, we can just display the image immediately. 332 | // Else we will have to load the photo in asynchronously. 333 | if (photoImage) { 334 | self.imageView.image = photoImage; 335 | [self sizeToAspectFitPhotoAnimated:YES]; 336 | } else { 337 | [self loadPhoto]; 338 | } 339 | } 340 | 341 | // Load the photo asynchronously and display it on the image view. 342 | - (void)loadPhoto 343 | { 344 | [MBProgressHUD showHUDAddedTo:self.imageView animated:YES]; 345 | 346 | // We'll display the low resolution thumbnail as a placeholder while we 347 | // load the larger size photo in the background. 348 | UIImage *thumbnail = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:[self.photo.thumbnailURL absoluteString]]; 349 | 350 | // Load image asynchronously from network or disk cache. 351 | [self.imageView setImageWithURL:self.photo.photoURL placeholderImage:thumbnail options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) { 352 | if (image) { 353 | [MBProgressHUD hideHUDForView:self.imageView animated:YES]; 354 | [self sizeToAspectFitPhotoAnimated:YES]; 355 | } else if (error) { 356 | NSLog(@"[SDWebImage Error] - %@", [error localizedDescription]); 357 | } 358 | }]; 359 | } 360 | 361 | #pragma mark - Resize to Aspect Fit Photo within Screen 362 | 363 | /* 364 | Resizes the view to aspect fit the photo. 365 | */ 366 | - (void)sizeToAspectFitPhotoAnimated:(BOOL)animated 367 | { 368 | // If there is no image, there's no need to resize view. 369 | if (!self.imageView.image) { 370 | return; 371 | } 372 | 373 | // Original photo size before any scaling. 374 | CGSize photoSize = self.imageView.image.size; 375 | 376 | // Calculate scale factor required to aspect fit the photo. 377 | CGFloat scaleFactor = [self scaleFactorToAspectFitPhotoWithSize:photoSize]; 378 | 379 | // Create the view's new bounds from the scaled size. 380 | CGSize scaledPhotoSize = CGSizeMake(floorf(photoSize.width * scaleFactor), 381 | floorf(photoSize.height * scaleFactor)); 382 | 383 | // Animate the layout constraints changing, if animation is wanted. 384 | if (animated) { 385 | // Animating NSLayoutConstraints: 386 | // http://stackoverflow.com/a/12926646 387 | // http://stackoverflow.com/q/12622424 388 | 389 | self.widthLayoutConstraint.constant = scaledPhotoSize.width; 390 | self.heightLayoutConstraint.constant = scaledPhotoSize.height; 391 | [UIView animateWithDuration:kResizeAnimationDuration animations:^{ 392 | [self.view layoutIfNeeded]; 393 | }]; 394 | } else { 395 | self.widthLayoutConstraint.constant = scaledPhotoSize.width; 396 | self.heightLayoutConstraint.constant = scaledPhotoSize.height; 397 | } 398 | } 399 | 400 | // The padding from the view's edge to the window's edge. 401 | static CGFloat const kViewToWindowPadding = 60.0f; 402 | 403 | /* 404 | Calculate the scale factor to resize view so that it aspect fits the 405 | photo within the window bounds (with some margin spacing). 406 | */ 407 | - (CGFloat)scaleFactorToAspectFitPhotoWithSize:(CGSize)photoSize 408 | { 409 | // Use the root view's bounds so that it takes into account the 410 | // device orientation. 411 | CGSize viewSize = self.rootView.bounds.size; 412 | 413 | // Include a padding space, so that scaled view will not be too close to 414 | // the window's edge. 415 | CGSize photoWithPaddingSize = CGSizeMake(photoSize.width + kViewToWindowPadding, 416 | photoSize.height + kViewToWindowPadding); 417 | 418 | // Scale factor to fit photo's width. 419 | CGFloat widthScaleFactor = 1.0f; 420 | if (photoWithPaddingSize.width > viewSize.width) { 421 | widthScaleFactor = viewSize.width / photoWithPaddingSize.width; 422 | } 423 | 424 | // Scale factor to fit photo's height. 425 | CGFloat heightScaleFactor = 1.0f; 426 | if (photoWithPaddingSize.height > viewSize.height) { 427 | heightScaleFactor = viewSize.height / photoWithPaddingSize.height; 428 | } 429 | 430 | // Return the scale factor that will be needed to fit both photo's width and height. 431 | return fminf(widthScaleFactor, heightScaleFactor); 432 | } 433 | 434 | @end 435 | 436 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Controllers/TCThumbnailsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPopularViewController.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/14/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TCCategoryListViewController.h" 12 | 13 | /* 14 | Displays a grid of thumbnails using UICollectionView. 15 | Thumbnails are loaded asynchronously and are lazily loaded for the fastest 16 | performance. 17 | */ 18 | @interface TCThumbnailsViewController : UICollectionViewController 19 | 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Controllers/TCThumbnailsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPopularViewController.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/14/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCThumbnailsViewController.h" 10 | #import "TCPhotoModalViewController.h" 11 | #import "TCPhotoCell.h" 12 | #import "TCPhotoStream.h" 13 | #import "TCPhotoStreamCategory.h" 14 | #import "TCPhoto.h" 15 | 16 | // FlatUIKit 17 | #import "UIColor+FlatUI.h" 18 | #import "UINavigationBar+FlatUI.h" 19 | #import "UIBarButtonItem+FlatUI.h" 20 | #import "UIPopoverController+FlatUI.h" 21 | #import "FUISegmentedControl.h" 22 | 23 | // Storyboard Segue IDs 24 | static NSString * const kSegueIdentifierCategoryPopover = @"showCategoryList"; 25 | 26 | @interface TCThumbnailsViewController () 27 | 28 | @property (nonatomic, weak) IBOutlet FUISegmentedControl *featureSegmentedControl; 29 | @property (nonatomic, weak) IBOutlet UIBarButtonItem *categoryBarButtonItem; 30 | 31 | // Show a popover for the list of categories to filter the photo stream. 32 | @property (nonatomic, weak) UIPopoverController *categoryListPopoverController; 33 | 34 | // Show the large size photo in an overlay modal view. 35 | @property (nonatomic, strong, readonly) TCPhotoModalViewController *photoModalViewController; 36 | 37 | // Photo Stream model that is presented on the collection view. 38 | @property (nonatomic, strong) TCPhotoStream *photoStream; 39 | 40 | // Array of all supported photo stream features. 41 | @property (nonatomic, strong, readonly) NSArray *photoStreamFeatures; 42 | 43 | // User selected a new photo stream feature. 44 | - (IBAction)featureChanged:(id)sender; 45 | 46 | @end 47 | 48 | #pragma mark - 49 | 50 | @implementation TCThumbnailsViewController 51 | 52 | @synthesize photoModalViewController = _photoModalViewController; 53 | @synthesize photoStreamFeatures = _photoStreamFeatures; 54 | 55 | #pragma mark - Lazy Properties 56 | 57 | - (TCPhotoModalViewController *)photoModalViewController 58 | { 59 | if (!_photoModalViewController) { 60 | _photoModalViewController = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([TCPhotoModalViewController class])]; 61 | } 62 | return _photoModalViewController; 63 | } 64 | 65 | - (NSArray *)photoStreamFeatures 66 | { 67 | if (!_photoStreamFeatures) { 68 | _photoStreamFeatures = @[@(PXAPIHelperPhotoFeaturePopular), 69 | @(PXAPIHelperPhotoFeatureEditors), 70 | @(PXAPIHelperPhotoFeatureUpcoming), 71 | @(PXAPIHelperPhotoFeatureFreshToday)]; 72 | } 73 | return _photoStreamFeatures; 74 | } 75 | 76 | #pragma mark - View Events 77 | 78 | - (void)viewDidLoad 79 | { 80 | [super viewDidLoad]; 81 | 82 | // Configure and customize the control's styles. 83 | [self configureNavigationBar]; 84 | [self configureSegmentedControl]; 85 | [self configureBarButtonItem]; 86 | 87 | // Allow user to "Pull-to-Refresh" load in new photos. 88 | [self addPullToRefreshView]; 89 | 90 | [self addDismissPopoverGestureToNavigationBar]; 91 | } 92 | 93 | #pragma mark - Tap Navigation Bar to Dismiss Popover 94 | 95 | /* 96 | Dismiss popover when user taps on the navigation bar. By default, navigation bar is 97 | added as one of popover's passthrough views. 98 | */ 99 | - (void)addDismissPopoverGestureToNavigationBar 100 | { 101 | UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dismissPopover:)]; 102 | tapGestureRecognizer.numberOfTapsRequired = 1; 103 | tapGestureRecognizer.cancelsTouchesInView = NO; 104 | [self.navigationController.navigationBar addGestureRecognizer:tapGestureRecognizer]; 105 | } 106 | 107 | - (void)dismissPopover:(UITapGestureRecognizer *)tapGestureRecognizer 108 | { 109 | [self.categoryListPopoverController dismissPopoverAnimated:YES]; 110 | } 111 | 112 | #pragma mark - Customize Views UIAppearance 113 | 114 | - (void)configureNavigationBar 115 | { 116 | [self.navigationController.navigationBar configureFlatNavigationBarWithColor:[UIColor blackColor]]; 117 | } 118 | 119 | - (void)configureSegmentedControl 120 | { 121 | // Resize the segmented control here, otherwise it will get squished by the autolayout. 122 | static const CGFloat kSegmentedControlWidth = 500.0f; 123 | CGRect currentBounds = self.featureSegmentedControl.bounds; 124 | self.featureSegmentedControl.bounds = CGRectMake(currentBounds.origin.x,currentBounds.origin.y, 125 | kSegmentedControlWidth, currentBounds.size.height); 126 | 127 | self.featureSegmentedControl.selectedFont = [UIFont systemFontOfSize:20.0f]; 128 | self.featureSegmentedControl.selectedFontColor = [UIColor whiteColor]; 129 | self.featureSegmentedControl.deselectedFont = [UIFont systemFontOfSize:20.0f]; 130 | self.featureSegmentedControl.deselectedFontColor = [UIColor grayColor]; 131 | self.featureSegmentedControl.selectedColor = [UIColor darkGrayColor]; 132 | self.featureSegmentedControl.deselectedColor = [UIColor clearColor]; 133 | self.featureSegmentedControl.dividerColor = [UIColor clearColor]; 134 | self.featureSegmentedControl.cornerRadius = 0.0f; 135 | 136 | // Adjust segment widths based on their content widths. 137 | self.featureSegmentedControl.apportionsSegmentWidthsByContent = YES; 138 | } 139 | 140 | - (void)configureBarButtonItem 141 | { 142 | [self.categoryBarButtonItem configureFlatButtonWithColor:[UIColor alizarinColor] 143 | highlightedColor:[UIColor pomegranateColor] 144 | cornerRadius:3.0f]; 145 | 146 | NSDictionary *textAttributes = @{UITextAttributeFont: [UIFont systemFontOfSize:16.0f], 147 | UITextAttributeTextColor: [UIColor whiteColor], 148 | UITextAttributeTextShadowColor: [UIColor clearColor], 149 | UITextAttributeTextShadowOffset: [NSValue valueWithUIOffset:UIOffsetMake(0.0f, 0.0f)]}; 150 | [self.categoryBarButtonItem setTitleTextAttributes:textAttributes 151 | forState:UIControlStateNormal]; 152 | } 153 | 154 | - (void)configurePopover 155 | { 156 | [self.categoryListPopoverController configureFlatPopoverWithBackgroundColor:[UIColor silverColor] 157 | cornerRadius:6.0f]; 158 | } 159 | 160 | #pragma mark - View Rotation Events 161 | 162 | /* 163 | We need to manually forward the view rotation events to TCPhotoModalViewController 164 | because it is not attached/related to any view controllers (it's view is added to 165 | UIWindow). 166 | Remember to check if TCPhotoModalViewController's view has been added to a window 167 | before forwarding the rotation events. 168 | */ 169 | 170 | - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 171 | { 172 | if (self.photoModalViewController.view.window) { 173 | [self.photoModalViewController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; 174 | } 175 | } 176 | 177 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration 178 | { 179 | if (self.photoModalViewController.view.window) { 180 | [self.photoModalViewController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration]; 181 | } 182 | } 183 | 184 | - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation 185 | { 186 | if (self.photoModalViewController.view.window) { 187 | [self.photoModalViewController didRotateFromInterfaceOrientation:fromInterfaceOrientation]; 188 | } 189 | } 190 | 191 | #pragma mark - Pull to Refresh 192 | 193 | - (void)addPullToRefreshView 194 | { 195 | __weak typeof(self) weakSelf = self; 196 | 197 | // When user pulls to refresh, we reload the photo stream with the currently 198 | // selected feature and category. 199 | [self.collectionView addPullToRefreshWithActionHandler:^{ 200 | __strong typeof(self) strongSelf = weakSelf; 201 | 202 | [strongSelf reloadPhotoStreamForFeature:strongSelf.photoStream.feature 203 | category:strongSelf.photoStream.category]; 204 | 205 | [strongSelf.collectionView.pullToRefreshView stopAnimating]; 206 | }]; 207 | 208 | // SVPullToRefreshView will be nil until after we call addPullToRefreshWithActionHandler: 209 | self.collectionView.pullToRefreshView.textColor = [UIColor whiteColor]; 210 | self.collectionView.pullToRefreshView.arrowColor = [UIColor whiteColor]; 211 | } 212 | 213 | #pragma mark - Photo Stream Model 214 | 215 | - (TCPhotoStream *)photoStream 216 | { 217 | if (!_photoStream) { 218 | // Initialize with a default photo stream for the first time. 219 | // Subsequently, user can choose the photo stream they want to view. 220 | _photoStream = [[TCPhotoStream alloc] initWithFeature:kPXAPIHelperDefaultFeature 221 | category:PXAPIHelperUnspecifiedCategory]; 222 | } 223 | return _photoStream; 224 | } 225 | 226 | - (void)reloadPhotoStreamForFeature:(PXAPIHelperPhotoFeature)feature 227 | category:(PXPhotoModelCategory)category 228 | { 229 | // Creates a new photo stream (discarding the old one) for the given feature and category. 230 | self.photoStream = [[TCPhotoStream alloc] initWithFeature:feature category:category]; 231 | 232 | // Call reloadData to trigger the data source methods to lazily fetch the photo 233 | // stream's pages. 234 | [self.collectionView reloadData]; 235 | 236 | // Scroll back to top when reloading a new photo stream. 237 | [self.collectionView setContentOffset:CGPointZero animated:NO]; 238 | } 239 | 240 | #pragma mark - UICollectionView Data Source 241 | 242 | - (NSInteger)collectionView:(UICollectionView*)collectionView numberOfItemsInSection:(NSInteger)section 243 | { 244 | // While photos are still loading, we show a default number of placeholders 245 | // initially. After photos have finished loading, we will replace these 246 | // placeholders with actual number of photos. 247 | NSInteger photoCount = [self.photoStream photoCount]; 248 | return TCPhotoStreamNoPhotoCount == photoCount ? kPXAPIHelperDefaultResultsPerPage : photoCount; 249 | } 250 | 251 | // Return the photo cell view. 252 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView 253 | cellForItemAtIndexPath:(NSIndexPath *)indexPath 254 | { 255 | static NSString * const CellIdentifier = @"PhotoCell"; 256 | 257 | // Draw a border around the cell view. 258 | TCPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath]; 259 | cell.imageView.image = nil; 260 | [cell.activityIndicator startAnimating]; 261 | 262 | // Get the photo from the cache if available; otherwise async fetch from network. 263 | TCPhoto *photo = [self.photoStream photoAtIndex:indexPath.item completion:^(TCPhoto *photo, NSError *error) { 264 | if (photo) { 265 | [collectionView reloadData]; 266 | } else if (error) { 267 | NSLog(@"[500px Error] - %@", [error localizedDescription]); 268 | } 269 | }]; 270 | 271 | // Display photo on the cell, if photo is available. 272 | [cell setPhoto:photo]; 273 | return cell; 274 | } 275 | 276 | #pragma mark - UICollectionView Delegate 277 | 278 | // User selected a thumbnail. Show the large size photo. 279 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath 280 | { 281 | TCPhotoCell *photoCell = (TCPhotoCell *)[collectionView cellForItemAtIndexPath:indexPath]; 282 | 283 | // Make sure we have a photo to display before presenting it on the modal view. 284 | if (photoCell.photo) { 285 | [self.photoModalViewController presentWithWindow:self.view.window 286 | photo:photoCell.photo 287 | sender:photoCell]; 288 | } 289 | } 290 | 291 | #pragma mark - TCCategoryListViewController Delegate 292 | 293 | // Dismiss popover and reload photo stream with selected category. 294 | - (void)categoryListViewController:(TCCategoryListViewController *)categoryListViewController 295 | didSelectCategory:(TCPhotoStreamCategory *)category 296 | { 297 | [self.categoryListPopoverController dismissPopoverAnimated:YES]; 298 | 299 | self.categoryBarButtonItem.title = category.title; 300 | 301 | [self reloadPhotoStreamForFeature:self.photoStream.feature 302 | category:category.value]; 303 | } 304 | 305 | #pragma mark - Storyboard Segues 306 | 307 | - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender 308 | { 309 | if ([identifier isEqualToString:kSegueIdentifierCategoryPopover]) { 310 | // If the popover is already showing, we'll dismiss it and not show it again. 311 | // Otherwise, we will have multiple popovers stacked over each other. 312 | if (self.categoryListPopoverController) { 313 | [self.categoryListPopoverController dismissPopoverAnimated:YES]; 314 | return NO; 315 | } 316 | } 317 | 318 | // By default, we want to perform the segue. 319 | return YES; 320 | } 321 | 322 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 323 | { 324 | if ([[segue identifier] isEqualToString:kSegueIdentifierCategoryPopover]) { 325 | // Save a reference to this segue's popover controller. 326 | // We will need to dismiss it to dismiss the popover later. 327 | self.categoryListPopoverController = [(UIStoryboardPopoverSegue *)segue popoverController]; 328 | [self configurePopover]; 329 | 330 | // Set up ourself as the delegate, so that we know when a category is selected from the list. 331 | TCCategoryListViewController *categoryListViewController = [segue destinationViewController]; 332 | categoryListViewController.delegate = self; 333 | } 334 | } 335 | 336 | #pragma mark - IBAction 337 | 338 | // Reload photo stream with new selected feature. 339 | - (IBAction)featureChanged:(id)sender 340 | { 341 | NSInteger selectedSegmentIndex = [self.featureSegmentedControl selectedSegmentIndex]; 342 | PXAPIHelperPhotoFeature selectedFeature = [self.photoStreamFeatures[selectedSegmentIndex] integerValue]; 343 | 344 | [self reloadPhotoStreamForFeature:selectedFeature 345 | category:self.photoStream.category]; 346 | } 347 | 348 | @end 349 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhoto.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhoto.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/17/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | @class TCPhotoStreamPage; 10 | 11 | /* 12 | This model represents a photo within a page. 13 | */ 14 | @interface TCPhoto : NSObject 15 | 16 | /* 17 | Weak reference to the page that owns this photo. 18 | */ 19 | @property (nonatomic, weak, readonly) TCPhotoStreamPage *photoStreamPage; 20 | 21 | @property (nonatomic, copy, readonly) NSURL *thumbnailURL; 22 | @property (nonatomic, copy, readonly) NSURL *photoURL; 23 | @property (nonatomic, copy, readonly) NSString *title; 24 | @property (nonatomic, copy, readonly) NSString *userFullName; 25 | 26 | /* 27 | Initializes a new photo with the given attributes and the page that this 28 | photo belongs to. 29 | */ 30 | - (id)initWithPage:(TCPhotoStreamPage *)page attributes:(NSDictionary *)attributes; 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhoto.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhoto.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/17/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhoto.h" 10 | 11 | // 500px Image Size Constants 12 | typedef NS_ENUM(NSInteger, TCPhotoSize) { 13 | TCPhotoSizeThumbnail = 3, 14 | TCPhotoSizeLarge = 4, 15 | }; 16 | 17 | @implementation TCPhoto 18 | 19 | - (id)initWithPage:(TCPhotoStreamPage *)page attributes:(NSDictionary *)attributes 20 | { 21 | self = [super init]; 22 | 23 | if (self) { 24 | _photoStreamPage = page; 25 | [self setAttributes:attributes]; 26 | } 27 | 28 | return self; 29 | } 30 | 31 | - (void)setAttributes:(NSDictionary *)attributes 32 | { 33 | // Title of the photo. 34 | _title = [attributes[@"name"] copy]; 35 | 36 | // User's full name. 37 | _userFullName = attributes[@"user"][@"fullname"]; 38 | 39 | // Set thumbnail and image URL. 40 | NSArray *imageArray = attributes[@"images"]; 41 | for (NSDictionary *imageDict in imageArray) { 42 | NSInteger imageSize = [imageDict[@"size"] integerValue]; 43 | NSURL *imageURL = [[NSURL alloc] initWithString:imageDict[@"url"]]; 44 | 45 | switch (imageSize) { 46 | case TCPhotoSizeThumbnail: 47 | _thumbnailURL = imageURL; 48 | break; 49 | 50 | case TCPhotoSizeLarge: 51 | _photoURL = imageURL; 52 | break; 53 | 54 | default: 55 | break; 56 | } 57 | } 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStream.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStream.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/15/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | @class TCPhotoStreamPage; 10 | @class TCPhoto; 11 | 12 | typedef void(^TCPhotoCompletionBlock)(TCPhoto *photo, NSError *error); 13 | 14 | /* 15 | Photo count value that indicates that the photo stream has not been fetched in yet. 16 | */ 17 | enum { TCPhotoStreamNoPhotoCount = -1 }; 18 | 19 | /* 20 | This model represents a photo stream. 21 | A photo stream will be separated into pages for more efficient retrieval. 22 | */ 23 | @interface TCPhotoStream : NSObject 24 | 25 | /* 26 | Gets the photo stream's feature. 27 | */ 28 | @property (nonatomic, assign, readonly) PXAPIHelperPhotoFeature feature; 29 | 30 | /* 31 | Gets the category that is used to filter the photo stream. 32 | */ 33 | @property (nonatomic, assign, readonly) PXPhotoModelCategory category; 34 | 35 | /* 36 | Initialize the photo stream with the given feature and category. 37 | */ 38 | - (id)initWithFeature:(PXAPIHelperPhotoFeature)feature 39 | category:(PXPhotoModelCategory)category; 40 | 41 | /* 42 | Returns the total number of photos in this photo stream. 43 | 44 | If photo stream has not finished loading yet, it returns TCPhotoStreamNoPhotoCount. 45 | */ 46 | - (NSInteger)photoCount; 47 | 48 | /* 49 | If photo is in the cache, this method will return the photo from the cache. 50 | It will not call the completionBlock in this case. 51 | 52 | If photo is not found in the cache, this method will return nil and async fetch 53 | the photo from network. In this case, completionBlock will be called when the 54 | photo is fetched. 55 | */ 56 | - (TCPhoto *)photoAtIndex:(NSUInteger)index completion:(TCPhotoCompletionBlock)completionBlock; 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStream.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStream.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/15/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhotoStream.h" 10 | #import "TCPhotoStreamPage.h" 11 | 12 | typedef void(^TCPhotoStreamPageCompletionBlock)(TCPhotoStreamPage *page, NSError *error); 13 | 14 | @interface TCPhotoStream () 15 | 16 | // Keeps track of the total number of photos in this photo stream. 17 | @property (nonatomic, assign) NSInteger photoCount; 18 | 19 | // We're using a pointer array because our array will be quite sparse. 20 | // E.g. We may have 2000 pages, but user is only viewing the first 2 pages. 21 | @property (nonatomic, strong) NSPointerArray *pages; 22 | 23 | @end 24 | 25 | #pragma mark - 26 | 27 | @implementation TCPhotoStream 28 | 29 | - (id)initWithFeature:(PXAPIHelperPhotoFeature)feature category:(PXPhotoModelCategory)category 30 | { 31 | self = [super init]; 32 | if (self) { 33 | _feature = feature; 34 | _category = category; 35 | _photoCount = TCPhotoStreamNoPhotoCount; 36 | } 37 | return self; 38 | } 39 | 40 | - (NSInteger)photoCount 41 | { 42 | return _photoCount; 43 | } 44 | 45 | - (TCPhoto *)photoAtIndex:(NSUInteger)photoIndex completion:(TCPhotoCompletionBlock)completionBlock 46 | { 47 | // Find the page index from the given photo index. 48 | NSUInteger pageIndex = photoIndex / kPXAPIHelperDefaultResultsPerPage; 49 | 50 | // Find the index of the photo within the page. 51 | NSUInteger photoIndexWithinPage = photoIndex % kPXAPIHelperDefaultResultsPerPage; 52 | 53 | TCPhotoStreamPage *page = [self pageAtIndex:pageIndex]; 54 | 55 | // If page is available, we will return the photo in the page. 56 | // If we're currently fetching the page, this will also prevent us from fetching 57 | // the same page again. 58 | if (page) { 59 | TCPhoto *photo = [page photoAtIndex:photoIndexWithinPage]; 60 | return photo; 61 | } 62 | 63 | // Create a new empty page at the given page index. 64 | // This is to indicate that we're currently fetching the page and that we should not fetch it again. 65 | page = [[TCPhotoStreamPage alloc] initWithPhotoStream:self pageNumber:pageIndex+1]; 66 | [self setPage:page atIndex:pageIndex]; 67 | 68 | // Fetch the page's photos asynchronously. 69 | [self fetchPage:page completion:^(TCPhotoStreamPage *page, NSError *error) { 70 | TCPhoto *photo = [page photoAtIndex:photoIndexWithinPage]; 71 | completionBlock(photo, error); 72 | }]; 73 | 74 | // Return nil to indicate we're fetching from network. 75 | return nil; 76 | } 77 | 78 | #pragma mark - Pages 79 | 80 | - (NSPointerArray *)pages 81 | { 82 | if (!_pages) { 83 | // We need strong references to the page objects because we're the only 84 | // object with a strong reference to them. 85 | _pages = [NSPointerArray strongObjectsPointerArray]; 86 | } 87 | return _pages; 88 | } 89 | 90 | - (TCPhotoStreamPage *)pageAtIndex:(NSUInteger)pageIndex 91 | { 92 | return (pageIndex < [self.pages count]) ? (__bridge TCPhotoStreamPage *)[self.pages pointerAtIndex:pageIndex] : nil; 93 | } 94 | 95 | - (void)setPage:(TCPhotoStreamPage *)page atIndex:(NSUInteger)pageIndex 96 | { 97 | // Dynamically grow the pointer array to match the number of pages. 98 | if (pageIndex >= [self.pages count]) { 99 | [self.pages setCount:(pageIndex + 1)]; 100 | } 101 | [self.pages replacePointerAtIndex:pageIndex withPointer:(__bridge void *)(page)]; 102 | } 103 | 104 | - (void)fetchPage:(TCPhotoStreamPage *)page completion:(TCPhotoStreamPageCompletionBlock)completionBlock 105 | { 106 | TCPhotoStreamPage * __block blockPage = page; 107 | 108 | // By default, we exclude Nude photos to make this app child-friendly. 109 | [PXRequest requestForPhotoFeature:self.feature resultsPerPage:kPXAPIHelperDefaultResultsPerPage page:page.pageNumber photoSizes:(PXPhotoModelSizeThumbnail|PXPhotoModelSizeLarge) sortOrder:kPXAPIHelperDefaultSortOrder except:PXPhotoModelCategoryNude only:self.category completion:^(NSDictionary *results, NSError *error) { 110 | if (results) { 111 | [blockPage setAttributes:results]; 112 | 113 | // Update the total number of pages and photos in this photo stream. 114 | self.photoCount = [results[@"total_items"] integerValue]; 115 | [self.pages setCount:[results[@"total_pages"] unsignedIntegerValue]]; 116 | } else if (error) { 117 | // nil out the page on error, so that we can retry loading it again. 118 | blockPage = nil; 119 | [self setPage:blockPage atIndex:(page.pageNumber - 1)]; 120 | } 121 | 122 | completionBlock(blockPage, error); 123 | }]; 124 | } 125 | 126 | #pragma mark - Debug 127 | 128 | - (NSString *)description 129 | { 130 | return [DescriptionBuilder reflectDescription:self style:DescriptionStyleMultiLine]; 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStreamCategory.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStreamCategory.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/20/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /* 12 | This model represents a category to filter the photo stream with. 13 | */ 14 | @interface TCPhotoStreamCategory : NSObject 15 | 16 | @property (nonatomic, assign, getter = isSelected) BOOL selected; 17 | @property (nonatomic, copy, readonly) NSString *title; 18 | @property (nonatomic, assign, readonly) PXPhotoModelCategory value; 19 | 20 | - (id)initWithTitle:(NSString *)title value:(PXPhotoModelCategory)value selected:(BOOL)selected; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStreamCategory.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStreamCategory.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/20/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhotoStreamCategory.h" 10 | 11 | @implementation TCPhotoStreamCategory 12 | 13 | - (id)initWithTitle:(NSString *)title value:(PXPhotoModelCategory)value selected:(BOOL)selected 14 | { 15 | self = [super init]; 16 | if (self) { 17 | _title = [title copy]; 18 | _value = value; 19 | _selected = selected; 20 | } 21 | return self; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStreamCategoryList.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStreamCategoryList.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/20/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | @class TCPhotoStreamCategory; 10 | 11 | /* 12 | This model represents a list of supported categories that can be used to 13 | filter the photo stream. 14 | */ 15 | @interface TCPhotoStreamCategoryList : NSObject 16 | 17 | /* 18 | Returns a shared list of supported categories. 19 | */ 20 | + (instancetype)defaultList; 21 | 22 | /* 23 | Number of categories in this list. 24 | */ 25 | - (NSUInteger)categoryCount; 26 | 27 | /* 28 | Returns the index of the selected category. 29 | */ 30 | - (NSUInteger)indexOfSelectedCategory; 31 | 32 | /* 33 | Selects category at given index and deselects any currently selected category. 34 | */ 35 | - (void)selectCategoryAtIndex:(NSUInteger)index; 36 | 37 | /* 38 | Returns the category at the given index. 39 | */ 40 | - (TCPhotoStreamCategory *)categoryAtIndex:(NSUInteger)index; 41 | 42 | /* 43 | Removes all TCPhotoStreamCategory objects from the list. The list will be 44 | re-created automatically when attempting to access the categories again. 45 | This method should be called when the controller receives a memory warning. 46 | */ 47 | - (void)removeAllCategories; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStreamCategoryList.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStreamCategoryList.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/20/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhotoStreamCategoryList.h" 10 | #import "TCPhotoStreamCategory.h" 11 | 12 | @interface TCPhotoStreamCategoryList () 13 | 14 | @property (nonatomic, copy) NSArray *categories; 15 | @property (nonatomic, assign) NSUInteger selectedIndex; 16 | 17 | @end 18 | 19 | #pragma mark - 20 | 21 | @implementation TCPhotoStreamCategoryList 22 | 23 | + (instancetype)defaultList 24 | { 25 | static TCPhotoStreamCategoryList *sharedInstance = nil; 26 | static dispatch_once_t onceToken; 27 | dispatch_once(&onceToken, ^{ 28 | sharedInstance = [[TCPhotoStreamCategoryList alloc] init]; 29 | }); 30 | return sharedInstance; 31 | } 32 | 33 | - (NSUInteger)categoryCount 34 | { 35 | return [self.categories count]; 36 | } 37 | 38 | - (NSUInteger)indexOfSelectedCategory 39 | { 40 | return _selectedIndex; 41 | } 42 | 43 | - (void)selectCategoryAtIndex:(NSUInteger)index 44 | { 45 | // Nothing to do if attempting to select an already selected category. 46 | if (index == self.selectedIndex) { 47 | return; 48 | } 49 | 50 | // Deselect the currently selected category. 51 | [(TCPhotoStreamCategory *)self.categories[self.selectedIndex] setSelected:NO]; 52 | 53 | // Select the category at given index. 54 | [(TCPhotoStreamCategory *)self.categories[index] setSelected:YES]; 55 | self.selectedIndex = index; 56 | } 57 | 58 | - (TCPhotoStreamCategory *)categoryAtIndex:(NSUInteger)index 59 | { 60 | return self.categories[index]; 61 | } 62 | 63 | - (void)removeAllCategories 64 | { 65 | self.categories = nil; 66 | } 67 | 68 | #pragma mark - Create Categories 69 | 70 | - (NSArray *)categories 71 | { 72 | if (!_categories) { 73 | _categories = [[[self class] supportedPhotoStreamCategories] copy]; 74 | } 75 | return _categories; 76 | } 77 | 78 | /* 79 | Create an array of TCPhotoStreamCategory objects. 80 | */ 81 | + (NSArray *)supportedPhotoStreamCategories 82 | { 83 | // Uncomment the Nude category to show adult-rated photos (which will get 84 | // your app rejected by Apple). 85 | 86 | NSArray *values = @[@(PXAPIHelperUnspecifiedCategory), 87 | @(PXPhotoModelCategoryAbstract), 88 | @(PXPhotoModelCategoryAnimals), 89 | @(PXPhotoModelCategoryBlackAndWhite), 90 | @(PXPhotoModelCategoryCelbrities), 91 | @(PXPhotoModelCategoryCityAndArchitecture), 92 | @(PXPhotoModelCategoryCommercial), 93 | @(PXPhotoModelCategoryConcert), 94 | @(PXPhotoModelCategoryFamily), 95 | @(PXPhotoModelCategoryFashion), 96 | @(PXPhotoModelCategoryFilm), 97 | @(PXPhotoModelCategoryFineArt), 98 | @(PXPhotoModelCategoryFood), 99 | @(PXPhotoModelCategoryJournalism), 100 | @(PXPhotoModelCategoryLandscapes), 101 | @(PXPhotoModelCategoryMacro), 102 | @(PXPhotoModelCategoryNature), 103 | // @(PXPhotoModelCategoryNude), 104 | @(PXPhotoModelCategoryPeople), 105 | @(PXPhotoModelCategoryPerformingArts), 106 | @(PXPhotoModelCategorySport), 107 | @(PXPhotoModelCategoryStillLife), 108 | @(PXPhotoModelCategoryStreet), 109 | @(PXPhotoModelCategoryTransportation), 110 | @(PXPhotoModelCategoryTravel), 111 | @(PXPhotoModelCategoryUnderwater), 112 | @(PXPhotoModelCategoryUrbanExploration), 113 | @(PXPhotoModelCategoryWedding), 114 | @(PXPhotoModelCategoryUncategorized)]; 115 | 116 | NSArray *titles = @[@"All Categories", 117 | @"Abstract", 118 | @"Animals", 119 | @"Black and White", 120 | @"Celebrities", 121 | @"City and Architecture", 122 | @"Commercial", 123 | @"Concert", 124 | @"Family", 125 | @"Fashion", 126 | @"Film", 127 | @"Fine Art", 128 | @"Food", 129 | @"Journalism", 130 | @"Landscapes", 131 | @"Macro", 132 | @"Nature", 133 | // @"Nude", 134 | @"People", 135 | @"Performing Arts", 136 | @"Sport", 137 | @"Still Life", 138 | @"Street", 139 | @"Transporation", 140 | @"Travel", 141 | @"Underwater", 142 | @"Urban Exploration", 143 | @"Wedding", 144 | @"Uncategorized"]; 145 | 146 | return [[self class] categoriesFromValues:values titles:titles]; 147 | } 148 | 149 | + (NSArray *)categoriesFromValues:(NSArray *)values titles:(NSArray *)titles 150 | { 151 | NSMutableArray *categories = [[NSMutableArray alloc] initWithCapacity:[values count]]; 152 | [values enumerateObjectsUsingBlock:^(NSNumber *value, NSUInteger index, BOOL *stop) { 153 | // Select the first category initially. 154 | TCPhotoStreamCategory *category = [[TCPhotoStreamCategory alloc] initWithTitle:titles[index] 155 | value:[value integerValue] 156 | selected:(0 == index)]; 157 | [categories addObject:category]; 158 | }]; 159 | return categories; 160 | } 161 | 162 | @end 163 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStreamPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStreamPage.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/17/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhotoStream.h" 10 | @class TCPhoto; 11 | 12 | /* 13 | This model represents a single page in the photo stream. 14 | Each page will have its own collection of photos. 15 | */ 16 | @interface TCPhotoStreamPage : NSObject 17 | 18 | /* 19 | Weak reference to the photo stream parent that owns this page. 20 | */ 21 | @property (nonatomic, weak, readonly) TCPhotoStream *photoStream; 22 | 23 | /* 24 | Gets the page number of this page. Page number starts from 1. 25 | */ 26 | @property (nonatomic, assign, readonly) NSInteger pageNumber; 27 | 28 | /* 29 | Initializes a page with the photo stream that this page belongs to and its page number. 30 | */ 31 | - (id)initWithPhotoStream:(TCPhotoStream *)photoStream 32 | pageNumber:(NSInteger)pageNumber; 33 | 34 | /* 35 | Set this page's properties from a dictionary of keys and values. 36 | */ 37 | - (void)setAttributes:(NSDictionary *)attributes; 38 | 39 | /* 40 | Returns the number of photos in this page. 41 | */ 42 | - (NSUInteger)photoCount; 43 | 44 | /* 45 | Returns the photo in this page at the specified index. 46 | */ 47 | - (TCPhoto *)photoAtIndex:(NSUInteger)index; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Models/TCPhotoStreamPage.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoStreamPage.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/17/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhotoStreamPage.h" 10 | #import "TCPhotoStream.h" 11 | #import "TCPhoto.h" 12 | 13 | @interface TCPhotoStreamPage () 14 | 15 | @property (nonatomic, strong) NSMutableArray *photos; 16 | 17 | @end 18 | 19 | @implementation TCPhotoStreamPage 20 | 21 | - (id)initWithPhotoStream:(TCPhotoStream *)photoStream 22 | pageNumber:(NSInteger)pageNumber 23 | { 24 | self = [super init]; 25 | 26 | if (self) { 27 | _photoStream = photoStream; 28 | _pageNumber = pageNumber; 29 | } 30 | 31 | return self; 32 | } 33 | 34 | - (void)setAttributes:(NSDictionary *)attributes 35 | { 36 | NSArray *photoArray = attributes[@"photos"]; 37 | self.photos = [[NSMutableArray alloc] initWithCapacity:[photoArray count]]; 38 | 39 | for (NSDictionary *photoDict in photoArray) { 40 | TCPhoto *photo = [[TCPhoto alloc] initWithPage:self attributes:photoDict]; 41 | [self.photos addObject:photo]; 42 | } 43 | } 44 | 45 | - (NSUInteger)photoCount 46 | { 47 | return [self.photos count]; 48 | } 49 | 50 | - (TCPhoto *)photoAtIndex:(NSUInteger)index 51 | { 52 | return (index < [self photoCount]) ? self.photos[index] : nil; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Views/TCCategoryCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCCategoryCell.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/21/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TCPhotoStreamCategory; 12 | 13 | @interface TCCategoryCell : UITableViewCell 14 | 15 | /* 16 | The photo stream category model that this cell view renders. 17 | */ 18 | @property (nonatomic, strong) TCPhotoStreamCategory *category; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Views/TCCategoryCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCCategoryCell.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/21/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCCategoryCell.h" 10 | #import "TCPhotoStreamCategory.h" 11 | 12 | // FlatUIKit 13 | #import "UIColor+FlatUI.h" 14 | #import "UITableViewCell+FlatUI.h" 15 | #import "FUICellBackgroundView.h" 16 | 17 | @implementation TCCategoryCell 18 | 19 | // Customize the cell's appearance after it has been loaded from the storyboard. 20 | - (void)awakeFromNib 21 | { 22 | [self configureFlatCellWithColor:[UIColor cloudsColor] selectedColor:[UIColor peterRiverColor]]; 23 | 24 | self.textLabel.textColor = [UIColor blackColor]; 25 | self.textLabel.highlightedTextColor = [UIColor whiteColor]; 26 | } 27 | 28 | - (void)setCategory:(TCPhotoStreamCategory *)category 29 | { 30 | self.textLabel.text = category.title; 31 | [self setSelected:category.isSelected animated:YES]; 32 | } 33 | 34 | - (void)setSelected:(BOOL)selected 35 | { 36 | [super setSelected:selected]; 37 | 38 | if (self.isSelected) { 39 | self.accessoryType = UITableViewCellAccessoryCheckmark; 40 | self.textLabel.font = [UIFont boldSystemFontOfSize:17]; 41 | } else { 42 | self.accessoryType = UITableViewCellAccessoryNone; 43 | self.textLabel.font = [UIFont systemFontOfSize:17]; 44 | } 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Views/TCPhotoCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoCell.h 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/14/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TCPhoto; 12 | 13 | @interface TCPhotoCell : UICollectionViewCell 14 | 15 | @property (strong, nonatomic) IBOutlet UIImageView *imageView; 16 | @property (strong, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; 17 | 18 | /* 19 | The photo model that this cell renders. 20 | */ 21 | @property (strong, nonatomic) TCPhoto *photo; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /TCPhotos500px/Classes/Views/TCPhotoCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TCPhotoCell.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/14/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import "TCPhotoCell.h" 10 | #import "TCPhoto.h" 11 | 12 | // Higher performance async remote image loading than AFNetworking's default 13 | // UIImageView category. 14 | #import "UIImageView+WebCache.h" 15 | 16 | @implementation TCPhotoCell 17 | 18 | - (void)setPhoto:(TCPhoto *)newPhoto 19 | { 20 | _photo = newPhoto; 21 | 22 | // Refer to Apple's Transitioning to ARC Release Notes on non-trivial cycles. 23 | // Non-trivial in this case means we're using weakSelf multiple times in the block. 24 | // So, weakSelf may become nil during the execution of the block. 25 | __weak typeof(self) weakSelf = self; 26 | 27 | // Asynchronously load the thumbnail from cache (if available) or network. 28 | [self.imageView setImageWithURL:_photo.thumbnailURL placeholderImage:nil options:0 completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) { 29 | __strong typeof(self) strongSelf = weakSelf; 30 | 31 | if (image) { 32 | // Image has finished loading. Hide the activity indicator. 33 | [strongSelf.activityIndicator stopAnimating]; 34 | 35 | // Only perform fade animation when loading image from web. 36 | if (cacheType == SDImageCacheTypeNone) { 37 | strongSelf.imageView.alpha = 0.0f; 38 | [UIView animateWithDuration:0.7f animations:^{ 39 | strongSelf.imageView.alpha = 1.0f; 40 | }]; 41 | } 42 | } else if (error) { 43 | NSLog(@"[SDWebImage Error] - %@", [error localizedDescription]); 44 | } 45 | }]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /TCPhotos500px/Resources/Images/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TCLee/TCPhotos500px/c0bcd707ea8e44b1410c2434f77f6aae12cd0c9d/TCPhotos500px/Resources/Images/Icon-72.png -------------------------------------------------------------------------------- /TCPhotos500px/Resources/Images/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TCLee/TCPhotos500px/c0bcd707ea8e44b1410c2434f77f6aae12cd0c9d/TCPhotos500px/Resources/Images/Icon-72@2x.png -------------------------------------------------------------------------------- /TCPhotos500px/Resources/Storyboards/en.lproj/MainStoryboard.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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 69 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | -------------------------------------------------------------------------------- /TCPhotos500px/TCPhotos500px-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIcons 12 | 13 | CFBundlePrimaryIcon 14 | 15 | CFBundleIconFiles 16 | 17 | Icon-72.png 18 | Icon-72@2x.png 19 | 20 | UIPrerenderedIcon 21 | 22 | 23 | 24 | CFBundleIdentifier 25 | com.tclee.${PRODUCT_NAME:rfc1034identifier} 26 | CFBundleInfoDictionaryVersion 27 | 6.0 28 | CFBundleName 29 | ${PRODUCT_NAME} 30 | CFBundlePackageType 31 | APPL 32 | CFBundleShortVersionString 33 | 1.0 34 | CFBundleSignature 35 | ???? 36 | CFBundleVersion 37 | 1.0 38 | LSRequiresIPhoneOS 39 | 40 | UIMainStoryboardFile 41 | MainStoryboard 42 | UIPrerenderedIcon 43 | 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UIStatusBarHidden~ipad 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /TCPhotos500px/TCPhotos500px-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'TCPhotos500px' target in the 'TCPhotos500px' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | 15 | // https://github.com/500px/500px-iOS-api 16 | #import "PXAPI.h" 17 | 18 | // https://github.com/rs/SDWebImage 19 | #import "UIImageView+WebCache.h" 20 | 21 | // https://github.com/samvermette/SVPullToRefresh 22 | #import "UIScrollView+SVPullToRefresh.h" 23 | 24 | // Utility methods for debugging purposes only. 25 | #import "DescriptionBuilder.h" 26 | #endif 27 | -------------------------------------------------------------------------------- /TCPhotos500px/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TCPhotos500px/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TCPhotos500px 4 | // 5 | // Created by Lee Tze Cheun on 7/14/13. 6 | // Copyright (c) 2013 Lee Tze Cheun. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TCAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([TCAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Vendor/DescriptionBuilder/DescriptionBuilder.h: -------------------------------------------------------------------------------- 1 | // 2 | // DescriptionBuilder.h 3 | // DescriptionBuilder 4 | // 5 | // Created by KISHIKAWA Katsumi on 09/09/07. 6 | // Copyright 2009 KISHIKAWA Katsumi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef enum DescriptionStyle { 12 | DescriptionStyleDefault, 13 | DescriptionStyleMultiLine, 14 | DescriptionStyleNoNames, 15 | DescriptionStyleShortPrefix, 16 | DescriptionStyleSimple, 17 | } DescriptionStyle; 18 | 19 | @interface DescriptionBuilder : NSObject 20 | 21 | + (NSString *)reflectDescription:(id)obj; 22 | + (NSString *)reflectDescription:(id)obj style:(DescriptionStyle)style; 23 | + (NSString *)reflectDescriptionWithSuperClass:(id)obj; 24 | + (NSString *)reflectDescriptionWithSuperClass:(id)obj style:(DescriptionStyle)style; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Vendor/DescriptionBuilder/DescriptionBuilder.m: -------------------------------------------------------------------------------- 1 | // 2 | // DescriptionBuilder.m 3 | // DescriptionBuilder 4 | // 5 | // Created by KISHIKAWA Katsumi on 09/09/07. 6 | // Copyright 2009 KISHIKAWA Katsumi. All rights reserved. 7 | // 8 | 9 | #import "DescriptionBuilder.h" 10 | #import 11 | 12 | @interface DescriptionBuilder() 13 | + (NSString *)reflectDescription:(id)obj style:(DescriptionStyle)style targetClass:(Class)cls withSuperClass:(BOOL) superClassFlag; 14 | @end 15 | 16 | @implementation DescriptionBuilder 17 | 18 | + (NSString *)reflectDescription:(id)obj { 19 | return [DescriptionBuilder reflectDescription:obj style:DescriptionStyleDefault]; 20 | } 21 | 22 | + (NSString *)reflectDescriptionWithSuperClass:(id)obj { 23 | return [DescriptionBuilder reflectDescription:obj style:DescriptionStyleDefault targetClass:[obj class] withSuperClass:YES]; 24 | } 25 | 26 | + (NSString *)reflectDescriptionWithSuperClass:(id)obj style:(DescriptionStyle)style { 27 | return [DescriptionBuilder reflectDescription:obj style:style targetClass:[obj class] withSuperClass:YES]; 28 | } 29 | 30 | 31 | /** 32 | c char 33 | C unsigned char 34 | i int 35 | I unsigned int 36 | s short 37 | S unsigned short 38 | l long 39 | L unsigned long 40 | q long long 41 | Q unsigned long long 42 | f float 43 | d double 44 | B C++のbool、またはC99の_Bool 45 | ^v void* 46 | * 文字列(char*) 47 | @ オブジェクト (静的に型定義されているもの、または id として型定義されているもの) 48 | # クラスオブジェクト(Class) 49 | : メソッドセレクタ(SEL) 50 | { 構造体 51 | */ 52 | + (NSString *)reflectDescription:(id)obj style:(DescriptionStyle)style { 53 | return [DescriptionBuilder reflectDescription:obj style:style targetClass:[obj class] withSuperClass:NO]; 54 | } 55 | + (NSString *)reflectDescription:(id)obj style:(DescriptionStyle)style targetClass:(Class)cls withSuperClass:(BOOL) superClassFlag{ 56 | id objValue; 57 | Class classValue; 58 | SEL selValue; 59 | signed char charValue; 60 | unsigned char ucharValue; 61 | signed int intValue; 62 | unsigned int uintValue; 63 | signed short shortValue; 64 | unsigned short ushortValue; 65 | signed long longValue; 66 | unsigned long ulongValue; 67 | signed long long longlongValue; 68 | unsigned long long ulonglongValue; 69 | float floatValue; 70 | double doubleValue; 71 | char *charPtrValue; 72 | void *voidPtrValue; 73 | 74 | NSMutableString *description = [[[NSMutableString alloc] init] autorelease]; 75 | 76 | Class clazz = cls; 77 | unsigned int outCount = 0; 78 | Ivar *ivars = class_copyIvarList(clazz, &outCount); 79 | 80 | if (style == DescriptionStyleMultiLine) { 81 | [description appendFormat:@"<%s: 0x%x;\n", class_getName(clazz), [obj hash]]; 82 | } else if (style == DescriptionStyleNoNames) { 83 | [description appendFormat:@"<%s: 0x%x; ", class_getName(clazz), [obj hash]]; 84 | } else if (style == DescriptionStyleShortPrefix) { 85 | [description appendFormat:@"<%s; ", class_getName(clazz)]; 86 | } else if (style == DescriptionStyleSimple) { 87 | [description appendString:@"<"]; 88 | } else { 89 | [description appendFormat:@"<%s: 0x%x; ", class_getName(clazz), [obj hash]]; 90 | } 91 | 92 | for (int i = 0; i < outCount; i++) { 93 | if (i > 0) { 94 | if (style == DescriptionStyleMultiLine) { 95 | [description appendString:@";\n"]; 96 | } else if (style == DescriptionStyleNoNames) { 97 | [description appendString:@"; "]; 98 | } else if (style == DescriptionStyleShortPrefix) { 99 | [description appendString:@"; "]; 100 | } else if (style == DescriptionStyleSimple) { 101 | [description appendString:@"; "]; 102 | } else { 103 | [description appendString:@"; "]; 104 | } 105 | } 106 | 107 | Ivar ivar = ivars[i]; 108 | const char *ivar_name = ivar_getName(ivar); 109 | const char *ivar_type = ivar_getTypeEncoding(ivar); 110 | 111 | if (style == DescriptionStyleMultiLine) { 112 | [description appendFormat:@"%s = ", ivar_name]; 113 | } else if (style == DescriptionStyleNoNames) { 114 | //Nothing to do.; 115 | } else if (style == DescriptionStyleShortPrefix) { 116 | [description appendFormat:@"%s = ", ivar_name]; 117 | } else if (style == DescriptionStyleSimple) { 118 | //Nothing to do.; 119 | } else { 120 | [description appendFormat:@"%s = ", ivar_name]; 121 | } 122 | 123 | switch(*ivar_type) { 124 | case '@': 125 | object_getInstanceVariable(obj, ivar_name, (void **)&objValue); 126 | if (!objValue) { 127 | [description appendFormat:@"%@", [NSNull null]]; 128 | break; 129 | } 130 | if ([objValue respondsToSelector:@selector(description)]) { 131 | NSString *type = [NSString stringWithUTF8String:ivar_type]; 132 | if ([type length] > 3) { 133 | NSString *className = [type substringWithRange:NSMakeRange(2, [type length] - 3)]; 134 | Class ivarClass = NSClassFromString(className); 135 | if ([NSString isSubclassOfClass:ivarClass]) { 136 | [description appendFormat:@"\"%@\"", [objValue description]]; 137 | break; 138 | } 139 | } 140 | [description appendFormat:@"%@", [objValue description]]; 141 | } else { 142 | [description appendFormat:@"<%s: 0x%x>", class_getName([objValue class]), [objValue hash]]; 143 | } 144 | break; 145 | case '#': 146 | object_getInstanceVariable(obj, ivar_name, (void **)&classValue); 147 | [description appendFormat:@"%@", NSStringFromClass(classValue)]; 148 | break; 149 | case ':': 150 | object_getInstanceVariable(obj, ivar_name, (void **)&selValue); 151 | [description appendFormat:@"%@", NSStringFromSelector(selValue)]; 152 | break; 153 | case 'c': 154 | object_getInstanceVariable(obj, ivar_name, (void **)&charValue); 155 | [description appendFormat:@"%@", charValue == 0 ? @"NO" : @"YES"]; 156 | break; 157 | case 'C': 158 | object_getInstanceVariable(obj, ivar_name, (void **)&ucharValue); 159 | [description appendFormat:@"%c", ucharValue]; 160 | break; 161 | case 'i': 162 | object_getInstanceVariable(obj, ivar_name, (void **)&intValue); 163 | [description appendFormat:@"%d", intValue]; 164 | break; 165 | case 'I': 166 | object_getInstanceVariable(obj, ivar_name, (void **)&uintValue); 167 | [description appendFormat:@"%u", uintValue]; 168 | break; 169 | case 's': 170 | object_getInstanceVariable(obj, ivar_name, (void **)&shortValue); 171 | [description appendFormat:@"%hi", shortValue]; 172 | break; 173 | case 'S': 174 | object_getInstanceVariable(obj, ivar_name, (void **)&ushortValue); 175 | [description appendFormat:@"%hu", ushortValue]; 176 | break; 177 | case 'l': 178 | object_getInstanceVariable(obj, ivar_name, (void **)&longValue); 179 | [description appendFormat:@"%ld", longValue]; 180 | break; 181 | case 'L': 182 | object_getInstanceVariable(obj, ivar_name, (void **)&ulongValue); 183 | [description appendFormat:@"%lu", ulongValue]; 184 | break; 185 | case 'q': 186 | object_getInstanceVariable(obj, ivar_name, (void **)&longlongValue); 187 | [description appendFormat:@"%qi", longlongValue]; 188 | break; 189 | case 'Q': 190 | object_getInstanceVariable(obj, ivar_name, (void **)&ulonglongValue); 191 | [description appendFormat:@"%qu", ulonglongValue]; 192 | break; 193 | case 'f': 194 | object_getInstanceVariable(obj, ivar_name, (void **)&floatValue); 195 | [description appendFormat:@"%f", floatValue]; 196 | break; 197 | case 'd': 198 | object_getInstanceVariable(obj, ivar_name, (void **)&doubleValue); 199 | [description appendFormat:@"%f", doubleValue]; 200 | break; 201 | case 'B': 202 | object_getInstanceVariable(obj, ivar_name, (void **)&intValue); 203 | [description appendFormat:@"%@", intValue == 0 ? @"false" : @"true"]; 204 | break; 205 | case '*': 206 | object_getInstanceVariable(obj, ivar_name, (void **)&charPtrValue); 207 | [description appendFormat:@"%s", charPtrValue]; 208 | break; 209 | case '^': 210 | object_getInstanceVariable(obj, ivar_name, (void **)&voidPtrValue); 211 | [description appendFormat:@"%p", voidPtrValue]; 212 | break; 213 | case '{': 214 | { 215 | NSString *type = [NSString stringWithUTF8String:ivar_type]; 216 | NSString *structName = [type substringWithRange:NSMakeRange(1, [type rangeOfString:@"="].location - 1)]; 217 | if ([structName isEqualToString:@"CGAffineTransform"]) { 218 | CGAffineTransform transform; 219 | object_getInstanceVariable(obj, ivar_name, (void **)&transform); 220 | [description appendFormat:@"%@", NSStringFromCGAffineTransform(transform)]; 221 | } else if ([structName isEqualToString:@"CGPoint"]) { 222 | CGPoint point; 223 | object_getInstanceVariable(obj, ivar_name, (void **)&point); 224 | [description appendFormat:@"%@", NSStringFromCGPoint(point)]; 225 | } else if ([structName isEqualToString:@"CGRect"]) { 226 | CGRect rect; 227 | object_getInstanceVariable(obj, ivar_name, (void **)&rect); 228 | [description appendFormat:@"%@", NSStringFromCGRect(rect)]; 229 | } else if ([structName isEqualToString:@"CGSize"]) { 230 | CGSize size; 231 | object_getInstanceVariable(obj, ivar_name, (void **)&size); 232 | [description appendFormat:@"%@", NSStringFromCGSize(size)]; 233 | } else if ([structName isEqualToString:@"_NSRange"]) { 234 | NSRange range; 235 | object_getInstanceVariable(obj, ivar_name, (void **)&range); 236 | [description appendFormat:@"%@", NSStringFromRange(range)]; 237 | } else if ([structName isEqualToString:@"UIEdgeInsets"]) { 238 | UIEdgeInsets insets; 239 | object_getInstanceVariable(obj, ivar_name, (void **)&insets); 240 | [description appendFormat:@"%@", NSStringFromUIEdgeInsets(insets)]; 241 | } 242 | break; 243 | } 244 | default: 245 | [description appendFormat:@"%s", ivar_type]; 246 | break; 247 | } 248 | } 249 | if (superClassFlag && class_getSuperclass(cls)) { 250 | [description appendString:@"\n"]; 251 | [description appendString:[DescriptionBuilder reflectDescription:obj style:style targetClass:class_getSuperclass(cls) withSuperClass:superClassFlag]]; 252 | } 253 | [description appendString:@">"]; 254 | if (outCount > 0) { free(ivars); } 255 | return description; 256 | } 257 | 258 | @end 259 | --------------------------------------------------------------------------------