├── .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 | 
9 |
10 | 
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 |
--------------------------------------------------------------------------------