├── .gitignore ├── Images ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── LICENSE ├── README.md ├── Resources ├── designs.sketch ├── logo-gray-small.png ├── logo-gray-small@2x.png ├── logo-gray-small@3x.png ├── logo-white.png ├── logo-white@2x.png └── logo-white@3x.png ├── ZhihuDaily.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── ZhihuDaily ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json ├── logo-gray-small.imageset │ ├── Contents.json │ ├── logo-gray-small.png │ ├── logo-gray-small@2x.png │ └── logo-gray-small@3x.png └── logo-white.imageset │ ├── Contents.json │ ├── logo-white.png │ ├── logo-white@2x.png │ └── logo-white@3x.png ├── Base.lproj ├── LaunchScreen.storyboard └── TopStoryView.xib ├── BaseOperation.h ├── BaseOperation.m ├── BaseTableViewCell.h ├── BaseTableViewCell.m ├── BaseTableViewController.h ├── BaseTableViewController.m ├── BaseViewController.h ├── BaseViewController.m ├── BlockOperation.h ├── BlockOperation.m ├── BottomToastView.xib ├── CacheManager.h ├── CacheManager.m ├── CacheManagmentViewController.h ├── CacheManagmentViewController.m ├── CentralProgressView.xib ├── HUDManager.h ├── HUDManager.m ├── ImageDownloadingOperation.h ├── ImageDownloadingOperation.m ├── Info.plist ├── NSLock+Helpers.h ├── NSLock+Helpers.m ├── NSString+MD5.h ├── NSString+MD5.m ├── NavigationController.h ├── NavigationController.m ├── OperationQueue.h ├── OperationQueue.m ├── ParsingOperation.h ├── ParsingOperation.m ├── ReaderViewController.h ├── ReaderViewController.m ├── Settings.storyboard ├── SettingsViewController.h ├── SettingsViewController.m ├── StartupImageParsingOperation.h ├── StartupImageParsingOperation.m ├── StartupViewController.h ├── StartupViewController.m ├── StartupViewController.xib ├── Story.h ├── Story.m ├── StoryBody.h ├── StoryBody.m ├── StoryBodyParsingOperation.h ├── StoryBodyParsingOperation.m ├── ThemeManager.h ├── ThemeManager.m ├── Timeline.h ├── Timeline.m ├── TimelineCell.h ├── TimelineCell.m ├── TimelineCell.xib ├── TimelineController.h ├── TimelineController.m ├── TimelineDataSource.h ├── TimelineDataSource.m ├── TimelineParsingOperation.h ├── TimelineParsingOperation.m ├── TimelineSectionHeaderView.h ├── TimelineSectionHeaderView.m ├── TimelineSectionHeaderView.xib ├── TimelineTableViewController.h ├── TimelineTableViewController.m ├── TimelineViewController.h ├── TimelineViewController.m ├── TopStoryCell.h ├── TopStoryCell.m ├── TopStoryPageController.h ├── TopStoryPageController.m ├── TopStoryViewController.h ├── TopStoryViewController.m ├── UIImage+Helpers.h ├── UIImage+Helpers.m ├── URLDownloadingOperation.h ├── URLDownloadingOperation.m ├── ZhihuDaily.xcdatamodeld ├── .xccurrentversion └── ZhihuDaily.xcdatamodel │ └── contents └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /Images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Images/1.png -------------------------------------------------------------------------------- /Images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Images/2.png -------------------------------------------------------------------------------- /Images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Images/3.png -------------------------------------------------------------------------------- /Images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Images/4.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cyandev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZhihuDaily-iOS 2 | A minimal Zhihu daily app for iOS without any 3rd-party frameworks. 3 | 4 | ## Features 5 | * Splash screen updating 6 | * Image disk cache and cache cleaner 7 | * UI Night mode 8 | * Built-in full-featured WebView 9 | 10 | ## Screenshots 11 | #### Splash screen: 12 | ![](https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/master/Images/1.png) 13 | #### Timeline: 14 | ![](https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/master/Images/2.png) 15 | #### Night mode: 16 | ![](https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/master/Images/3.png) 17 | #### Reader: 18 | ![](https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/master/Images/4.png) 19 | 20 | ## Project Structure 21 | * Extensions (系统类扩展) 22 | * Services (应用依赖的全局服务、例如缓存、主题管理等) 23 | * Operations (一些 `NSOperation` 子类,实现了一些粒度较小的异步操作) 24 | * Models (数据模型) 25 | * Data Sources (数据源,用于将数据模型映射到视图,减少 VC 负荷) 26 | * Views (一些视图类) 27 | * View Controllers (视图控制器) 28 | * Supporting Files (应用依赖的支持文件) 29 | 30 | ## Build 31 | 本项目无任何依赖库。 32 | ``` 33 | git clone https://github.com/unixzii/ZhihuDaily-iOS.git 34 | cd ./ZhihuDaily-iOS 35 | open ./ZhihuDaily.xcodeproj 36 | ``` 37 | 在 Xcode 中直接编译调试即可。 38 | 39 | ## Author 40 | **Cyandev**, an UC student majored in Software Engineering. 41 | * Weibo: [@unixzii](http://weibo.com/2834711045/profile?topnav=1&wvr=6&is_all=1) 42 | * Email: , (alternative) 43 | 44 | ## License 45 | ZhihuDaily-iOS is available under the MIT license. See the LICENSE file for more info. 46 | -------------------------------------------------------------------------------- /Resources/designs.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Resources/designs.sketch -------------------------------------------------------------------------------- /Resources/logo-gray-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Resources/logo-gray-small.png -------------------------------------------------------------------------------- /Resources/logo-gray-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Resources/logo-gray-small@2x.png -------------------------------------------------------------------------------- /Resources/logo-gray-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Resources/logo-gray-small@3x.png -------------------------------------------------------------------------------- /Resources/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Resources/logo-white.png -------------------------------------------------------------------------------- /Resources/logo-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Resources/logo-white@2x.png -------------------------------------------------------------------------------- /Resources/logo-white@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/Resources/logo-white@3x.png -------------------------------------------------------------------------------- /ZhihuDaily.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9D8166FB1CF014DB00570DF6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8166FA1CF014DB00570DF6 /* main.m */; }; 11 | 9D8166FE1CF014DB00570DF6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8166FD1CF014DB00570DF6 /* AppDelegate.m */; }; 12 | 9D8167071CF014DB00570DF6 /* ZhihuDaily.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167051CF014DB00570DF6 /* ZhihuDaily.xcdatamodeld */; }; 13 | 9D8167091CF014DB00570DF6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D8167081CF014DB00570DF6 /* Assets.xcassets */; }; 14 | 9D81670C1CF014DB00570DF6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D81670A1CF014DB00570DF6 /* LaunchScreen.storyboard */; }; 15 | 9D8167191CF0184900570DF6 /* TimelineViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167171CF0184900570DF6 /* TimelineViewController.m */; }; 16 | 9D81671F1CF0186D00570DF6 /* BaseViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D81671E1CF0186D00570DF6 /* BaseViewController.m */; }; 17 | 9D8167221CF018D600570DF6 /* NavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167211CF018D600570DF6 /* NavigationController.m */; }; 18 | 9D8167271CF01A9C00570DF6 /* StartupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167251CF01A9C00570DF6 /* StartupViewController.m */; }; 19 | 9D8167281CF01A9C00570DF6 /* StartupViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9D8167261CF01A9C00570DF6 /* StartupViewController.xib */; }; 20 | 9D81672C1CF0271600570DF6 /* BaseOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D81672B1CF0271600570DF6 /* BaseOperation.m */; }; 21 | 9D8167311CF02A6F00570DF6 /* NSLock+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167301CF02A6F00570DF6 /* NSLock+Helpers.m */; }; 22 | 9D8167371CF0595200570DF6 /* OperationQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167361CF0595200570DF6 /* OperationQueue.m */; }; 23 | 9D81673B1CF05C8B00570DF6 /* URLDownloadingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D81673A1CF05C8B00570DF6 /* URLDownloadingOperation.m */; }; 24 | 9D81673F1CF05E6300570DF6 /* StartupImageParsingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D81673E1CF05E6300570DF6 /* StartupImageParsingOperation.m */; }; 25 | 9D8167421CF0637E00570DF6 /* BlockOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167411CF0637E00570DF6 /* BlockOperation.m */; }; 26 | 9D8167461CF069DF00570DF6 /* CacheManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167451CF069DF00570DF6 /* CacheManager.m */; }; 27 | 9D8167491CF06C1000570DF6 /* NSString+MD5.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167481CF06C1000570DF6 /* NSString+MD5.m */; }; 28 | 9D81674C1CF07BEC00570DF6 /* UIImage+Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D81674B1CF07BEC00570DF6 /* UIImage+Helpers.m */; }; 29 | 9D81674E1CF0807C00570DF6 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D81674D1CF0807C00570DF6 /* Settings.storyboard */; }; 30 | 9D8167501CF0878000570DF6 /* TimelineCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9D81674F1CF0878000570DF6 /* TimelineCell.xib */; }; 31 | 9D8167541CF088EE00570DF6 /* TimelineDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167531CF088EE00570DF6 /* TimelineDataSource.m */; }; 32 | 9D8167571CF08ACD00570DF6 /* Timeline.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167561CF08ACD00570DF6 /* Timeline.m */; }; 33 | 9D81675A1CF08AD700570DF6 /* Story.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167591CF08AD700570DF6 /* Story.m */; }; 34 | 9D81675D1CF08BE800570DF6 /* StoryBody.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D81675C1CF08BE800570DF6 /* StoryBody.m */; }; 35 | 9D8167611CF08D4A00570DF6 /* TimelineController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167601CF08D4A00570DF6 /* TimelineController.m */; }; 36 | 9D8167641CF08ED100570DF6 /* TimelineParsingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167631CF08ED100570DF6 /* TimelineParsingOperation.m */; }; 37 | 9D8167671CF08F7D00570DF6 /* ParsingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D8167661CF08F7D00570DF6 /* ParsingOperation.m */; }; 38 | 9D81676B1CF0970300570DF6 /* TimelineCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9D81676A1CF0970300570DF6 /* TimelineCell.m */; }; 39 | 9DD4A2E51CF2CB6E006FE0EA /* ImageDownloadingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4A2E41CF2CB6E006FE0EA /* ImageDownloadingOperation.m */; }; 40 | 9DD4A2E81CF2CFA6006FE0EA /* TimelineSectionHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9DD4A2E71CF2CFA6006FE0EA /* TimelineSectionHeaderView.xib */; }; 41 | 9DD4A2EB1CF2D1E1006FE0EA /* TimelineSectionHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4A2EA1CF2D1E1006FE0EA /* TimelineSectionHeaderView.m */; }; 42 | 9DD4A2ED1CF2D475006FE0EA /* CentralProgressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9DD4A2EC1CF2D475006FE0EA /* CentralProgressView.xib */; }; 43 | 9DFCC78F1CF15380005DD5AC /* TimelineTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC78E1CF15380005DD5AC /* TimelineTableViewController.m */; }; 44 | 9DFCC7921CF15851005DD5AC /* ThemeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7911CF15851005DD5AC /* ThemeManager.m */; }; 45 | 9DFCC7961CF15BB0005DD5AC /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7951CF15BB0005DD5AC /* SettingsViewController.m */; }; 46 | 9DFCC7991CF15DF7005DD5AC /* BaseTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7981CF15DF7005DD5AC /* BaseTableViewController.m */; }; 47 | 9DFCC79C1CF15ECF005DD5AC /* BaseTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC79B1CF15ECF005DD5AC /* BaseTableViewCell.m */; }; 48 | 9DFCC79F1CF1620E005DD5AC /* HUDManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC79E1CF1620E005DD5AC /* HUDManager.m */; }; 49 | 9DFCC7A11CF16397005DD5AC /* BottomToastView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9DFCC7A01CF16397005DD5AC /* BottomToastView.xib */; }; 50 | 9DFCC7A41CF1699A005DD5AC /* TopStoryPageController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7A31CF1699A005DD5AC /* TopStoryPageController.m */; }; 51 | 9DFCC7A71CF16CCC005DD5AC /* TopStoryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9DFCC7A51CF16CCC005DD5AC /* TopStoryView.xib */; }; 52 | 9DFCC7AB1CF16E6E005DD5AC /* TopStoryViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7AA1CF16E6E005DD5AC /* TopStoryViewController.m */; }; 53 | 9DFCC7AF1CF17692005DD5AC /* TopStoryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7AD1CF17692005DD5AC /* TopStoryCell.m */; }; 54 | 9DFCC7B41CF17B0F005DD5AC /* ReaderViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7B31CF17B0F005DD5AC /* ReaderViewController.m */; }; 55 | 9DFCC7B71CF17C1E005DD5AC /* StoryBodyParsingOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7B61CF17C1E005DD5AC /* StoryBodyParsingOperation.m */; }; 56 | 9DFCC7BA1CF19585005DD5AC /* CacheManagmentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DFCC7B91CF19585005DD5AC /* CacheManagmentViewController.m */; }; 57 | /* End PBXBuildFile section */ 58 | 59 | /* Begin PBXFileReference section */ 60 | 9D8166F61CF014DB00570DF6 /* ZhihuDaily.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ZhihuDaily.app; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 9D8166FA1CF014DB00570DF6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 62 | 9D8166FC1CF014DB00570DF6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 63 | 9D8166FD1CF014DB00570DF6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 64 | 9D8167061CF014DB00570DF6 /* ZhihuDaily.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ZhihuDaily.xcdatamodel; sourceTree = ""; }; 65 | 9D8167081CF014DB00570DF6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 66 | 9D81670B1CF014DB00570DF6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 67 | 9D81670D1CF014DB00570DF6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 68 | 9D8167161CF0184900570DF6 /* TimelineViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimelineViewController.h; sourceTree = ""; }; 69 | 9D8167171CF0184900570DF6 /* TimelineViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimelineViewController.m; sourceTree = ""; }; 70 | 9D81671D1CF0186D00570DF6 /* BaseViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseViewController.h; sourceTree = ""; }; 71 | 9D81671E1CF0186D00570DF6 /* BaseViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseViewController.m; sourceTree = ""; }; 72 | 9D8167201CF018D600570DF6 /* NavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NavigationController.h; sourceTree = ""; }; 73 | 9D8167211CF018D600570DF6 /* NavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationController.m; sourceTree = ""; }; 74 | 9D8167241CF01A9C00570DF6 /* StartupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StartupViewController.h; sourceTree = ""; }; 75 | 9D8167251CF01A9C00570DF6 /* StartupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StartupViewController.m; sourceTree = ""; }; 76 | 9D8167261CF01A9C00570DF6 /* StartupViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = StartupViewController.xib; sourceTree = ""; }; 77 | 9D81672A1CF0271600570DF6 /* BaseOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseOperation.h; sourceTree = ""; }; 78 | 9D81672B1CF0271600570DF6 /* BaseOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseOperation.m; sourceTree = ""; }; 79 | 9D81672F1CF02A6F00570DF6 /* NSLock+Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSLock+Helpers.h"; sourceTree = ""; }; 80 | 9D8167301CF02A6F00570DF6 /* NSLock+Helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSLock+Helpers.m"; sourceTree = ""; }; 81 | 9D8167351CF0595200570DF6 /* OperationQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OperationQueue.h; sourceTree = ""; }; 82 | 9D8167361CF0595200570DF6 /* OperationQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OperationQueue.m; sourceTree = ""; }; 83 | 9D8167391CF05C8B00570DF6 /* URLDownloadingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = URLDownloadingOperation.h; sourceTree = ""; }; 84 | 9D81673A1CF05C8B00570DF6 /* URLDownloadingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = URLDownloadingOperation.m; sourceTree = ""; }; 85 | 9D81673D1CF05E6300570DF6 /* StartupImageParsingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StartupImageParsingOperation.h; sourceTree = ""; }; 86 | 9D81673E1CF05E6300570DF6 /* StartupImageParsingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StartupImageParsingOperation.m; sourceTree = ""; }; 87 | 9D8167401CF0637E00570DF6 /* BlockOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BlockOperation.h; sourceTree = ""; }; 88 | 9D8167411CF0637E00570DF6 /* BlockOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BlockOperation.m; sourceTree = ""; }; 89 | 9D8167441CF069DF00570DF6 /* CacheManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CacheManager.h; sourceTree = ""; }; 90 | 9D8167451CF069DF00570DF6 /* CacheManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CacheManager.m; sourceTree = ""; }; 91 | 9D8167471CF06C1000570DF6 /* NSString+MD5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+MD5.h"; sourceTree = ""; }; 92 | 9D8167481CF06C1000570DF6 /* NSString+MD5.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+MD5.m"; sourceTree = ""; }; 93 | 9D81674A1CF07BEC00570DF6 /* UIImage+Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Helpers.h"; sourceTree = ""; }; 94 | 9D81674B1CF07BEC00570DF6 /* UIImage+Helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Helpers.m"; sourceTree = ""; }; 95 | 9D81674D1CF0807C00570DF6 /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = ""; }; 96 | 9D81674F1CF0878000570DF6 /* TimelineCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TimelineCell.xib; sourceTree = ""; }; 97 | 9D8167521CF088EE00570DF6 /* TimelineDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimelineDataSource.h; sourceTree = ""; }; 98 | 9D8167531CF088EE00570DF6 /* TimelineDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimelineDataSource.m; sourceTree = ""; }; 99 | 9D8167551CF08ACD00570DF6 /* Timeline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Timeline.h; sourceTree = ""; }; 100 | 9D8167561CF08ACD00570DF6 /* Timeline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Timeline.m; sourceTree = ""; }; 101 | 9D8167581CF08AD700570DF6 /* Story.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Story.h; sourceTree = ""; }; 102 | 9D8167591CF08AD700570DF6 /* Story.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Story.m; sourceTree = ""; }; 103 | 9D81675B1CF08BE800570DF6 /* StoryBody.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoryBody.h; sourceTree = ""; }; 104 | 9D81675C1CF08BE800570DF6 /* StoryBody.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoryBody.m; sourceTree = ""; }; 105 | 9D81675F1CF08D4A00570DF6 /* TimelineController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimelineController.h; sourceTree = ""; }; 106 | 9D8167601CF08D4A00570DF6 /* TimelineController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimelineController.m; sourceTree = ""; }; 107 | 9D8167621CF08ED100570DF6 /* TimelineParsingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimelineParsingOperation.h; sourceTree = ""; }; 108 | 9D8167631CF08ED100570DF6 /* TimelineParsingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimelineParsingOperation.m; sourceTree = ""; }; 109 | 9D8167651CF08F7D00570DF6 /* ParsingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParsingOperation.h; sourceTree = ""; }; 110 | 9D8167661CF08F7D00570DF6 /* ParsingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ParsingOperation.m; sourceTree = ""; }; 111 | 9D8167691CF0970300570DF6 /* TimelineCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimelineCell.h; sourceTree = ""; }; 112 | 9D81676A1CF0970300570DF6 /* TimelineCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimelineCell.m; sourceTree = ""; }; 113 | 9DD4A2E31CF2CB6E006FE0EA /* ImageDownloadingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageDownloadingOperation.h; sourceTree = ""; }; 114 | 9DD4A2E41CF2CB6E006FE0EA /* ImageDownloadingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageDownloadingOperation.m; sourceTree = ""; }; 115 | 9DD4A2E71CF2CFA6006FE0EA /* TimelineSectionHeaderView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TimelineSectionHeaderView.xib; sourceTree = ""; }; 116 | 9DD4A2E91CF2D1E1006FE0EA /* TimelineSectionHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimelineSectionHeaderView.h; sourceTree = ""; }; 117 | 9DD4A2EA1CF2D1E1006FE0EA /* TimelineSectionHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimelineSectionHeaderView.m; sourceTree = ""; }; 118 | 9DD4A2EC1CF2D475006FE0EA /* CentralProgressView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CentralProgressView.xib; sourceTree = ""; }; 119 | 9DFCC78D1CF15380005DD5AC /* TimelineTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimelineTableViewController.h; sourceTree = ""; }; 120 | 9DFCC78E1CF15380005DD5AC /* TimelineTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TimelineTableViewController.m; sourceTree = ""; }; 121 | 9DFCC7901CF15851005DD5AC /* ThemeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThemeManager.h; sourceTree = ""; }; 122 | 9DFCC7911CF15851005DD5AC /* ThemeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThemeManager.m; sourceTree = ""; }; 123 | 9DFCC7941CF15BB0005DD5AC /* SettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsViewController.h; sourceTree = ""; }; 124 | 9DFCC7951CF15BB0005DD5AC /* SettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsViewController.m; sourceTree = ""; }; 125 | 9DFCC7971CF15DF7005DD5AC /* BaseTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseTableViewController.h; sourceTree = ""; }; 126 | 9DFCC7981CF15DF7005DD5AC /* BaseTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseTableViewController.m; sourceTree = ""; }; 127 | 9DFCC79A1CF15ECF005DD5AC /* BaseTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BaseTableViewCell.h; sourceTree = ""; }; 128 | 9DFCC79B1CF15ECF005DD5AC /* BaseTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BaseTableViewCell.m; sourceTree = ""; }; 129 | 9DFCC79D1CF1620E005DD5AC /* HUDManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HUDManager.h; sourceTree = ""; }; 130 | 9DFCC79E1CF1620E005DD5AC /* HUDManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HUDManager.m; sourceTree = ""; }; 131 | 9DFCC7A01CF16397005DD5AC /* BottomToastView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BottomToastView.xib; sourceTree = ""; }; 132 | 9DFCC7A21CF1699A005DD5AC /* TopStoryPageController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TopStoryPageController.h; sourceTree = ""; }; 133 | 9DFCC7A31CF1699A005DD5AC /* TopStoryPageController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopStoryPageController.m; sourceTree = ""; }; 134 | 9DFCC7A61CF16CCC005DD5AC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/TopStoryView.xib; sourceTree = ""; }; 135 | 9DFCC7A91CF16E6E005DD5AC /* TopStoryViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TopStoryViewController.h; sourceTree = ""; }; 136 | 9DFCC7AA1CF16E6E005DD5AC /* TopStoryViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopStoryViewController.m; sourceTree = ""; }; 137 | 9DFCC7AC1CF17692005DD5AC /* TopStoryCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TopStoryCell.h; sourceTree = ""; }; 138 | 9DFCC7AD1CF17692005DD5AC /* TopStoryCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TopStoryCell.m; sourceTree = ""; }; 139 | 9DFCC7B21CF17B0F005DD5AC /* ReaderViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReaderViewController.h; sourceTree = ""; }; 140 | 9DFCC7B31CF17B0F005DD5AC /* ReaderViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReaderViewController.m; sourceTree = ""; }; 141 | 9DFCC7B51CF17C1E005DD5AC /* StoryBodyParsingOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StoryBodyParsingOperation.h; sourceTree = ""; }; 142 | 9DFCC7B61CF17C1E005DD5AC /* StoryBodyParsingOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StoryBodyParsingOperation.m; sourceTree = ""; }; 143 | 9DFCC7B81CF19585005DD5AC /* CacheManagmentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CacheManagmentViewController.h; sourceTree = ""; }; 144 | 9DFCC7B91CF19585005DD5AC /* CacheManagmentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CacheManagmentViewController.m; sourceTree = ""; }; 145 | /* End PBXFileReference section */ 146 | 147 | /* Begin PBXFrameworksBuildPhase section */ 148 | 9D8166F31CF014DB00570DF6 /* Frameworks */ = { 149 | isa = PBXFrameworksBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | ); 153 | runOnlyForDeploymentPostprocessing = 0; 154 | }; 155 | /* End PBXFrameworksBuildPhase section */ 156 | 157 | /* Begin PBXGroup section */ 158 | 9D8166ED1CF014DA00570DF6 = { 159 | isa = PBXGroup; 160 | children = ( 161 | 9D8166F81CF014DB00570DF6 /* ZhihuDaily */, 162 | 9D8166F71CF014DB00570DF6 /* Products */, 163 | ); 164 | sourceTree = ""; 165 | }; 166 | 9D8166F71CF014DB00570DF6 /* Products */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 9D8166F61CF014DB00570DF6 /* ZhihuDaily.app */, 170 | ); 171 | name = Products; 172 | sourceTree = ""; 173 | }; 174 | 9D8166F81CF014DB00570DF6 /* ZhihuDaily */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 9D81672E1CF02A2300570DF6 /* Extensions */, 178 | 9D8167431CF069BE00570DF6 /* Services */, 179 | 9D8167291CF026FA00570DF6 /* Operations */, 180 | 9D8167131CF015E000570DF6 /* Models */, 181 | 9D8167511CF088D500570DF6 /* Data Sources */, 182 | 9D81675E1CF08D3100570DF6 /* Controllers */, 183 | 9D8167681CF096E500570DF6 /* Views */, 184 | 9D8167141CF0181700570DF6 /* View Controllers */, 185 | 9D8166F91CF014DB00570DF6 /* Supporting Files */, 186 | ); 187 | path = ZhihuDaily; 188 | sourceTree = ""; 189 | }; 190 | 9D8166F91CF014DB00570DF6 /* Supporting Files */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | 9D8167151CF0182300570DF6 /* Xibs & Storyboards */, 194 | 9D8166FC1CF014DB00570DF6 /* AppDelegate.h */, 195 | 9D8166FD1CF014DB00570DF6 /* AppDelegate.m */, 196 | 9D8167081CF014DB00570DF6 /* Assets.xcassets */, 197 | 9D81670A1CF014DB00570DF6 /* LaunchScreen.storyboard */, 198 | 9D81670D1CF014DB00570DF6 /* Info.plist */, 199 | 9D8167051CF014DB00570DF6 /* ZhihuDaily.xcdatamodeld */, 200 | 9D8166FA1CF014DB00570DF6 /* main.m */, 201 | ); 202 | name = "Supporting Files"; 203 | sourceTree = ""; 204 | }; 205 | 9D8167131CF015E000570DF6 /* Models */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | 9D8167551CF08ACD00570DF6 /* Timeline.h */, 209 | 9D8167561CF08ACD00570DF6 /* Timeline.m */, 210 | 9D8167581CF08AD700570DF6 /* Story.h */, 211 | 9D8167591CF08AD700570DF6 /* Story.m */, 212 | 9D81675B1CF08BE800570DF6 /* StoryBody.h */, 213 | 9D81675C1CF08BE800570DF6 /* StoryBody.m */, 214 | ); 215 | name = Models; 216 | sourceTree = ""; 217 | }; 218 | 9D8167141CF0181700570DF6 /* View Controllers */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 9D8167231CF01A8400570DF6 /* Startup */, 222 | 9D81671C1CF0185E00570DF6 /* Common */, 223 | 9D81671B1CF0185100570DF6 /* Timeline */, 224 | 9DFCC7A81CF16E58005DD5AC /* Top Story */, 225 | 9DFCC7B11CF17A6C005DD5AC /* Reader */, 226 | 9DFCC7931CF15B94005DD5AC /* Settings */, 227 | ); 228 | name = "View Controllers"; 229 | sourceTree = ""; 230 | }; 231 | 9D8167151CF0182300570DF6 /* Xibs & Storyboards */ = { 232 | isa = PBXGroup; 233 | children = ( 234 | 9D8167261CF01A9C00570DF6 /* StartupViewController.xib */, 235 | 9DFCC7A51CF16CCC005DD5AC /* TopStoryView.xib */, 236 | 9D81674F1CF0878000570DF6 /* TimelineCell.xib */, 237 | 9D81674D1CF0807C00570DF6 /* Settings.storyboard */, 238 | 9DFCC7A01CF16397005DD5AC /* BottomToastView.xib */, 239 | 9DD4A2EC1CF2D475006FE0EA /* CentralProgressView.xib */, 240 | 9DD4A2E71CF2CFA6006FE0EA /* TimelineSectionHeaderView.xib */, 241 | ); 242 | name = "Xibs & Storyboards"; 243 | sourceTree = ""; 244 | }; 245 | 9D81671B1CF0185100570DF6 /* Timeline */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 9D8167161CF0184900570DF6 /* TimelineViewController.h */, 249 | 9D8167171CF0184900570DF6 /* TimelineViewController.m */, 250 | 9DFCC78D1CF15380005DD5AC /* TimelineTableViewController.h */, 251 | 9DFCC78E1CF15380005DD5AC /* TimelineTableViewController.m */, 252 | 9DFCC7A21CF1699A005DD5AC /* TopStoryPageController.h */, 253 | 9DFCC7A31CF1699A005DD5AC /* TopStoryPageController.m */, 254 | ); 255 | name = Timeline; 256 | sourceTree = ""; 257 | }; 258 | 9D81671C1CF0185E00570DF6 /* Common */ = { 259 | isa = PBXGroup; 260 | children = ( 261 | 9D81671D1CF0186D00570DF6 /* BaseViewController.h */, 262 | 9D81671E1CF0186D00570DF6 /* BaseViewController.m */, 263 | 9D8167201CF018D600570DF6 /* NavigationController.h */, 264 | 9D8167211CF018D600570DF6 /* NavigationController.m */, 265 | 9DFCC7971CF15DF7005DD5AC /* BaseTableViewController.h */, 266 | 9DFCC7981CF15DF7005DD5AC /* BaseTableViewController.m */, 267 | ); 268 | name = Common; 269 | sourceTree = ""; 270 | }; 271 | 9D8167231CF01A8400570DF6 /* Startup */ = { 272 | isa = PBXGroup; 273 | children = ( 274 | 9D8167241CF01A9C00570DF6 /* StartupViewController.h */, 275 | 9D8167251CF01A9C00570DF6 /* StartupViewController.m */, 276 | ); 277 | name = Startup; 278 | sourceTree = ""; 279 | }; 280 | 9D8167291CF026FA00570DF6 /* Operations */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | 9D81672D1CF0271B00570DF6 /* Common */, 284 | 9D8167381CF05AD200570DF6 /* Networking */, 285 | 9D81673C1CF05E1B00570DF6 /* Parsing */, 286 | ); 287 | name = Operations; 288 | sourceTree = ""; 289 | }; 290 | 9D81672D1CF0271B00570DF6 /* Common */ = { 291 | isa = PBXGroup; 292 | children = ( 293 | 9D81672A1CF0271600570DF6 /* BaseOperation.h */, 294 | 9D81672B1CF0271600570DF6 /* BaseOperation.m */, 295 | 9D8167351CF0595200570DF6 /* OperationQueue.h */, 296 | 9D8167361CF0595200570DF6 /* OperationQueue.m */, 297 | 9D8167401CF0637E00570DF6 /* BlockOperation.h */, 298 | 9D8167411CF0637E00570DF6 /* BlockOperation.m */, 299 | ); 300 | name = Common; 301 | sourceTree = ""; 302 | }; 303 | 9D81672E1CF02A2300570DF6 /* Extensions */ = { 304 | isa = PBXGroup; 305 | children = ( 306 | 9D81672F1CF02A6F00570DF6 /* NSLock+Helpers.h */, 307 | 9D8167301CF02A6F00570DF6 /* NSLock+Helpers.m */, 308 | 9D8167471CF06C1000570DF6 /* NSString+MD5.h */, 309 | 9D8167481CF06C1000570DF6 /* NSString+MD5.m */, 310 | 9D81674A1CF07BEC00570DF6 /* UIImage+Helpers.h */, 311 | 9D81674B1CF07BEC00570DF6 /* UIImage+Helpers.m */, 312 | ); 313 | name = Extensions; 314 | sourceTree = ""; 315 | }; 316 | 9D8167381CF05AD200570DF6 /* Networking */ = { 317 | isa = PBXGroup; 318 | children = ( 319 | 9D8167391CF05C8B00570DF6 /* URLDownloadingOperation.h */, 320 | 9D81673A1CF05C8B00570DF6 /* URLDownloadingOperation.m */, 321 | 9DD4A2E31CF2CB6E006FE0EA /* ImageDownloadingOperation.h */, 322 | 9DD4A2E41CF2CB6E006FE0EA /* ImageDownloadingOperation.m */, 323 | ); 324 | name = Networking; 325 | sourceTree = ""; 326 | }; 327 | 9D81673C1CF05E1B00570DF6 /* Parsing */ = { 328 | isa = PBXGroup; 329 | children = ( 330 | 9D8167651CF08F7D00570DF6 /* ParsingOperation.h */, 331 | 9D8167661CF08F7D00570DF6 /* ParsingOperation.m */, 332 | 9D81673D1CF05E6300570DF6 /* StartupImageParsingOperation.h */, 333 | 9D81673E1CF05E6300570DF6 /* StartupImageParsingOperation.m */, 334 | 9D8167621CF08ED100570DF6 /* TimelineParsingOperation.h */, 335 | 9D8167631CF08ED100570DF6 /* TimelineParsingOperation.m */, 336 | 9DFCC7B51CF17C1E005DD5AC /* StoryBodyParsingOperation.h */, 337 | 9DFCC7B61CF17C1E005DD5AC /* StoryBodyParsingOperation.m */, 338 | ); 339 | name = Parsing; 340 | sourceTree = ""; 341 | }; 342 | 9D8167431CF069BE00570DF6 /* Services */ = { 343 | isa = PBXGroup; 344 | children = ( 345 | 9D8167441CF069DF00570DF6 /* CacheManager.h */, 346 | 9D8167451CF069DF00570DF6 /* CacheManager.m */, 347 | 9DFCC7901CF15851005DD5AC /* ThemeManager.h */, 348 | 9DFCC7911CF15851005DD5AC /* ThemeManager.m */, 349 | 9DFCC79D1CF1620E005DD5AC /* HUDManager.h */, 350 | 9DFCC79E1CF1620E005DD5AC /* HUDManager.m */, 351 | ); 352 | name = Services; 353 | sourceTree = ""; 354 | }; 355 | 9D8167511CF088D500570DF6 /* Data Sources */ = { 356 | isa = PBXGroup; 357 | children = ( 358 | 9D8167521CF088EE00570DF6 /* TimelineDataSource.h */, 359 | 9D8167531CF088EE00570DF6 /* TimelineDataSource.m */, 360 | ); 361 | name = "Data Sources"; 362 | sourceTree = ""; 363 | }; 364 | 9D81675E1CF08D3100570DF6 /* Controllers */ = { 365 | isa = PBXGroup; 366 | children = ( 367 | 9D81675F1CF08D4A00570DF6 /* TimelineController.h */, 368 | 9D8167601CF08D4A00570DF6 /* TimelineController.m */, 369 | ); 370 | name = Controllers; 371 | sourceTree = ""; 372 | }; 373 | 9D8167681CF096E500570DF6 /* Views */ = { 374 | isa = PBXGroup; 375 | children = ( 376 | 9DD4A2E61CF2CF62006FE0EA /* Supplementaries */, 377 | 9D81676C1CF0970600570DF6 /* Cells */, 378 | ); 379 | name = Views; 380 | sourceTree = ""; 381 | }; 382 | 9D81676C1CF0970600570DF6 /* Cells */ = { 383 | isa = PBXGroup; 384 | children = ( 385 | 9DFCC79A1CF15ECF005DD5AC /* BaseTableViewCell.h */, 386 | 9DFCC79B1CF15ECF005DD5AC /* BaseTableViewCell.m */, 387 | 9D8167691CF0970300570DF6 /* TimelineCell.h */, 388 | 9D81676A1CF0970300570DF6 /* TimelineCell.m */, 389 | 9DFCC7AC1CF17692005DD5AC /* TopStoryCell.h */, 390 | 9DFCC7AD1CF17692005DD5AC /* TopStoryCell.m */, 391 | ); 392 | name = Cells; 393 | sourceTree = ""; 394 | }; 395 | 9DD4A2E61CF2CF62006FE0EA /* Supplementaries */ = { 396 | isa = PBXGroup; 397 | children = ( 398 | 9DD4A2E91CF2D1E1006FE0EA /* TimelineSectionHeaderView.h */, 399 | 9DD4A2EA1CF2D1E1006FE0EA /* TimelineSectionHeaderView.m */, 400 | ); 401 | name = Supplementaries; 402 | sourceTree = ""; 403 | }; 404 | 9DFCC7931CF15B94005DD5AC /* Settings */ = { 405 | isa = PBXGroup; 406 | children = ( 407 | 9DFCC7941CF15BB0005DD5AC /* SettingsViewController.h */, 408 | 9DFCC7951CF15BB0005DD5AC /* SettingsViewController.m */, 409 | 9DFCC7B81CF19585005DD5AC /* CacheManagmentViewController.h */, 410 | 9DFCC7B91CF19585005DD5AC /* CacheManagmentViewController.m */, 411 | ); 412 | name = Settings; 413 | sourceTree = ""; 414 | }; 415 | 9DFCC7A81CF16E58005DD5AC /* Top Story */ = { 416 | isa = PBXGroup; 417 | children = ( 418 | 9DFCC7A91CF16E6E005DD5AC /* TopStoryViewController.h */, 419 | 9DFCC7AA1CF16E6E005DD5AC /* TopStoryViewController.m */, 420 | ); 421 | name = "Top Story"; 422 | sourceTree = ""; 423 | }; 424 | 9DFCC7B11CF17A6C005DD5AC /* Reader */ = { 425 | isa = PBXGroup; 426 | children = ( 427 | 9DFCC7B21CF17B0F005DD5AC /* ReaderViewController.h */, 428 | 9DFCC7B31CF17B0F005DD5AC /* ReaderViewController.m */, 429 | ); 430 | name = Reader; 431 | sourceTree = ""; 432 | }; 433 | /* End PBXGroup section */ 434 | 435 | /* Begin PBXNativeTarget section */ 436 | 9D8166F51CF014DB00570DF6 /* ZhihuDaily */ = { 437 | isa = PBXNativeTarget; 438 | buildConfigurationList = 9D8167101CF014DB00570DF6 /* Build configuration list for PBXNativeTarget "ZhihuDaily" */; 439 | buildPhases = ( 440 | 9D8166F21CF014DB00570DF6 /* Sources */, 441 | 9D8166F31CF014DB00570DF6 /* Frameworks */, 442 | 9D8166F41CF014DB00570DF6 /* Resources */, 443 | ); 444 | buildRules = ( 445 | ); 446 | dependencies = ( 447 | ); 448 | name = ZhihuDaily; 449 | productName = ZhihuDaily; 450 | productReference = 9D8166F61CF014DB00570DF6 /* ZhihuDaily.app */; 451 | productType = "com.apple.product-type.application"; 452 | }; 453 | /* End PBXNativeTarget section */ 454 | 455 | /* Begin PBXProject section */ 456 | 9D8166EE1CF014DA00570DF6 /* Project object */ = { 457 | isa = PBXProject; 458 | attributes = { 459 | LastUpgradeCheck = 0730; 460 | ORGANIZATIONNAME = Cyandev; 461 | TargetAttributes = { 462 | 9D8166F51CF014DB00570DF6 = { 463 | CreatedOnToolsVersion = 7.3; 464 | DevelopmentTeam = S8ZP685HLU; 465 | }; 466 | }; 467 | }; 468 | buildConfigurationList = 9D8166F11CF014DA00570DF6 /* Build configuration list for PBXProject "ZhihuDaily" */; 469 | compatibilityVersion = "Xcode 3.2"; 470 | developmentRegion = English; 471 | hasScannedForEncodings = 0; 472 | knownRegions = ( 473 | en, 474 | Base, 475 | ); 476 | mainGroup = 9D8166ED1CF014DA00570DF6; 477 | productRefGroup = 9D8166F71CF014DB00570DF6 /* Products */; 478 | projectDirPath = ""; 479 | projectRoot = ""; 480 | targets = ( 481 | 9D8166F51CF014DB00570DF6 /* ZhihuDaily */, 482 | ); 483 | }; 484 | /* End PBXProject section */ 485 | 486 | /* Begin PBXResourcesBuildPhase section */ 487 | 9D8166F41CF014DB00570DF6 /* Resources */ = { 488 | isa = PBXResourcesBuildPhase; 489 | buildActionMask = 2147483647; 490 | files = ( 491 | 9D8167281CF01A9C00570DF6 /* StartupViewController.xib in Resources */, 492 | 9DFCC7A71CF16CCC005DD5AC /* TopStoryView.xib in Resources */, 493 | 9DFCC7A11CF16397005DD5AC /* BottomToastView.xib in Resources */, 494 | 9DD4A2ED1CF2D475006FE0EA /* CentralProgressView.xib in Resources */, 495 | 9D8167501CF0878000570DF6 /* TimelineCell.xib in Resources */, 496 | 9D81670C1CF014DB00570DF6 /* LaunchScreen.storyboard in Resources */, 497 | 9D8167091CF014DB00570DF6 /* Assets.xcassets in Resources */, 498 | 9DD4A2E81CF2CFA6006FE0EA /* TimelineSectionHeaderView.xib in Resources */, 499 | 9D81674E1CF0807C00570DF6 /* Settings.storyboard in Resources */, 500 | ); 501 | runOnlyForDeploymentPostprocessing = 0; 502 | }; 503 | /* End PBXResourcesBuildPhase section */ 504 | 505 | /* Begin PBXSourcesBuildPhase section */ 506 | 9D8166F21CF014DB00570DF6 /* Sources */ = { 507 | isa = PBXSourcesBuildPhase; 508 | buildActionMask = 2147483647; 509 | files = ( 510 | 9D81673F1CF05E6300570DF6 /* StartupImageParsingOperation.m in Sources */, 511 | 9DFCC7B41CF17B0F005DD5AC /* ReaderViewController.m in Sources */, 512 | 9D8167271CF01A9C00570DF6 /* StartupViewController.m in Sources */, 513 | 9D8167671CF08F7D00570DF6 /* ParsingOperation.m in Sources */, 514 | 9DFCC7961CF15BB0005DD5AC /* SettingsViewController.m in Sources */, 515 | 9D8167461CF069DF00570DF6 /* CacheManager.m in Sources */, 516 | 9D81675A1CF08AD700570DF6 /* Story.m in Sources */, 517 | 9DFCC7AB1CF16E6E005DD5AC /* TopStoryViewController.m in Sources */, 518 | 9DFCC7AF1CF17692005DD5AC /* TopStoryCell.m in Sources */, 519 | 9D8166FE1CF014DB00570DF6 /* AppDelegate.m in Sources */, 520 | 9D81675D1CF08BE800570DF6 /* StoryBody.m in Sources */, 521 | 9D81671F1CF0186D00570DF6 /* BaseViewController.m in Sources */, 522 | 9D81676B1CF0970300570DF6 /* TimelineCell.m in Sources */, 523 | 9DFCC7991CF15DF7005DD5AC /* BaseTableViewController.m in Sources */, 524 | 9DFCC7B71CF17C1E005DD5AC /* StoryBodyParsingOperation.m in Sources */, 525 | 9DFCC79F1CF1620E005DD5AC /* HUDManager.m in Sources */, 526 | 9DFCC7A41CF1699A005DD5AC /* TopStoryPageController.m in Sources */, 527 | 9D8167371CF0595200570DF6 /* OperationQueue.m in Sources */, 528 | 9D8167191CF0184900570DF6 /* TimelineViewController.m in Sources */, 529 | 9DFCC7921CF15851005DD5AC /* ThemeManager.m in Sources */, 530 | 9D8167611CF08D4A00570DF6 /* TimelineController.m in Sources */, 531 | 9D8167221CF018D600570DF6 /* NavigationController.m in Sources */, 532 | 9D8167071CF014DB00570DF6 /* ZhihuDaily.xcdatamodeld in Sources */, 533 | 9DFCC78F1CF15380005DD5AC /* TimelineTableViewController.m in Sources */, 534 | 9D81672C1CF0271600570DF6 /* BaseOperation.m in Sources */, 535 | 9DFCC79C1CF15ECF005DD5AC /* BaseTableViewCell.m in Sources */, 536 | 9D8167641CF08ED100570DF6 /* TimelineParsingOperation.m in Sources */, 537 | 9D8167421CF0637E00570DF6 /* BlockOperation.m in Sources */, 538 | 9D81674C1CF07BEC00570DF6 /* UIImage+Helpers.m in Sources */, 539 | 9DD4A2E51CF2CB6E006FE0EA /* ImageDownloadingOperation.m in Sources */, 540 | 9D8167571CF08ACD00570DF6 /* Timeline.m in Sources */, 541 | 9D8166FB1CF014DB00570DF6 /* main.m in Sources */, 542 | 9D8167491CF06C1000570DF6 /* NSString+MD5.m in Sources */, 543 | 9DFCC7BA1CF19585005DD5AC /* CacheManagmentViewController.m in Sources */, 544 | 9D8167311CF02A6F00570DF6 /* NSLock+Helpers.m in Sources */, 545 | 9D81673B1CF05C8B00570DF6 /* URLDownloadingOperation.m in Sources */, 546 | 9DD4A2EB1CF2D1E1006FE0EA /* TimelineSectionHeaderView.m in Sources */, 547 | 9D8167541CF088EE00570DF6 /* TimelineDataSource.m in Sources */, 548 | ); 549 | runOnlyForDeploymentPostprocessing = 0; 550 | }; 551 | /* End PBXSourcesBuildPhase section */ 552 | 553 | /* Begin PBXVariantGroup section */ 554 | 9D81670A1CF014DB00570DF6 /* LaunchScreen.storyboard */ = { 555 | isa = PBXVariantGroup; 556 | children = ( 557 | 9D81670B1CF014DB00570DF6 /* Base */, 558 | ); 559 | name = LaunchScreen.storyboard; 560 | sourceTree = ""; 561 | }; 562 | 9DFCC7A51CF16CCC005DD5AC /* TopStoryView.xib */ = { 563 | isa = PBXVariantGroup; 564 | children = ( 565 | 9DFCC7A61CF16CCC005DD5AC /* Base */, 566 | ); 567 | name = TopStoryView.xib; 568 | sourceTree = ""; 569 | }; 570 | /* End PBXVariantGroup section */ 571 | 572 | /* Begin XCBuildConfiguration section */ 573 | 9D81670E1CF014DB00570DF6 /* Debug */ = { 574 | isa = XCBuildConfiguration; 575 | buildSettings = { 576 | ALWAYS_SEARCH_USER_PATHS = NO; 577 | CLANG_ANALYZER_NONNULL = YES; 578 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 579 | CLANG_CXX_LIBRARY = "libc++"; 580 | CLANG_ENABLE_MODULES = YES; 581 | CLANG_ENABLE_OBJC_ARC = YES; 582 | CLANG_WARN_BOOL_CONVERSION = YES; 583 | CLANG_WARN_CONSTANT_CONVERSION = YES; 584 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 585 | CLANG_WARN_EMPTY_BODY = YES; 586 | CLANG_WARN_ENUM_CONVERSION = YES; 587 | CLANG_WARN_INT_CONVERSION = YES; 588 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 589 | CLANG_WARN_UNREACHABLE_CODE = YES; 590 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 591 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 592 | COPY_PHASE_STRIP = NO; 593 | DEBUG_INFORMATION_FORMAT = dwarf; 594 | ENABLE_STRICT_OBJC_MSGSEND = YES; 595 | ENABLE_TESTABILITY = YES; 596 | GCC_C_LANGUAGE_STANDARD = gnu99; 597 | GCC_DYNAMIC_NO_PIC = NO; 598 | GCC_NO_COMMON_BLOCKS = YES; 599 | GCC_OPTIMIZATION_LEVEL = 0; 600 | GCC_PREPROCESSOR_DEFINITIONS = ( 601 | "DEBUG=1", 602 | "$(inherited)", 603 | ); 604 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 605 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 606 | GCC_WARN_UNDECLARED_SELECTOR = YES; 607 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 608 | GCC_WARN_UNUSED_FUNCTION = YES; 609 | GCC_WARN_UNUSED_VARIABLE = YES; 610 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 611 | MTL_ENABLE_DEBUG_INFO = YES; 612 | ONLY_ACTIVE_ARCH = YES; 613 | SDKROOT = iphoneos; 614 | }; 615 | name = Debug; 616 | }; 617 | 9D81670F1CF014DB00570DF6 /* Release */ = { 618 | isa = XCBuildConfiguration; 619 | buildSettings = { 620 | ALWAYS_SEARCH_USER_PATHS = NO; 621 | CLANG_ANALYZER_NONNULL = YES; 622 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 623 | CLANG_CXX_LIBRARY = "libc++"; 624 | CLANG_ENABLE_MODULES = YES; 625 | CLANG_ENABLE_OBJC_ARC = YES; 626 | CLANG_WARN_BOOL_CONVERSION = YES; 627 | CLANG_WARN_CONSTANT_CONVERSION = YES; 628 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 629 | CLANG_WARN_EMPTY_BODY = YES; 630 | CLANG_WARN_ENUM_CONVERSION = YES; 631 | CLANG_WARN_INT_CONVERSION = YES; 632 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 633 | CLANG_WARN_UNREACHABLE_CODE = YES; 634 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 635 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 636 | COPY_PHASE_STRIP = NO; 637 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 638 | ENABLE_NS_ASSERTIONS = NO; 639 | ENABLE_STRICT_OBJC_MSGSEND = YES; 640 | GCC_C_LANGUAGE_STANDARD = gnu99; 641 | GCC_NO_COMMON_BLOCKS = YES; 642 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 643 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 644 | GCC_WARN_UNDECLARED_SELECTOR = YES; 645 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 646 | GCC_WARN_UNUSED_FUNCTION = YES; 647 | GCC_WARN_UNUSED_VARIABLE = YES; 648 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 649 | MTL_ENABLE_DEBUG_INFO = NO; 650 | SDKROOT = iphoneos; 651 | VALIDATE_PRODUCT = YES; 652 | }; 653 | name = Release; 654 | }; 655 | 9D8167111CF014DB00570DF6 /* Debug */ = { 656 | isa = XCBuildConfiguration; 657 | buildSettings = { 658 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 659 | INFOPLIST_FILE = ZhihuDaily/Info.plist; 660 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 661 | PRODUCT_BUNDLE_IDENTIFIER = com.cyandev.ZhihuDaily; 662 | PRODUCT_NAME = "$(TARGET_NAME)"; 663 | }; 664 | name = Debug; 665 | }; 666 | 9D8167121CF014DB00570DF6 /* Release */ = { 667 | isa = XCBuildConfiguration; 668 | buildSettings = { 669 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 670 | INFOPLIST_FILE = ZhihuDaily/Info.plist; 671 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 672 | PRODUCT_BUNDLE_IDENTIFIER = com.cyandev.ZhihuDaily; 673 | PRODUCT_NAME = "$(TARGET_NAME)"; 674 | }; 675 | name = Release; 676 | }; 677 | /* End XCBuildConfiguration section */ 678 | 679 | /* Begin XCConfigurationList section */ 680 | 9D8166F11CF014DA00570DF6 /* Build configuration list for PBXProject "ZhihuDaily" */ = { 681 | isa = XCConfigurationList; 682 | buildConfigurations = ( 683 | 9D81670E1CF014DB00570DF6 /* Debug */, 684 | 9D81670F1CF014DB00570DF6 /* Release */, 685 | ); 686 | defaultConfigurationIsVisible = 0; 687 | defaultConfigurationName = Release; 688 | }; 689 | 9D8167101CF014DB00570DF6 /* Build configuration list for PBXNativeTarget "ZhihuDaily" */ = { 690 | isa = XCConfigurationList; 691 | buildConfigurations = ( 692 | 9D8167111CF014DB00570DF6 /* Debug */, 693 | 9D8167121CF014DB00570DF6 /* Release */, 694 | ); 695 | defaultConfigurationIsVisible = 0; 696 | defaultConfigurationName = Release; 697 | }; 698 | /* End XCConfigurationList section */ 699 | 700 | /* Begin XCVersionGroup section */ 701 | 9D8167051CF014DB00570DF6 /* ZhihuDaily.xcdatamodeld */ = { 702 | isa = XCVersionGroup; 703 | children = ( 704 | 9D8167061CF014DB00570DF6 /* ZhihuDaily.xcdatamodel */, 705 | ); 706 | currentVersion = 9D8167061CF014DB00570DF6 /* ZhihuDaily.xcdatamodel */; 707 | path = ZhihuDaily.xcdatamodeld; 708 | sourceTree = ""; 709 | versionGroupType = wrapper.xcdatamodel; 710 | }; 711 | /* End XCVersionGroup section */ 712 | }; 713 | rootObject = 9D8166EE1CF014DA00570DF6 /* Project object */; 714 | } 715 | -------------------------------------------------------------------------------- /ZhihuDaily.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ZhihuDaily/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface AppDelegate : UIResponder 13 | 14 | @property (strong, nonatomic) UIWindow *window; 15 | @property (weak, nonatomic) UIViewController *mainViewController; 16 | 17 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 18 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 19 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 20 | @property (readonly, assign, nonatomic) BOOL supportsCoreData; 21 | 22 | + (instancetype)sharedDelegate; 23 | 24 | - (void)saveContext; 25 | @property (NS_NONATOMIC_IOSONLY, readonly, copy) NSURL *applicationDocumentsDirectory; 26 | @property (NS_NONATOMIC_IOSONLY, readonly, copy) NSURL *applicationCachesDirectory; 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /ZhihuDaily/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "CacheManager.h" 11 | #import "StartupViewController.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | + (instancetype)sharedDelegate { 20 | return [UIApplication sharedApplication].delegate; 21 | } 22 | 23 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 24 | 25 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 26 | self.window.rootViewController = [[StartupViewController alloc] init]; 27 | [self.window makeKeyAndVisible]; 28 | 29 | [self initializeManagedObjectContext]; 30 | 31 | return YES; 32 | } 33 | 34 | - (void)applicationWillTerminate:(UIApplication *)application { 35 | [self saveContext]; 36 | } 37 | 38 | - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { 39 | [[CacheManager defaultManager] clearMemoryCaches]; 40 | } 41 | 42 | #pragma mark - Core Data stack 43 | 44 | @synthesize managedObjectContext = _managedObjectContext; 45 | @synthesize managedObjectModel = _managedObjectModel; 46 | @synthesize persistentStoreCoordinator = _persistentStoreCoordinator; 47 | @synthesize supportsCoreData = _supportsCoreData; 48 | 49 | - (NSManagedObjectModel *)managedObjectModel { 50 | if (_managedObjectModel == nil) { 51 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"ZhihuDaily" withExtension:@"momd"]; 52 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 53 | } 54 | return _managedObjectModel; 55 | } 56 | 57 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { 58 | if (_persistentStoreCoordinator != nil) { 59 | return _persistentStoreCoordinator; 60 | } 61 | 62 | // Create the coordinator and store 63 | 64 | _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel]; 65 | NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"ZhihuDaily.sqlite"]; 66 | NSError *error = nil; 67 | if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { 68 | NSLog(@"Unresolved error %@, %@", error, error.userInfo); 69 | _supportsCoreData = NO; 70 | } else { 71 | _supportsCoreData = YES; 72 | } 73 | 74 | return _persistentStoreCoordinator; 75 | } 76 | 77 | - (void)initializeManagedObjectContext { 78 | NSPersistentStoreCoordinator *coordinator = self.persistentStoreCoordinator; 79 | if (!coordinator) { 80 | return; 81 | } 82 | _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 83 | _managedObjectContext.persistentStoreCoordinator = coordinator; 84 | } 85 | 86 | #pragma mark - Core Data Saving support 87 | 88 | - (void)saveContext { 89 | NSManagedObjectContext *managedObjectContext = self.managedObjectContext; 90 | if (managedObjectContext != nil) { 91 | NSError *error = nil; 92 | if (managedObjectContext.hasChanges && ![managedObjectContext save:&error]) { 93 | NSLog(@"Unresolved error %@, %@", error, error.userInfo); 94 | _supportsCoreData = NO; 95 | } 96 | } 97 | } 98 | 99 | #pragma mark - Utility 100 | 101 | - (NSURL *)applicationDocumentsDirectory { 102 | return [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject; 103 | } 104 | 105 | - (NSURL *)applicationCachesDirectory { 106 | return [[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].lastObject; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-gray-small.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo-gray-small.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "logo-gray-small@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "logo-gray-small@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-gray-small.imageset/logo-gray-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/ZhihuDaily/Assets.xcassets/logo-gray-small.imageset/logo-gray-small.png -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-gray-small.imageset/logo-gray-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/ZhihuDaily/Assets.xcassets/logo-gray-small.imageset/logo-gray-small@2x.png -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-gray-small.imageset/logo-gray-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/ZhihuDaily/Assets.xcassets/logo-gray-small.imageset/logo-gray-small@3x.png -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-white.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "logo-white.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "logo-white@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "logo-white@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-white.imageset/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/ZhihuDaily/Assets.xcassets/logo-white.imageset/logo-white.png -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-white.imageset/logo-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/ZhihuDaily/Assets.xcassets/logo-white.imageset/logo-white@2x.png -------------------------------------------------------------------------------- /ZhihuDaily/Assets.xcassets/logo-white.imageset/logo-white@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/ZhihuDaily-iOS/13063dfcbfa403126d5071028eb828c0f5bec789/ZhihuDaily/Assets.xcassets/logo-white.imageset/logo-white@3x.png -------------------------------------------------------------------------------- /ZhihuDaily/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /ZhihuDaily/Base.lproj/TopStoryView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, OperationState) { 12 | OperationStateInitialized, 13 | OperationStatePending, 14 | OperationStateEvaluating, 15 | OperationStateReady, 16 | OperationStateExecuting, 17 | OperationStateFinished 18 | }; 19 | 20 | @interface BaseOperation : NSOperation 21 | 22 | @property (readonly, assign, atomic) OperationState state; 23 | @property (readonly, strong, nonatomic) NSArray *errors; 24 | @property (readonly, assign, nonatomic) BOOL hasErrors; 25 | @property (readonly, weak, nonatomic) NSOperationQueue *parentQueue; 26 | 27 | - (void)willEnqueue:(NSOperationQueue *)queue; 28 | 29 | - (void)executeMain; 30 | 31 | - (void)finish; 32 | - (void)finishWithError:(NSError *)error; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseOperation.h" 10 | #import "NSLock+Helpers.h" 11 | 12 | @interface BaseOperation () { 13 | OperationState _state; 14 | } 15 | 16 | @property (readwrite, assign, atomic) OperationState state; 17 | @property (getter=errors, strong, nonatomic) NSArray *errors; 18 | @property (getter=hasErrors, assign, nonatomic) BOOL hasErrors; 19 | @property (readwrite, weak, nonatomic) NSOperationQueue *parentQueue; 20 | 21 | @property (strong, nonatomic) NSLock *stateLock; 22 | @property (strong, nonatomic) NSMutableArray *internalErrors; 23 | 24 | @end 25 | 26 | @implementation BaseOperation 27 | 28 | + (NSSet *)keyPathsForValuesAffectingIsReady { 29 | return [NSSet setWithObject:@"state"]; 30 | } 31 | 32 | + (NSSet *)keyPathsForValuesAffectingIsExecuting { 33 | return [NSSet setWithObject:@"state"]; 34 | } 35 | 36 | + (NSSet *)keyPathsForValuesAffectingIsFinished { 37 | return [NSSet setWithObject:@"state"]; 38 | } 39 | 40 | - (instancetype)init 41 | { 42 | self = [super init]; 43 | if (self) { 44 | self.stateLock = [[NSLock alloc] init]; 45 | self.internalErrors = [[NSMutableArray alloc] init]; 46 | } 47 | return self; 48 | } 49 | 50 | - (BOOL)isReady { 51 | if (self.state >= OperationStateReady) { 52 | return super.ready || self.cancelled; 53 | } 54 | 55 | if ((self.state == OperationStateInitialized || self.state == OperationStatePending) && self.cancelled) { 56 | return YES; 57 | } 58 | 59 | if (self.state == OperationStatePending) { 60 | if (super.ready) { 61 | [self evaluateConditions]; 62 | } 63 | } 64 | 65 | return NO; 66 | } 67 | 68 | - (BOOL)isExecuting { 69 | return self.state == OperationStateExecuting; 70 | } 71 | 72 | - (BOOL)isFinished { 73 | return self.state == OperationStateFinished; 74 | } 75 | 76 | - (void)start { 77 | [super start]; 78 | 79 | if (self.cancelled) { 80 | [self finish]; 81 | } 82 | } 83 | 84 | - (void)main { 85 | if (self.state != OperationStateReady) { 86 | NSLog(@"Invalid state."); 87 | abort(); 88 | } 89 | 90 | if (self.cancelled || self.hasErrors) { 91 | [self finish]; 92 | } else { 93 | __block BOOL needCancel = NO; 94 | 95 | [self.dependencies enumerateObjectsUsingBlock:^(NSOperation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 96 | if ([obj isKindOfClass:[BaseOperation class]]) { 97 | if (((BaseOperation *) obj).hasErrors) { 98 | needCancel = YES; 99 | *stop = YES; 100 | } 101 | } 102 | }]; 103 | 104 | if (needCancel) { 105 | [self finish]; 106 | return; 107 | } 108 | 109 | self.state = OperationStateExecuting; 110 | 111 | [self executeMain]; 112 | } 113 | } 114 | 115 | - (void)executeMain { 116 | NSLog(@"executeMain not implemented."); 117 | [self finish]; 118 | } 119 | 120 | - (OperationState)state { 121 | return *(OperationState *) [[self.stateLock withCriticalZone:^id{ 122 | return [NSValue valueWithPointer:&self->_state]; 123 | }] pointerValue]; 124 | } 125 | 126 | - (void)setState:(OperationState)state { 127 | [self willChangeValueForKey:@"state"]; 128 | 129 | [self.stateLock withCriticalZone:^id{ 130 | if (self->_state == OperationStateFinished) { 131 | return nil; 132 | } 133 | 134 | if (![self validateTranslationFromState:self->_state toState:state]) { 135 | NSLog(@"Invalid state translation"); 136 | abort(); 137 | } 138 | 139 | self->_state = state; 140 | 141 | return nil; 142 | }]; 143 | 144 | [self didChangeValueForKey:@"state"]; 145 | } 146 | 147 | - (NSArray *)errors { 148 | return [self.internalErrors copy]; 149 | } 150 | 151 | - (BOOL)hasErrors { 152 | return self.internalErrors.count > 0; 153 | } 154 | 155 | - (BOOL)validateTranslationFromState:(OperationState)state toState:(OperationState)toState { 156 | if (state == OperationStateInitialized && toState == OperationStatePending) { 157 | return YES; 158 | } 159 | 160 | if (state == OperationStatePending && toState == OperationStateEvaluating) { 161 | return YES; 162 | } 163 | 164 | if (state == OperationStateEvaluating && toState == OperationStateReady) { 165 | return YES; 166 | } 167 | 168 | if (state == OperationStateReady && toState == OperationStateReady) { 169 | return YES; 170 | } 171 | 172 | if (state == OperationStateReady && toState == OperationStateExecuting) { 173 | return YES; 174 | } 175 | 176 | if (state == OperationStateReady && toState == OperationStateFinished) { 177 | return YES; 178 | } 179 | 180 | if (state == OperationStateExecuting && toState == OperationStateFinished) { 181 | return YES; 182 | } 183 | 184 | return NO; 185 | } 186 | 187 | - (void)evaluateConditions { 188 | self.state = OperationStateEvaluating; 189 | self.state = OperationStateReady; // TODO: not support conditions yet. 190 | } 191 | 192 | - (void)willEnqueue:(NSOperationQueue *)queue { 193 | self.state = OperationStatePending; 194 | self.parentQueue = queue; 195 | } 196 | 197 | - (void)finish { 198 | [self finishWithError:nil]; 199 | } 200 | 201 | - (void)finishWithError:(NSError *)error { 202 | if (self.state == OperationStateFinished) { 203 | return; 204 | } 205 | 206 | if (error) { 207 | [self.internalErrors addObject:error]; 208 | } 209 | 210 | self.state = OperationStateFinished; 211 | } 212 | 213 | @end 214 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseTableViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewCell.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BaseTableViewCell : UITableViewCell 12 | 13 | - (void)applyTheme; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseTableViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewCell.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewCell.h" 10 | #import "ThemeManager.h" 11 | 12 | @implementation BaseTableViewCell 13 | 14 | - (void)awakeFromNib { 15 | [super awakeFromNib]; 16 | 17 | [self applyTheme]; 18 | 19 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applyTheme) name:kThemeManagerDidChangeThemeNotification object:nil]; 20 | } 21 | 22 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 23 | [super setSelected:selected animated:animated]; 24 | 25 | // Configure the view for the selected state 26 | } 27 | 28 | - (void)dealloc { 29 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 30 | } 31 | 32 | - (void)applyTheme { 33 | if ([[ThemeManager defaultManager].currentThemeName isEqualToString:@"Dark"]) { 34 | self.backgroundColor = [UIColor colorWithRed:0.12 green:0.12 blue:0.16 alpha:1]; 35 | self.textLabel.textColor = [UIColor colorWithRed:0.62 green:0.65 blue:0.72 alpha:1]; 36 | self.detailTextLabel.textColor = [UIColor colorWithRed:0.32 green:0.33 blue:0.4 alpha:1]; 37 | 38 | UIView *selected = [[UIView alloc] init]; 39 | selected.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.15]; 40 | self.selectedBackgroundView = selected; 41 | } else { 42 | self.backgroundColor = [UIColor whiteColor]; 43 | self.textLabel.textColor = [UIColor colorWithRed:0.37 green:0.37 blue:0.37 alpha:1]; 44 | self.detailTextLabel.textColor = [UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1]; 45 | self.selectedBackgroundView = nil; 46 | } 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BaseTableViewController : UITableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTableViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewController.h" 10 | #import "ThemeManager.h" 11 | 12 | @interface BaseTableViewController () 13 | 14 | @end 15 | 16 | @implementation BaseTableViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | 21 | [self applyTheme]; 22 | 23 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applyTheme) name:kThemeManagerDidChangeThemeNotification object:nil]; 24 | } 25 | 26 | - (void)dealloc { 27 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 28 | } 29 | 30 | - (void)applyTheme { 31 | if ([[ThemeManager defaultManager].currentThemeName isEqualToString:@"Dark"]) { 32 | self.tableView.backgroundColor = [UIColor colorWithRed:0.15 green:0.15 blue:0.2 alpha:1]; 33 | self.tableView.separatorColor = [UIColor colorWithRed:0.11 green:0.11 blue:0.16 alpha:1]; 34 | self.tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; 35 | } else { 36 | self.tableView.backgroundColor = [UIColor colorWithRed:0.94 green:0.94 blue:0.96 alpha:1]; 37 | self.tableView.separatorColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.91 alpha:1]; 38 | self.tableView.indicatorStyle = UIScrollViewIndicatorStyleDefault; 39 | } 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface BaseViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/BaseViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseViewController.h" 10 | 11 | @interface BaseViewController () 12 | 13 | @end 14 | 15 | @implementation BaseViewController 16 | 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ZhihuDaily/BlockOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // BlockOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseOperation.h" 10 | 11 | @interface BlockOperation : BaseOperation 12 | 13 | @property (assign, nonatomic) dispatch_queue_t dispatchQueue; 14 | @property (copy, nonatomic) void(^block)(); 15 | 16 | + (instancetype)mainQueueOperationWithBlock:(void(^)())block; 17 | + (instancetype)globalQueueOperationWithBlock:(void(^)())block; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ZhihuDaily/BlockOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // BlockOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BlockOperation.h" 10 | 11 | @implementation BlockOperation 12 | 13 | + (instancetype)mainQueueOperationWithBlock:(void(^)())block { 14 | BlockOperation *op = [[BlockOperation alloc] init]; 15 | op.dispatchQueue = dispatch_get_main_queue(); 16 | op.block = block; 17 | 18 | return op; 19 | } 20 | 21 | + (instancetype)globalQueueOperationWithBlock:(void(^)())block { 22 | BlockOperation *op = [[BlockOperation alloc] init]; 23 | op.dispatchQueue = dispatch_get_global_queue(0, 0); 24 | op.block = block; 25 | 26 | return op; 27 | } 28 | 29 | - (void)executeMain { 30 | dispatch_async(self.dispatchQueue, ^{ 31 | self.block(); 32 | [self finish]; 33 | }); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ZhihuDaily/BottomToastView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ZhihuDaily/CacheManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // CacheManager.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CacheManager : NSObject 12 | 13 | + (instancetype)defaultManager; 14 | 15 | - (void)putObject:(NSObject *)object forKey:(NSString *)key; 16 | - (void)putObject:(NSObject *)object forKey:(NSString *)key toBothDiskAndMemory:(BOOL)flag; 17 | 18 | - (id)objectForKey:(NSString *)key; 19 | 20 | - (void)clearMemoryCaches; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ZhihuDaily/CacheManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // CacheManager.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "CacheManager.h" 10 | #import "AppDelegate.h" 11 | #import "NSLock+Helpers.h" 12 | #import "NSString+MD5.h" 13 | 14 | @interface CacheManager () 15 | 16 | @property (strong, nonatomic) NSLock *cacheLock; 17 | @property (strong, nonatomic) NSMutableDictionary *memoryCache; 18 | 19 | @end 20 | 21 | @implementation CacheManager 22 | 23 | + (instancetype)defaultManager { 24 | static CacheManager *DefaultManager = nil; 25 | static dispatch_once_t onceToken; 26 | 27 | dispatch_once(&onceToken, ^{ 28 | DefaultManager = [[CacheManager alloc] init]; 29 | }); 30 | 31 | return DefaultManager; 32 | } 33 | 34 | - (instancetype)init 35 | { 36 | self = [super init]; 37 | if (self) { 38 | self.cacheLock = [[NSLock alloc] init]; 39 | } 40 | return self; 41 | } 42 | 43 | - (NSString *)filePathForKey:(NSString *)key { 44 | NSString *basePath = [[[AppDelegate sharedDelegate] applicationCachesDirectory].absoluteString substringFromIndex:5]; 45 | NSString *finalPath = [[basePath stringByAppendingPathComponent:[key stringDigestedViaMD5]] stringByAppendingString:@".bcache"]; 46 | 47 | return finalPath; 48 | } 49 | 50 | - (void)putObject:(NSObject *)object forKey:(NSString *)key { 51 | [self putObject:object forKey:key toBothDiskAndMemory:NO]; 52 | } 53 | 54 | - (void)putObject:(NSObject *)object forKey:(NSString *)key toBothDiskAndMemory:(BOOL)flag { 55 | [self.cacheLock withCriticalZone:^id{ 56 | (self.memoryCache)[key] = object; 57 | return nil; 58 | }]; 59 | 60 | if (flag) { 61 | NSMutableData *data = [[NSMutableData alloc] init]; 62 | NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; 63 | [archiver encodeObject:object forKey:@"CachedData"]; 64 | [archiver encodeObject:[NSDate dateWithTimeIntervalSinceNow:0] forKey:@"LastModifiedDate"]; 65 | [archiver finishEncoding]; 66 | 67 | [data writeToFile:[self filePathForKey:key] options:kNilOptions error:nil]; 68 | } 69 | } 70 | 71 | - (id)objectForKey:(NSString *)key { 72 | NSObject *obj = [self.cacheLock withCriticalZone:^id{ 73 | return (self.memoryCache)[key]; 74 | }]; 75 | 76 | if (!obj) { 77 | NSString *filePath = [self filePathForKey:key]; 78 | if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { 79 | NSData *data = [[NSData alloc] initWithContentsOfFile:filePath]; 80 | NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; 81 | obj = [unarchiver decodeObjectForKey:@"CachedData"]; 82 | [unarchiver finishDecoding]; 83 | } 84 | } 85 | 86 | return obj; 87 | } 88 | 89 | - (void)clearMemoryCaches { 90 | [self.cacheLock withCriticalZone:^id{ 91 | [self.memoryCache removeAllObjects]; 92 | return nil; 93 | }]; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /ZhihuDaily/CacheManagmentViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CacheManagmentViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseTableViewController.h" 10 | 11 | @interface CacheManagmentViewController : BaseTableViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/CacheManagmentViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CacheManagmentViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "CacheManagmentViewController.h" 10 | #import "AppDelegate.h" 11 | #import "HUDManager.h" 12 | 13 | @interface CacheManagmentViewController () 14 | 15 | @property (weak, nonatomic) IBOutlet UILabel *cacheSizeLabel; 16 | @property (weak, nonatomic) IBOutlet UITableViewCell *clearCacheCell; 17 | 18 | @end 19 | 20 | @implementation CacheManagmentViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | } 25 | 26 | - (void)viewDidAppear:(BOOL)animated { 27 | [super viewDidAppear:animated]; 28 | 29 | [self calculateCacheSize]; 30 | } 31 | 32 | - (void)calculateCacheSize { 33 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 34 | NSFileManager *fileManager = [NSFileManager defaultManager]; 35 | 36 | NSString *cachePath = [[[AppDelegate sharedDelegate] applicationCachesDirectory].absoluteString substringFromIndex:5]; 37 | 38 | __block NSUInteger totalSize = 0; 39 | 40 | [[fileManager subpathsOfDirectoryAtPath:cachePath error:nil] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 41 | totalSize += [[fileManager attributesOfItemAtPath:[cachePath stringByAppendingPathComponent:obj] error:nil] fileSize]; 42 | }]; 43 | 44 | NSByteCountFormatter *formatter = [[NSByteCountFormatter alloc] init]; 45 | NSString *result = [formatter stringFromByteCount:totalSize]; 46 | 47 | dispatch_async(dispatch_get_main_queue(), ^{ 48 | self.cacheSizeLabel.text = result; 49 | }); 50 | }); 51 | } 52 | 53 | - (void)clearCache { 54 | dispatch_async(dispatch_get_main_queue(), ^{ 55 | [[HUDManager defaultManager] showProgressWithString:@"正在清理"]; 56 | }); 57 | 58 | NSString *cachePath = [[[AppDelegate sharedDelegate] applicationCachesDirectory].absoluteString substringFromIndex:5]; 59 | 60 | [[NSURLCache sharedURLCache] removeAllCachedResponses]; 61 | [[NSFileManager defaultManager] removeItemAtPath:cachePath error:nil]; 62 | 63 | // 等待一秒,让用户感觉清理了许多垃圾...... 64 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 65 | [[HUDManager defaultManager] hideProgress]; 66 | [[HUDManager defaultManager] showToastWithString:@"缓存已经全部清空。"]; 67 | [self.navigationController popViewControllerAnimated:YES]; 68 | }); 69 | } 70 | 71 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 72 | if ([tableView cellForRowAtIndexPath:indexPath] == self.clearCacheCell) { 73 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 74 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 75 | [self clearCache]; 76 | }); 77 | } 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /ZhihuDaily/CentralProgressView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /ZhihuDaily/HUDManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // HUDManager.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HUDManager : NSObject 12 | 13 | + (instancetype)defaultManager; 14 | 15 | - (void)showToastWithString:(NSString *)string; 16 | - (void)hideToast; 17 | 18 | - (void)showProgressWithString:(NSString *)string; 19 | - (void)hideProgress; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ZhihuDaily/HUDManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // HUDManager.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "HUDManager.h" 10 | 11 | @interface HUDManager () 12 | 13 | @property (strong, nonatomic) UIView *centralProgressView; 14 | @property (strong, nonatomic) UIView *bottomToastView; 15 | 16 | @property (strong, nonatomic) NSTimer *timer; 17 | 18 | @end 19 | 20 | @implementation HUDManager 21 | 22 | + (instancetype)defaultManager { 23 | static HUDManager *DefaultManager = nil; 24 | static dispatch_once_t onceToken; 25 | 26 | dispatch_once(&onceToken, ^{ 27 | DefaultManager = [[HUDManager alloc] init]; 28 | }); 29 | 30 | return DefaultManager; 31 | } 32 | 33 | - (instancetype)init 34 | { 35 | self = [super init]; 36 | if (self) { 37 | self.centralProgressView = [[UINib nibWithNibName:@"CentralProgressView" bundle:nil] instantiateWithOwner:self options:nil].firstObject; 38 | self.bottomToastView = [[UINib nibWithNibName:@"BottomToastView" bundle:nil] instantiateWithOwner:self options:nil].firstObject; 39 | self.timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(hideToast) userInfo:nil repeats:YES]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)showToastWithString:(NSString *)string { 45 | [self.timer invalidate]; 46 | 47 | UIWindow *window = [UIApplication sharedApplication].keyWindow; 48 | 49 | self.bottomToastView.frame = CGRectMake(0, window.frame.size.height - 50, window.frame.size.width, 50); 50 | self.bottomToastView.transform = CGAffineTransformMakeTranslation(0, 50); 51 | ((UILabel *) [self.bottomToastView viewWithTag:1]).text = string; 52 | 53 | [window addSubview:self.bottomToastView]; 54 | 55 | [UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 56 | self.bottomToastView.transform = CGAffineTransformIdentity; 57 | } completion:nil]; 58 | 59 | self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(hideToast) userInfo:nil repeats:NO]; 60 | } 61 | 62 | - (void)hideToast { 63 | [UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ 64 | self.bottomToastView.transform = CGAffineTransformMakeTranslation(0, 50); 65 | } completion:^(BOOL finished) { 66 | [self.bottomToastView removeFromSuperview]; 67 | self.bottomToastView.transform = CGAffineTransformIdentity; 68 | }]; 69 | [self.timer invalidate]; 70 | } 71 | 72 | - (void)showProgressWithString:(NSString *)string { 73 | UIWindow *window = [UIApplication sharedApplication].keyWindow; 74 | 75 | self.centralProgressView.frame = CGRectMake((CGRectGetWidth(window.frame) - 160) / 2.0, (CGRectGetHeight(window.frame) - 100) / 2.0, 160, 100); 76 | self.centralProgressView.alpha = 0; 77 | ((UILabel *) [self.centralProgressView viewWithTag:1]).text = string; 78 | 79 | [window addSubview:self.centralProgressView]; 80 | [[UIApplication sharedApplication] beginIgnoringInteractionEvents]; 81 | 82 | [UIView animateWithDuration:0.2 animations:^{ 83 | self.centralProgressView.alpha = 1; 84 | }]; 85 | } 86 | 87 | - (void)hideProgress { 88 | [UIView animateWithDuration:0.4 animations:^{ 89 | self.centralProgressView.alpha = 0; 90 | } completion:^(BOOL finished) { 91 | [self.centralProgressView removeFromSuperview]; 92 | [[UIApplication sharedApplication] endIgnoringInteractionEvents]; 93 | }]; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /ZhihuDaily/ImageDownloadingOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageDownloadingOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/23. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseOperation.h" 11 | 12 | @interface ImageDownloadingOperation : BaseOperation 13 | 14 | @property (copy, nonatomic) NSURL *URL; 15 | @property (weak, nonatomic) UIImageView *targetImageView; 16 | @property (assign, nonatomic) BOOL cacheDisable; 17 | 18 | + (instancetype)operationWithURL:(NSURL *)URL forImageView:(UIImageView *)imageView; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ZhihuDaily/ImageDownloadingOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // ImageDownloadingOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/23. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "ImageDownloadingOperation.h" 10 | #import "CacheManager.h" 11 | 12 | @interface ImageDownloadingOperation () 13 | 14 | @property (strong, nonatomic) NSURLSessionDataTask *task; 15 | 16 | @end 17 | 18 | @implementation ImageDownloadingOperation 19 | 20 | + (instancetype)operationWithURL:(NSURL *)URL forImageView:(UIImageView *)imageView { 21 | ImageDownloadingOperation *op = [[ImageDownloadingOperation alloc] init]; 22 | op.URL = URL; 23 | op.targetImageView = imageView; 24 | 25 | return op; 26 | } 27 | 28 | - (void)executeMain { 29 | UIImage *cachedImage = [[CacheManager defaultManager] objectForKey:self.URL.absoluteString]; 30 | if (cachedImage) { 31 | dispatch_async(dispatch_get_main_queue(), ^{ 32 | self.targetImageView.image = cachedImage; 33 | }); 34 | [self finish]; 35 | return; 36 | } 37 | 38 | __weak typeof(ImageDownloadingOperation) *weakSelf = self; 39 | 40 | NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; 41 | config.timeoutIntervalForRequest = 3.0; 42 | NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; 43 | 44 | self.task = [session dataTaskWithURL:self.URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 45 | if (error) { 46 | [weakSelf finishWithError:error]; 47 | } else { 48 | UIImage *image = [UIImage imageWithData:data]; 49 | if (image) { 50 | if (!weakSelf.cacheDisable) { 51 | [[CacheManager defaultManager] putObject:image forKey:weakSelf.URL.absoluteString toBothDiskAndMemory:YES]; 52 | } 53 | dispatch_async(dispatch_get_main_queue(), ^{ 54 | weakSelf.targetImageView.image = image; 55 | }); 56 | [weakSelf finish]; 57 | } else { 58 | [weakSelf finishWithError:[NSError errorWithDomain:@"ErrorDomainUnknown" code:1 userInfo:nil]]; 59 | } 60 | 61 | } 62 | }]; 63 | 64 | [self.task resume]; 65 | } 66 | 67 | - (void)cancel { 68 | [self.task cancel]; 69 | 70 | [super cancel]; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /ZhihuDaily/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | zh_CN 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | LaunchScreen 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarHidden 34 | 35 | NSAppTransportSecurity 36 | 37 | NSAllowsArbitraryLoads 38 | 39 | 40 | LSApplicationCategoryType 41 | public.app-category.news 42 | CFBundleDisplayName 43 | 知乎日报 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ZhihuDaily/NSLock+Helpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSLock+Helpers.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSLock (Helpers) 12 | 13 | - (id)withCriticalZone:(id(^)())block; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ZhihuDaily/NSLock+Helpers.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSLock+Helpers.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "NSLock+Helpers.h" 10 | 11 | @implementation NSLock (Helpers) 12 | 13 | - (id)withCriticalZone:(id(^)())block { 14 | [self lock]; 15 | id obj = block(); 16 | [self unlock]; 17 | 18 | return obj; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ZhihuDaily/NSString+MD5.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+MD5.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSString (MD5) 12 | 13 | @property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *stringDigestedViaMD5; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ZhihuDaily/NSString+MD5.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+MD5.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "NSString+MD5.h" 10 | #import 11 | 12 | @implementation NSString (MD5) 13 | 14 | - (NSString *)stringDigestedViaMD5 { 15 | const char * cStr = self.UTF8String; 16 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 17 | 18 | CC_MD5(cStr, (CC_LONG) strlen(cStr), result); 19 | 20 | NSMutableString *resultString = [[NSMutableString alloc] init]; 21 | 22 | for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { 23 | [resultString appendFormat:@"%02x", result[i]]; 24 | } 25 | 26 | return [resultString copy]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /ZhihuDaily/NavigationController.h: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NavigationController : UINavigationController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/NavigationController.m: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "NavigationController.h" 10 | #import "ThemeManager.h" 11 | #import "UIImage+Helpers.h" 12 | 13 | @interface NavigationController () 14 | 15 | @end 16 | 17 | @implementation NavigationController 18 | 19 | - (void)viewDidLoad { 20 | [super viewDidLoad]; 21 | 22 | [self applyTheme]; 23 | 24 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applyTheme) name:kThemeManagerDidChangeThemeNotification object:nil]; 25 | } 26 | 27 | - (UIStatusBarStyle)preferredStatusBarStyle { 28 | return [[ThemeManager defaultManager].currentThemeName isEqualToString:@"Dark"] ? UIStatusBarStyleLightContent : UIStatusBarStyleDefault; 29 | } 30 | 31 | - (void)dealloc { 32 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 33 | } 34 | 35 | - (void)applyTheme { 36 | if ([[ThemeManager defaultManager].currentThemeName isEqualToString:@"Dark"]) { 37 | [self.navigationBar setBackgroundImage:[UIImage imageFilledWithColor:[UIColor colorWithRed:0.12 green:0.12 blue:0.16 alpha:1]] forBarMetrics:UIBarMetricsDefault]; 38 | self.navigationBar.shadowImage = [UIImage imageFilledWithColor:[UIColor colorWithRed:0.11 green:0.11 blue:0.16 alpha:1]]; 39 | self.navigationBar.tintColor = [UIColor colorWithRed:0.38 green:0.38 blue:0.44 alpha:1]; 40 | self.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor colorWithRed:0.74 green:0.77 blue:0.82 alpha:1]}; 41 | } else { 42 | [self.navigationBar setBackgroundImage:[UIImage imageFilledWithColor:[UIColor whiteColor]] forBarMetrics:UIBarMetricsDefault]; 43 | self.navigationBar.shadowImage = [UIImage imageFilledWithColor:[UIColor colorWithRed:0.9 green:0.9 blue:0.91 alpha:1]]; 44 | self.navigationBar.tintColor = [UIColor colorWithRed:0.73 green:0.73 blue:0.84 alpha:1]; 45 | self.navigationBar.titleTextAttributes = @{NSForegroundColorAttributeName: [UIColor colorWithRed:0.23 green:0.25 blue:0.27 alpha:1]}; 46 | } 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /ZhihuDaily/OperationQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // OperationQueue.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface OperationQueue : NSOperationQueue 12 | 13 | + (instancetype)globalQueue; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ZhihuDaily/OperationQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // OperationQueue.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "OperationQueue.h" 10 | #import "BaseOperation.h" 11 | 12 | @implementation OperationQueue 13 | 14 | + (instancetype)globalQueue { 15 | static OperationQueue *GlobalQueue = nil; 16 | static dispatch_once_t onceToken; 17 | 18 | dispatch_once(&onceToken, ^{ 19 | GlobalQueue = [[OperationQueue alloc] init]; 20 | }); 21 | 22 | return GlobalQueue; 23 | } 24 | 25 | - (void)addOperation:(NSOperation *)op { 26 | if ([op isKindOfClass:[BaseOperation class]]) { 27 | [((BaseOperation *) op) willEnqueue:self]; 28 | } 29 | 30 | [super addOperation:op]; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /ZhihuDaily/ParsingOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // ParsingOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseOperation.h" 10 | 11 | @interface ParsingOperation : BaseOperation 12 | 13 | @property (copy, nonatomic) NSData *data; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ZhihuDaily/ParsingOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // ParsingOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "ParsingOperation.h" 10 | #import "URLDownloadingOperation.h" 11 | 12 | @implementation ParsingOperation 13 | 14 | - (NSData *)data { 15 | __block NSData *inputData = self->_data; 16 | 17 | if (!inputData) { 18 | [self.dependencies enumerateObjectsUsingBlock:^(NSOperation * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 19 | if ([obj isKindOfClass:[URLDownloadingOperation class]]) { 20 | inputData = ((URLDownloadingOperation *) obj).data; 21 | 22 | if (inputData) { 23 | *stop = YES; 24 | } 25 | } 26 | }]; 27 | } 28 | 29 | if (!inputData) { 30 | [self finishWithError:[NSError errorWithDomain:@"ErrorDomainInvalidParameter" code:1 userInfo:nil]]; 31 | return nil; 32 | } 33 | 34 | return inputData; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /ZhihuDaily/ReaderViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Story.h" 11 | 12 | @interface ReaderViewController : UIViewController 13 | 14 | @property (weak, nonatomic) Story *story; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ZhihuDaily/ReaderViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ReaderViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "ReaderViewController.h" 10 | #import "OperationQueue.h" 11 | #import "URLDownloadingOperation.h" 12 | #import "StoryBodyParsingOperation.h" 13 | #import "BlockOperation.h" 14 | #import "CacheManager.h" 15 | #import "HUDManager.h" 16 | 17 | @interface ReaderViewController () 18 | 19 | @property (strong, nonatomic) UIWebView *webView; 20 | @property (strong, nonatomic) UIImageView *imageView; 21 | 22 | @end 23 | 24 | @implementation ReaderViewController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | 29 | self.navigationItem.title = @"文章正文"; 30 | 31 | self.webView = [[UIWebView alloc] initWithFrame:self.view.bounds]; 32 | self.webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 50, 0); 33 | self.webView.scrollView.scrollIndicatorInsets = self.webView.scrollView.contentInset; 34 | self.webView.delegate = self; 35 | [self.view addSubview:self.webView]; 36 | 37 | UIImageView *grayLogoImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"logo-gray-small"]]; 38 | grayLogoImageView.frame = CGRectMake((self.view.frame.size.width - 65) / 2.0, 20, 65, 65); 39 | [self.webView insertSubview:grayLogoImageView atIndex:0]; 40 | 41 | self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 200)]; 42 | self.imageView.clipsToBounds = YES; 43 | self.imageView.contentMode = UIViewContentModeScaleAspectFill; 44 | [self.webView.scrollView addSubview:self.imageView]; 45 | 46 | dispatch_async(dispatch_get_main_queue(), ^{ 47 | [self loadStoryBody]; 48 | [[HUDManager defaultManager] showProgressWithString:@"正在载入"]; 49 | }); 50 | } 51 | 52 | - (void)loadStoryBody { 53 | URLDownloadingOperation *downloadOp = [URLDownloadingOperation operationWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://news-at.zhihu.com/api/4/news/%lu", (unsigned long) self.story.storyId]]]; 54 | StoryBodyParsingOperation *parsingOp = [[StoryBodyParsingOperation alloc] init]; 55 | BlockOperation *updatingOp = [BlockOperation mainQueueOperationWithBlock:^{ 56 | if (parsingOp.story.body) { 57 | NSString *compoundHTML = [NSString stringWithFormat:@"%@", self.story.body.cssString, self.story.body.htmlString]; 58 | [self.webView loadHTMLString:compoundHTML baseURL:nil]; 59 | self.imageView.image = [[CacheManager defaultManager] objectForKey:self.story.imageURL.absoluteString]; 60 | } else { 61 | [[HUDManager defaultManager] showToastWithString:@"无法加载文章,请检查网络设置。"]; 62 | } 63 | [[HUDManager defaultManager] hideProgress]; 64 | }]; 65 | 66 | parsingOp.story = self.story; 67 | 68 | [parsingOp addDependency:downloadOp]; 69 | [updatingOp addDependency:parsingOp]; 70 | 71 | [[OperationQueue globalQueue] addOperation:downloadOp]; 72 | [[OperationQueue globalQueue] addOperation:parsingOp]; 73 | [[OperationQueue globalQueue] addOperation:updatingOp]; 74 | } 75 | 76 | - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 77 | if (navigationType == UIWebViewNavigationTypeLinkClicked) { 78 | [[UIApplication sharedApplication] openURL:request.URL]; 79 | return NO; 80 | } 81 | 82 | return YES; 83 | } 84 | 85 | @end 86 | -------------------------------------------------------------------------------- /ZhihuDaily/Settings.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 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 | 141 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 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 | -------------------------------------------------------------------------------- /ZhihuDaily/SettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseTableViewController.h" 11 | 12 | @interface SettingsViewController : BaseTableViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ZhihuDaily/SettingsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "SettingsViewController.h" 10 | #import "ThemeManager.h" 11 | 12 | @interface SettingsViewController () 13 | 14 | @property (weak, nonatomic) IBOutlet UISwitch *nightModeSwitch; 15 | @property (strong, nonatomic) IBOutlet UISwitch *noImageModeSwitch; 16 | 17 | @end 18 | 19 | @implementation SettingsViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | self.nightModeSwitch.on = [[ThemeManager defaultManager].currentThemeName isEqualToString:@"Dark"]; 25 | self.noImageModeSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"NoImageMode"]; 26 | } 27 | 28 | - (IBAction)nightModeSwitchDidChange:(id)sender { 29 | // 延时应用主题以避免开关动画卡顿。 30 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 31 | [[ThemeManager defaultManager] setThemeName:self.nightModeSwitch.on ? @"Dark" : @"Default"]; 32 | }); 33 | } 34 | 35 | - (IBAction)noImageModeSwitchDidChange:(id)sender { 36 | [[NSUserDefaults standardUserDefaults] setBool:self.noImageModeSwitch.on forKey:@"NoImageMode"]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ZhihuDaily/StartupImageParsingOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // StartupImageParsingOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ParsingOperation.h" 11 | 12 | @interface StartupImageParsingOperation : ParsingOperation 13 | 14 | @property (readonly, strong, nonatomic) UIImage *image; 15 | @property (readonly, copy, nonatomic) NSString *text; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ZhihuDaily/StartupImageParsingOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // StartupImageParsingOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "StartupImageParsingOperation.h" 10 | #import "URLDownloadingOperation.h" 11 | 12 | @interface StartupImageParsingOperation () 13 | 14 | @property (readwrite, strong, nonatomic) UIImage *image; 15 | @property (readwrite, copy, nonatomic) NSString *text; 16 | 17 | @end 18 | 19 | @implementation StartupImageParsingOperation 20 | 21 | - (void)executeMain { 22 | NSError *error; 23 | 24 | NSObject *obj = [NSJSONSerialization JSONObjectWithData:self.data options:kNilOptions error:&error]; 25 | 26 | if (error) { 27 | [self finishWithError:error]; 28 | return; 29 | } 30 | 31 | if (![obj isKindOfClass:[NSDictionary class]]) { 32 | [self finishWithError:[NSError errorWithDomain:@"ErrorDomainUnknown" code:1 userInfo:nil]]; 33 | return; 34 | } 35 | 36 | NSString *imgURLString = ((NSDictionary *) obj)[@"img"]; 37 | NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imgURLString]]; 38 | 39 | if (imgData) { 40 | self.image = [UIImage imageWithData:imgData]; 41 | } 42 | 43 | self.text = ((NSDictionary *) obj)[@"text"]; 44 | 45 | [self finish]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /ZhihuDaily/StartupViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // StartupViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface StartupViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/StartupViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // StartupViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "StartupViewController.h" 11 | #import "OperationQueue.h" 12 | #import "BlockOperation.h" 13 | #import "URLDownloadingOperation.h" 14 | #import "StartupImageParsingOperation.h" 15 | #import "TimelineViewController.h" 16 | #import "NavigationController.h" 17 | #import "CacheManager.h" 18 | 19 | NSString * const kStartupImageCacheKey = @"com.cyandev.ZhihuDaily/startup_image"; 20 | 21 | @interface StartupViewController () 22 | 23 | @property (assign, nonatomic) BOOL mainViewControllerShown; 24 | 25 | @property (weak, nonatomic) IBOutlet UIView *scrimView; 26 | @property (weak, nonatomic) IBOutlet UIImageView *imageView; 27 | @property (weak, nonatomic) IBOutlet UILabel *textLabel; 28 | @property (assign, nonatomic) NSTimeInterval startTime; 29 | 30 | @end 31 | 32 | @implementation StartupViewController 33 | 34 | - (void)viewDidLoad { 35 | [super viewDidLoad]; 36 | 37 | [self makeScrimView]; 38 | 39 | self.imageView.image = [[CacheManager defaultManager] objectForKey:kStartupImageCacheKey]; 40 | } 41 | 42 | - (void)viewDidAppear:(BOOL)animated { 43 | [super viewDidAppear:animated]; 44 | 45 | self.startTime = [NSDate dateWithTimeIntervalSinceNow:0].timeIntervalSince1970; 46 | 47 | [self beginLoadStartupImage]; 48 | 49 | [UIView animateWithDuration:4 animations:^{ 50 | self.imageView.transform = CGAffineTransformMakeScale(1.1, 1.1); 51 | }]; 52 | } 53 | 54 | - (BOOL)prefersStatusBarHidden { 55 | return true; 56 | } 57 | 58 | - (void)makeScrimView { 59 | CAGradientLayer *gradient = [CAGradientLayer layer]; 60 | gradient.frame = self.scrimView.bounds; 61 | gradient.colors = @[(id) [UIColor colorWithWhite:0 alpha:0.5].CGColor, (id) [UIColor clearColor].CGColor]; 62 | gradient.startPoint = CGPointMake(0, 1.5); 63 | gradient.endPoint = CGPointMake(0, 0); 64 | 65 | [self.scrimView.layer addSublayer:gradient]; 66 | } 67 | 68 | - (void)beginLoadStartupImage { 69 | void(^delayedBlock)() = ^{ 70 | if (!self.mainViewControllerShown) { 71 | [self showMainViewController]; 72 | } 73 | }; 74 | 75 | URLDownloadingOperation *downloadOp = [URLDownloadingOperation operationWithURL:[NSURL URLWithString:@"http://news-at.zhihu.com/api/4/start-image/720*1184"]]; 76 | StartupImageParsingOperation *parsingOp = [[StartupImageParsingOperation alloc] init]; 77 | BlockOperation *updatingOp = [BlockOperation mainQueueOperationWithBlock:^{ 78 | UIImage *image = parsingOp.image; 79 | 80 | if (!image) { 81 | self.textLabel.text = @"首屏图片拉取失败"; 82 | return; 83 | } 84 | 85 | [[CacheManager defaultManager] putObject:image forKey:kStartupImageCacheKey toBothDiskAndMemory:YES]; 86 | 87 | [UIView transitionWithView:self.imageView duration:0.3 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ 88 | self.imageView.image = image; 89 | } completion:nil]; 90 | self.textLabel.text = parsingOp.text; 91 | }]; 92 | 93 | [parsingOp addDependency:downloadOp]; 94 | [updatingOp addDependency:parsingOp]; 95 | 96 | [[OperationQueue globalQueue] addOperation:downloadOp]; 97 | [[OperationQueue globalQueue] addOperation:parsingOp]; 98 | [[OperationQueue globalQueue] addOperation:updatingOp]; 99 | 100 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), delayedBlock); 101 | } 102 | 103 | - (void)showMainViewController { 104 | self.mainViewControllerShown = YES; 105 | 106 | TimelineViewController *vc = [[TimelineViewController alloc] init]; 107 | vc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; 108 | [AppDelegate sharedDelegate].mainViewController = vc; 109 | 110 | NavigationController *nc = [[NavigationController alloc] initWithRootViewController:vc]; 111 | [self presentViewController:nc animated:YES completion:nil]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /ZhihuDaily/StartupViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 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 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /ZhihuDaily/Story.h: -------------------------------------------------------------------------------- 1 | // 2 | // Story.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "StoryBody.h" 11 | 12 | @interface Story : NSObject 13 | 14 | @property (copy, nonatomic) NSString *title; 15 | @property (copy, nonatomic) NSURL *imageURL; 16 | @property (assign, nonatomic) NSUInteger storyId; 17 | @property (strong, nonatomic) StoryBody *body; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ZhihuDaily/Story.m: -------------------------------------------------------------------------------- 1 | // 2 | // Story.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "Story.h" 10 | 11 | @implementation Story 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/StoryBody.h: -------------------------------------------------------------------------------- 1 | // 2 | // StoryBody.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface StoryBody : NSObject 12 | 13 | @property (copy, nonatomic) NSString *htmlString; 14 | @property (strong, nonatomic) NSString *cssString; 15 | @property (strong, nonatomic) NSString *imageSource; 16 | @property (copy, nonatomic) NSURL *shareURL; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ZhihuDaily/StoryBody.m: -------------------------------------------------------------------------------- 1 | // 2 | // StoryBody.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "StoryBody.h" 10 | 11 | @implementation StoryBody 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/StoryBodyParsingOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // StoryBodyParsingOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "ParsingOperation.h" 10 | #import "Story.h" 11 | 12 | @interface StoryBodyParsingOperation : ParsingOperation 13 | 14 | @property (weak, nonatomic) Story *story; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ZhihuDaily/StoryBodyParsingOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // StoryBodyParsingOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "StoryBodyParsingOperation.h" 10 | #import "StoryBody.h" 11 | 12 | @implementation StoryBodyParsingOperation 13 | 14 | - (void)executeMain { 15 | NSError *error; 16 | 17 | NSObject *obj = [NSJSONSerialization JSONObjectWithData:self.data options:kNilOptions error:&error]; 18 | 19 | if (error) { 20 | [self finishWithError:error]; 21 | return; 22 | } 23 | 24 | if (![obj isKindOfClass:[NSDictionary class]]) { 25 | [self finishWithError:[NSError errorWithDomain:@"ErrorDomainUnknown" code:1 userInfo:nil]]; 26 | return; 27 | } 28 | 29 | self.story.body = [[StoryBody alloc] init]; 30 | self.story.body.htmlString = ((NSDictionary *) obj)[@"body"]; 31 | self.story.body.cssString = [((NSDictionary *) obj)[@"css"] firstObject]; 32 | self.story.body.imageSource = ((NSDictionary *) obj)[@"image_source"]; 33 | self.story.body.shareURL = [NSURL URLWithString:((NSDictionary *) obj)[@"share_url"]]; 34 | 35 | [self finish]; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ZhihuDaily/ThemeManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeManager.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | static NSString * const kThemeManagerDidChangeThemeNotification = @"kThemeManagerDidChangeThemeNotification"; 12 | 13 | @interface ThemeManager : NSObject 14 | 15 | @property (readonly, copy, nonatomic) NSString *currentThemeName; 16 | 17 | + (instancetype)defaultManager; 18 | 19 | - (void)setThemeName:(NSString *)themeName; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ZhihuDaily/ThemeManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // ThemeManager.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "ThemeManager.h" 11 | 12 | @interface ThemeManager () 13 | 14 | @property (readwrite, copy, nonatomic) NSString *currentThemeName; 15 | 16 | @end 17 | 18 | @implementation ThemeManager 19 | 20 | + (instancetype)defaultManager { 21 | static ThemeManager *DefaultManager = nil; 22 | static dispatch_once_t onceToken; 23 | 24 | dispatch_once(&onceToken, ^{ 25 | DefaultManager = [[ThemeManager alloc] init]; 26 | }); 27 | 28 | return DefaultManager; 29 | } 30 | 31 | - (instancetype)init 32 | { 33 | self = [super init]; 34 | if (self) { 35 | self.currentThemeName = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppTheme"]; 36 | if (!self.currentThemeName) { 37 | self.currentThemeName = @"Default"; 38 | } 39 | } 40 | return self; 41 | } 42 | 43 | - (void)setThemeName:(NSString *)themeName { 44 | self.currentThemeName = themeName; 45 | 46 | [[NSUserDefaults standardUserDefaults] setObject:themeName forKey:@"AppTheme"]; 47 | 48 | [[NSNotificationCenter defaultCenter] postNotificationName:kThemeManagerDidChangeThemeNotification object:nil]; 49 | [UIView transitionWithView:[UIApplication sharedApplication].keyWindow duration:0.5 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ 50 | 51 | } completion:nil]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /ZhihuDaily/Timeline.h: -------------------------------------------------------------------------------- 1 | // 2 | // Timeline.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Story.h" 11 | 12 | @interface Timeline : NSObject 13 | 14 | @property (copy, nonatomic) NSDate *date; 15 | @property (copy, nonatomic) NSString *dateString; 16 | @property (copy, nonatomic) NSArray *stories; 17 | @property (copy, nonatomic) NSArray *topStories; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ZhihuDaily/Timeline.m: -------------------------------------------------------------------------------- 1 | // 2 | // Timeline.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "Timeline.h" 10 | 11 | @implementation Timeline 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineCell.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseTableViewCell.h" 11 | #import "Story.h" 12 | 13 | @interface TimelineCell : BaseTableViewCell 14 | 15 | - (void)loadWithStory:(Story *)story; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineCell.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TimelineCell.h" 10 | #import "OperationQueue.h" 11 | #import "ImageDownloadingOperation.h" 12 | 13 | @interface TimelineCell () 14 | 15 | @property (strong, nonatomic) ImageDownloadingOperation *currentDownloadingOperation; 16 | 17 | @end 18 | 19 | @implementation TimelineCell 20 | 21 | - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { 22 | if (highlighted) { 23 | self.backgroundColor = [UIColor colorWithWhite:0 alpha:0.1]; 24 | } else { 25 | [self applyTheme]; 26 | [super setHighlighted:highlighted animated:animated]; 27 | } 28 | } 29 | 30 | - (UILabel *)textLabel { 31 | return [self viewWithTag:1]; 32 | } 33 | 34 | - (UIImageView *)imageView { 35 | return [self viewWithTag:2]; 36 | } 37 | 38 | - (void)loadWithStory:(Story *)story { 39 | self.textLabel.text = story.title; 40 | self.imageView.image = nil; 41 | 42 | if (self.currentDownloadingOperation) { 43 | [self.currentDownloadingOperation cancel]; 44 | } 45 | 46 | self.currentDownloadingOperation = [ImageDownloadingOperation operationWithURL:story.imageURL forImageView:self.imageView]; 47 | 48 | [[OperationQueue globalQueue] addOperation:self.currentDownloadingOperation]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 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 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Timeline.h" 11 | 12 | @class TimelineController; 13 | 14 | 15 | @protocol TimelineControllerDelegate 16 | 17 | @optional 18 | 19 | - (void)timelineControllerDidFinishLoading:(TimelineController *)timelineController; 20 | - (void)timelineControllerDidFailedLoading:(TimelineController *)timelineController; 21 | 22 | @end 23 | 24 | 25 | @interface TimelineController : NSObject 26 | 27 | @property (readonly, copy, nonatomic) NSArray *timelines; 28 | @property (weak, nonatomic) id delegate; 29 | 30 | @property (readonly, assign, nonatomic) BOOL busy; 31 | 32 | - (void)reload; 33 | - (void)reserve; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TimelineController.h" 10 | #import "OperationQueue.h" 11 | #import "URLDownloadingOperation.h" 12 | #import "TimelineParsingOperation.h" 13 | #import "BlockOperation.h" 14 | 15 | @interface TimelineController () 16 | 17 | @property (readwrite, strong, nonatomic) NSMutableArray *internalTimelines; 18 | @property (readwrite, assign, nonatomic) BOOL busy; 19 | 20 | @end 21 | 22 | @implementation TimelineController 23 | 24 | - (NSArray *)timelines { 25 | return [self.internalTimelines copy]; 26 | } 27 | 28 | - (void)reload { 29 | if (self.busy) 30 | return; 31 | 32 | if (!self.internalTimelines) { 33 | self.internalTimelines = [[NSMutableArray alloc] init]; 34 | } 35 | 36 | self.busy = YES; 37 | 38 | URLDownloadingOperation *downloadOp = [URLDownloadingOperation operationWithURL:[NSURL URLWithString:@"http://news-at.zhihu.com/api/4/news/latest"]]; 39 | TimelineParsingOperation *parsingOp = [[TimelineParsingOperation alloc] init]; 40 | BlockOperation *notifyOp = [BlockOperation mainQueueOperationWithBlock:^{ 41 | if (parsingOp.timeline) { 42 | if (![(self.internalTimelines).firstObject.date isEqualToDate:parsingOp.timeline.date]) { 43 | [self.internalTimelines removeAllObjects]; 44 | [self.internalTimelines addObject:parsingOp.timeline]; 45 | } else { 46 | [self.internalTimelines setObject:parsingOp.timeline atIndexedSubscript:0]; 47 | } 48 | [self.delegate timelineControllerDidFinishLoading:self]; 49 | } else { 50 | [self.delegate timelineControllerDidFailedLoading:self]; 51 | } 52 | 53 | self.busy = NO; 54 | }]; 55 | 56 | [parsingOp addDependency:downloadOp]; 57 | [notifyOp addDependency:parsingOp]; 58 | 59 | [[OperationQueue globalQueue] addOperation:downloadOp]; 60 | [[OperationQueue globalQueue] addOperation:parsingOp]; 61 | [[OperationQueue globalQueue] addOperation:notifyOp]; 62 | } 63 | 64 | - (void)reserve { 65 | if (self.busy) 66 | return; 67 | 68 | self.busy = YES; 69 | 70 | URLDownloadingOperation *downloadOp = [URLDownloadingOperation operationWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://news-at.zhihu.com/api/4/news/before/%@", (self.timelines).lastObject.dateString]]]; 71 | TimelineParsingOperation *parsingOp = [[TimelineParsingOperation alloc] init]; 72 | BlockOperation *notifyOp = [BlockOperation mainQueueOperationWithBlock:^{ 73 | if (parsingOp.timeline) { 74 | [self.internalTimelines addObject:parsingOp.timeline]; 75 | [self.delegate timelineControllerDidFinishLoading:self]; 76 | } else { 77 | [self.delegate timelineControllerDidFailedLoading:self]; 78 | } 79 | 80 | self.busy = NO; 81 | }]; 82 | 83 | [parsingOp addDependency:downloadOp]; 84 | [notifyOp addDependency:parsingOp]; 85 | 86 | [[OperationQueue globalQueue] addOperation:downloadOp]; 87 | [[OperationQueue globalQueue] addOperation:parsingOp]; 88 | [[OperationQueue globalQueue] addOperation:notifyOp]; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineDataSource.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Timeline.h" 11 | 12 | @interface TimelineDataSource : NSObject 13 | 14 | @property (copy, nonatomic) NSString *cellIdentifier; 15 | @property (copy, nonatomic) NSArray *timelines; 16 | 17 | - (Story *)storyAtIndexPath:(NSIndexPath *)indexPath; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineDataSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineDataSource.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TimelineDataSource.h" 10 | #import "TimelineCell.h" 11 | #import "TopStoryCell.h" 12 | 13 | @interface TimelineDataSource () 14 | 15 | @property (strong, nonatomic) NSDateFormatter *formatter; 16 | 17 | @end 18 | 19 | @implementation TimelineDataSource 20 | 21 | - (instancetype)init 22 | { 23 | self = [super init]; 24 | if (self) { 25 | self.formatter = [[NSDateFormatter alloc] init]; 26 | self.formatter.dateFormat = @"yyyy年MM月dd日"; 27 | } 28 | return self; 29 | } 30 | 31 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 32 | if (!self.timelines) { 33 | return 0; 34 | } 35 | 36 | return self.timelines.count + 1; 37 | } 38 | 39 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 40 | if (!self.timelines) { 41 | return 0; 42 | } 43 | 44 | if (section == 0) { 45 | return 1; 46 | } 47 | 48 | return (self.timelines)[section - 1].stories.count; 49 | } 50 | 51 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 52 | if (indexPath.section == 0) { 53 | static NSString *TopStoryCellIdentifier = @"TopStoryCell"; 54 | 55 | TopStoryCell *cell = [tableView dequeueReusableCellWithIdentifier:TopStoryCellIdentifier]; 56 | 57 | if (!cell) { 58 | cell = [[TopStoryCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TopStoryCellIdentifier]; 59 | } 60 | 61 | [cell.pageController setTimeline:(self.timelines).firstObject]; 62 | cell.pageControl.numberOfPages = (self.timelines).firstObject.topStories.count; 63 | cell.pageControl.currentPage = 0; 64 | 65 | return cell; 66 | } 67 | 68 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath]; 69 | 70 | [((TimelineCell *) cell) loadWithStory:[self storyAtIndexPath:indexPath]]; 71 | 72 | return cell; 73 | } 74 | 75 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 76 | if (!self.timelines || section == 0) { 77 | return nil; 78 | } 79 | 80 | return [self.formatter stringFromDate:(self.timelines)[section - 1].date]; 81 | } 82 | 83 | - (Story *)storyAtIndexPath:(NSIndexPath *)indexPath { 84 | return ((self.timelines)[indexPath.section - 1].stories)[indexPath.row]; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineParsingOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineParsingOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "ParsingOperation.h" 10 | #import "Timeline.h" 11 | 12 | @interface TimelineParsingOperation : ParsingOperation 13 | 14 | @property (readonly, strong, nonatomic) Timeline *timeline; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineParsingOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineParsingOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TimelineParsingOperation.h" 10 | 11 | @interface TimelineParsingOperation () 12 | 13 | @property (readwrite, strong, nonatomic) Timeline *timeline; 14 | 15 | @end 16 | 17 | @implementation TimelineParsingOperation 18 | 19 | - (void)executeMain { 20 | NSError *error; 21 | 22 | NSObject *obj = [NSJSONSerialization JSONObjectWithData:self.data options:kNilOptions error:&error]; 23 | 24 | if (error) { 25 | [self finishWithError:error]; 26 | return; 27 | } 28 | 29 | if (![obj isKindOfClass:[NSDictionary class]]) { 30 | [self finishWithError:[NSError errorWithDomain:@"ErrorDomainUnknown" code:1 userInfo:nil]]; 31 | return; 32 | } 33 | 34 | self.timeline = [[Timeline alloc] init]; 35 | 36 | NSString *dateString = ((NSDictionary *) obj)[@"date"]; 37 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 38 | formatter.dateFormat = @"yyyyMMdd"; 39 | 40 | self.timeline.dateString = dateString; 41 | self.timeline.date = [formatter dateFromString:dateString]; 42 | 43 | NSArray *stories = ((NSDictionary *) obj)[@"stories"]; 44 | NSArray *topStories = ((NSDictionary *) obj)[@"top_stories"]; 45 | 46 | [self inflateStories:stories forTop:NO]; 47 | [self inflateStories:topStories forTop:YES]; 48 | 49 | [self finish]; 50 | } 51 | 52 | - (void)inflateStories:(NSArray *)stories forTop:(BOOL)top { 53 | NSMutableArray *storyModels = [[NSMutableArray alloc] init]; 54 | 55 | [stories enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 56 | Story *story = [[Story alloc] init]; 57 | story.title = obj[@"title"]; 58 | story.storyId = [obj[@"id"] unsignedIntegerValue]; 59 | 60 | if (top) { 61 | story.imageURL = [NSURL URLWithString:obj[@"image"]]; 62 | } else { 63 | story.imageURL = [NSURL URLWithString:[obj[@"images"] firstObject]]; 64 | } 65 | 66 | [storyModels addObject:story]; 67 | }]; 68 | 69 | if (top) { 70 | self.timeline.topStories = storyModels; 71 | } else { 72 | self.timeline.stories = storyModels; 73 | } 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineSectionHeaderView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineSectionHeaderView.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/23. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TimelineSectionHeaderView : UIView 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *textLabel; 14 | @property (weak, nonatomic) IBOutlet UIView *topLineView; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineSectionHeaderView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineSectionHeaderView.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/23. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TimelineSectionHeaderView.h" 10 | #import "ThemeManager.h" 11 | 12 | @interface TimelineSectionHeaderView () 13 | 14 | @property (weak, nonatomic) IBOutlet UIVisualEffectView *visualEffectView; 15 | 16 | @end 17 | 18 | @implementation TimelineSectionHeaderView 19 | 20 | - (void)awakeFromNib { 21 | [super awakeFromNib]; 22 | 23 | [self applyTheme]; 24 | 25 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applyTheme) name:kThemeManagerDidChangeThemeNotification object:nil]; 26 | } 27 | 28 | - (void)dealloc { 29 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 30 | } 31 | 32 | - (void)applyTheme { 33 | if ([[ThemeManager defaultManager].currentThemeName isEqualToString:@"Dark"]) { 34 | self.visualEffectView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; 35 | //self.backgroundColor = [UIColor colorWithRed:0.12 green:0.12 blue:0.16 alpha:1]; 36 | self.textLabel.textColor = [UIColor colorWithRed:0.62 green:0.65 blue:0.72 alpha:1]; 37 | } else { 38 | self.visualEffectView.effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight]; 39 | //self.backgroundColor = [UIColor whiteColor]; 40 | self.textLabel.textColor = [UIColor lightGrayColor]; 41 | } 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineSectionHeaderView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineTableViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseTableViewController.h" 11 | 12 | @interface TimelineTableViewController : BaseTableViewController 13 | 14 | @property (strong, nonatomic) id dataSource; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineTableViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineTableViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TimelineTableViewController.h" 10 | #import "TimelineViewController.h" 11 | #import "TimelineDataSource.h" 12 | #import "TimelineSectionHeaderView.h" 13 | #import "ReaderViewController.h" 14 | 15 | @interface TimelineTableViewController () 16 | 17 | @end 18 | 19 | @implementation TimelineTableViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | 24 | self.refreshControl = [[UIRefreshControl alloc] init]; 25 | } 26 | 27 | - (void)viewWillAppear:(BOOL)animated { 28 | [super viewWillAppear:animated]; 29 | 30 | [self.tableView deselectRowAtIndexPath:(self.tableView).indexPathForSelectedRow animated:animated]; 31 | } 32 | 33 | #pragma mark - Table view data source 34 | 35 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 36 | return [self.dataSource numberOfSectionsInTableView:tableView]; 37 | } 38 | 39 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 40 | return [self.dataSource tableView:tableView numberOfRowsInSection:section]; 41 | } 42 | 43 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 44 | return [self.dataSource tableView:tableView cellForRowAtIndexPath:indexPath]; 45 | } 46 | 47 | - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 48 | return [self.dataSource tableView:tableView titleForHeaderInSection:section]; 49 | } 50 | 51 | #pragma mark - Table view delegate 52 | 53 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { 54 | if (section == 0) { 55 | return 0; 56 | } 57 | 58 | TimelineSectionHeaderView *headerView = [[UINib nibWithNibName:@"TimelineSectionHeaderView" bundle:nil] instantiateWithOwner:self options:nil].firstObject; 59 | headerView.textLabel.text = [self tableView:tableView titleForHeaderInSection:section]; 60 | headerView.topLineView.hidden = section <= 1; 61 | 62 | return headerView; 63 | } 64 | 65 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 66 | if (section == 0) { 67 | return 0; 68 | } 69 | 70 | return 40; 71 | } 72 | 73 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { 74 | if ([self.dataSource isKindOfClass:[TimelineDataSource class]]) { 75 | if (indexPath.section == 0) { 76 | return 200; 77 | } 78 | } 79 | 80 | return 90; 81 | } 82 | 83 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 84 | Story *story = [((TimelineDataSource *) self.dataSource) storyAtIndexPath:indexPath]; 85 | 86 | NSNotification *notification = [NSNotification notificationWithName:kStoryShouldShowNotification object:nil userInfo:@{kStoryUserInfoKey: story}]; 87 | [[NSNotificationCenter defaultCenter] postNotification:notification]; 88 | } 89 | 90 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 91 | CGFloat offsetY = scrollView.contentOffset.y; 92 | 93 | if (offsetY > scrollView.contentSize.height - scrollView.frame.size.height) { 94 | [[NSNotificationCenter defaultCenter] postNotificationName:kTimelineNeedsReserveNotification object:nil]; 95 | } 96 | } 97 | 98 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 99 | [[NSNotificationCenter defaultCenter] postNotificationName:kNeedsUpdateRefreshStateNotification object:nil]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "BaseViewController.h" 11 | 12 | static NSString * const kNeedsUpdateRefreshStateNotification = @"kNeedsUpdateRefreshStateNotification"; 13 | static NSString * const kTimelineNeedsReserveNotification = @"kTimelineNeedsReserveNotification"; 14 | static NSString * const kStoryShouldShowNotification = @"kStoryShouldShowNotification"; 15 | static NSString * const kStoryUserInfoKey = @"kTopStoryUserInfoKey"; 16 | 17 | @interface TimelineViewController : BaseViewController 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ZhihuDaily/TimelineViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimelineViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TimelineViewController.h" 10 | #import "TimelineTableViewController.h" 11 | #import "TimelineDataSource.h" 12 | #import "TimelineController.h" 13 | #import "ReaderViewController.h" 14 | #import "HUDManager.h" 15 | #import "UIImage+Helpers.h" 16 | 17 | @interface TimelineViewController () 18 | 19 | @property (strong, nonatomic) TimelineController *timelineController; 20 | @property (strong, nonatomic) TimelineDataSource *timelineDataSource; 21 | 22 | @property (strong, nonatomic) TimelineTableViewController *timelineTableViewController; 23 | 24 | @property (strong, nonatomic) UIView *progressBar; 25 | 26 | @end 27 | 28 | @implementation TimelineViewController 29 | 30 | - (void)viewDidLoad { 31 | [super viewDidLoad]; 32 | 33 | self.navigationItem.title = @"知乎日报"; 34 | self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"主页" style:UIBarButtonItemStylePlain target:nil action:nil]; 35 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"设置" style:UIBarButtonItemStylePlain target:self action:@selector(showSettingsViewController:)]; 36 | 37 | self.automaticallyAdjustsScrollViewInsets = NO; 38 | 39 | self.timelineController = [[TimelineController alloc] init]; 40 | self.timelineController.delegate = self; 41 | 42 | self.timelineDataSource = [[TimelineDataSource alloc] init]; 43 | self.timelineDataSource.cellIdentifier = @"Cell"; 44 | 45 | self.timelineTableViewController = [[TimelineTableViewController alloc] init]; 46 | [self.timelineTableViewController willMoveToParentViewController:self]; 47 | [self addChildViewController:self.timelineTableViewController]; 48 | self.timelineTableViewController.view.frame = self.view.bounds; 49 | [self.view addSubview:self.timelineTableViewController.view]; 50 | [self.timelineTableViewController didMoveToParentViewController:self]; 51 | 52 | self.timelineTableViewController.dataSource = self.timelineDataSource; 53 | [self.timelineTableViewController.tableView registerNib:[UINib nibWithNibName:@"TimelineCell" bundle:nil] forCellReuseIdentifier:@"Cell"]; 54 | [self.timelineTableViewController.refreshControl addTarget:self.timelineController action:@selector(reload) forControlEvents:UIControlEventValueChanged]; 55 | 56 | self.progressBar = [[UIView alloc] init]; 57 | self.progressBar.frame = CGRectMake(0, 0, CGRectGetWidth(self.view.frame), 5); 58 | self.progressBar.backgroundColor = [UIColor colorWithRed:0 green:0.51 blue:1 alpha:1]; 59 | [self.view addSubview:self.progressBar]; 60 | 61 | dispatch_async(dispatch_get_main_queue(), ^{ 62 | [self.timelineController reload]; 63 | }); 64 | 65 | [self.timelineController addObserver:self forKeyPath:@"busy" options:NSKeyValueObservingOptionNew context:nil]; 66 | 67 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateRefreshState) name:kNeedsUpdateRefreshStateNotification object:nil]; 68 | [[NSNotificationCenter defaultCenter] addObserver:self.timelineController selector:@selector(reserve) name:kTimelineNeedsReserveNotification object:nil]; 69 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showReaderViewController:) name:kStoryShouldShowNotification object:nil]; 70 | } 71 | 72 | - (void)viewWillAppear:(BOOL)animated { 73 | [super viewWillAppear:animated]; 74 | } 75 | 76 | - (void)viewDidAppear:(BOOL)animated { 77 | [super viewDidAppear:animated]; 78 | } 79 | 80 | - (void)viewWillDisappear:(BOOL)animated { 81 | [super viewWillDisappear:animated]; 82 | } 83 | 84 | - (UIStatusBarStyle)preferredStatusBarStyle { 85 | return UIStatusBarStyleLightContent; 86 | } 87 | 88 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 89 | if ([keyPath isEqualToString:@"busy"] && object == self.timelineController) { 90 | [self updateRefreshState]; 91 | } 92 | } 93 | 94 | - (void)dealloc { 95 | [[NSNotificationCenter defaultCenter] removeObserver:self.timelineController]; 96 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 97 | } 98 | 99 | - (void)showSettingsViewController:(id)sender { 100 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Settings" bundle:nil]; 101 | [self showViewController:[storyboard instantiateInitialViewController] sender:sender]; 102 | } 103 | 104 | - (void)showReaderViewController:(NSNotification *)aNotification { 105 | ReaderViewController *readerVC = [[ReaderViewController alloc] init]; 106 | readerVC.story = (aNotification.userInfo)[kStoryUserInfoKey]; 107 | 108 | [self showViewController:readerVC sender:self]; 109 | } 110 | 111 | - (void)updateRefreshState { 112 | if (!self.timelineController.busy) { 113 | if (self.timelineTableViewController.refreshControl.refreshing) { 114 | [self.timelineTableViewController.refreshControl endRefreshing]; 115 | } 116 | 117 | [UIView animateWithDuration:0.6 delay:0 usingSpringWithDamping:1.0 initialSpringVelocity:0 options:kNilOptions animations:^{ 118 | CGRect frame = self.progressBar.frame; 119 | frame.size.width = CGRectGetWidth(self.view.frame); 120 | self.progressBar.frame = frame; 121 | } completion:^(BOOL finished) { 122 | [UIView animateWithDuration:0.4 animations:^{ 123 | self.progressBar.alpha = 0; 124 | }]; 125 | }]; 126 | } else { 127 | __block CGRect frame = self.progressBar.frame; 128 | frame.size.width = 0; 129 | self.progressBar.frame = frame; 130 | self.progressBar.alpha = 1; 131 | 132 | [UIView animateWithDuration:0.3 animations:^{ 133 | frame.size.width = 30; 134 | self.progressBar.frame = frame; 135 | }]; 136 | } 137 | } 138 | 139 | #pragma mark - Timeline Controller Delegate 140 | 141 | - (void)timelineControllerDidFinishLoading:(TimelineController *)timelineController { 142 | [self.timelineTableViewController.refreshControl endRefreshing]; 143 | self.timelineDataSource.timelines = timelineController.timelines; 144 | [self.timelineTableViewController.tableView reloadData]; 145 | } 146 | 147 | - (void)timelineControllerDidFailedLoading:(TimelineController *)timelineController { 148 | [self.timelineTableViewController.refreshControl endRefreshing]; 149 | [[HUDManager defaultManager] showToastWithString:@"无法获取最新消息,请检查网络设置。"]; 150 | } 151 | 152 | @end 153 | -------------------------------------------------------------------------------- /ZhihuDaily/TopStoryCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TopStoryCell.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TopStoryPageController.h" 11 | 12 | @interface TopStoryCell : UITableViewCell 13 | 14 | @property (strong, nonatomic) TopStoryPageController *pageController; 15 | @property (strong, nonatomic) UIPageControl *pageControl; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ZhihuDaily/TopStoryCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TopStoryCell.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TopStoryCell.h" 10 | #import "TopStoryViewController.h" 11 | 12 | @interface TopStoryCell () 13 | 14 | @end 15 | 16 | @implementation TopStoryCell 17 | 18 | - (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { 19 | self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 20 | if (self) { 21 | self.pageController = [[TopStoryPageController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil]; 22 | self.pageController.delegate = self; 23 | [self addSubview:self.pageController.view]; 24 | 25 | self.pageControl = [[UIPageControl alloc] init]; 26 | self.pageControl.userInteractionEnabled = false; 27 | [self addSubview:self.pageControl]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)setSelected:(BOOL)selected animated:(BOOL)animated { 33 | [super setSelected:selected animated:animated]; 34 | 35 | // Configure the view for the selected state 36 | } 37 | 38 | - (void)layoutSubviews { 39 | [super layoutSubviews]; 40 | 41 | self.pageController.view.frame = self.bounds; 42 | [self.pageController.view setNeedsLayout]; 43 | 44 | self.pageControl.frame = CGRectMake(0, self.frame.size.height - 20, self.frame.size.width, 20); 45 | } 46 | 47 | - (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed { 48 | self.pageControl.currentPage = ((TopStoryViewController *) (pageViewController.viewControllers).firstObject).index; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /ZhihuDaily/TopStoryPageController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TopStoryPageController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Timeline.h" 11 | 12 | @interface TopStoryPageController : UIPageViewController 13 | 14 | - (void)setTimeline:(Timeline *)timeline; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ZhihuDaily/TopStoryPageController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TopStoryPageController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TopStoryPageController.h" 10 | #import "TopStoryViewController.h" 11 | 12 | @interface TopStoryPageController () 13 | 14 | @property (weak, nonatomic) Timeline *timeline; 15 | 16 | @end 17 | 18 | @implementation TopStoryPageController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | self.view.backgroundColor = [UIColor darkGrayColor]; 24 | 25 | self.dataSource = self; 26 | } 27 | 28 | - (void)setTimeline:(Timeline *)timeline { 29 | self->_timeline = timeline; 30 | 31 | if (timeline.topStories.count > 0) { 32 | [self setViewControllers:@[[self makeViewControllerForStoryAtIndex:0]] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; 33 | } else { 34 | [self setViewControllers:@[] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; 35 | } 36 | } 37 | 38 | - (UIViewController *)makeViewControllerForStoryAtIndex:(NSUInteger)idx { 39 | TopStoryViewController *vc = [[TopStoryViewController alloc] init]; 40 | vc.view.frame = self.view.bounds; 41 | vc.index = idx; 42 | [vc loadWithStory:(self.timeline.topStories)[vc.index]]; 43 | 44 | return vc; 45 | } 46 | 47 | - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController { 48 | if (((TopStoryViewController *) viewController).index + 1 < self.timeline.topStories.count) { 49 | return [self makeViewControllerForStoryAtIndex:((TopStoryViewController *) viewController).index + 1]; 50 | } else { 51 | return nil; 52 | } 53 | } 54 | 55 | - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { 56 | if (((TopStoryViewController *) viewController).index >= 1) { 57 | return [self makeViewControllerForStoryAtIndex:((TopStoryViewController *) viewController).index - 1]; 58 | } else { 59 | return nil; 60 | } 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /ZhihuDaily/TopStoryViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TopStoryViewController.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Story.h" 11 | 12 | static NSString * const kTopStoryViewDidTapNotification = @"kTopStoryViewDidTapNotification"; 13 | static NSString * const kTopStoryUserInfoKey = @"kTopStoryUserInfoKey"; 14 | 15 | @interface TopStoryViewController : UIViewController 16 | 17 | @property (assign) NSUInteger index; 18 | 19 | - (void)loadWithStory:(Story *)story; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /ZhihuDaily/TopStoryViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TopStoryViewController.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/22. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "TopStoryViewController.h" 10 | #import "TimelineViewController.h" 11 | #import "OperationQueue.h" 12 | #import "ImageDownloadingOperation.h" 13 | 14 | @interface TopStoryViewController () 15 | 16 | @property (strong, nonatomic) ImageDownloadingOperation *currentDownloadingOperation; 17 | 18 | @property (weak, nonatomic) Story *story; 19 | 20 | @property (weak, nonatomic) IBOutlet UIImageView *imageView; 21 | @property (weak, nonatomic) IBOutlet UILabel *label; 22 | 23 | @end 24 | 25 | @implementation TopStoryViewController 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | 30 | UITapGestureRecognizer *aRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)]; 31 | [self.view addGestureRecognizer:aRecognizer]; 32 | } 33 | 34 | - (void)loadWithStory:(Story *)story { 35 | self.story = story; 36 | 37 | self.label.text = story.title; 38 | self.imageView.image = nil; 39 | 40 | if (self.currentDownloadingOperation) { 41 | [self.currentDownloadingOperation cancel]; 42 | } 43 | 44 | self.currentDownloadingOperation = [ImageDownloadingOperation operationWithURL:story.imageURL forImageView:self.imageView]; 45 | 46 | [[OperationQueue globalQueue] addOperation:self.currentDownloadingOperation]; 47 | } 48 | 49 | - (void)handleTap:(id)sender { 50 | NSNotification *notification = [NSNotification notificationWithName:kStoryShouldShowNotification object:nil userInfo:@{kStoryUserInfoKey: self.story}]; 51 | [[NSNotificationCenter defaultCenter] postNotification:notification]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /ZhihuDaily/UIImage+Helpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Helpers.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (Helpers) 12 | 13 | + (instancetype)imageFilledWithColor:(UIColor *)color; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ZhihuDaily/UIImage+Helpers.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Helpers.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "UIImage+Helpers.h" 10 | 11 | @implementation UIImage (Helpers) 12 | 13 | + (instancetype)imageFilledWithColor:(UIColor *)color { 14 | UIImage *result; 15 | 16 | UIGraphicsBeginImageContext(CGSizeMake(1, 1)); 17 | [color setFill]; 18 | CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 1, 1)); 19 | result = UIGraphicsGetImageFromCurrentImageContext(); 20 | UIGraphicsEndImageContext(); 21 | 22 | return result; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /ZhihuDaily/URLDownloadingOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // URLDownloadingOperation.h 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "BaseOperation.h" 10 | 11 | @interface URLDownloadingOperation : BaseOperation 12 | 13 | @property (copy, nonatomic) NSURL *URL; 14 | @property (readonly, copy, nonatomic) NSData *data; 15 | 16 | + (instancetype)operationWithURL:(NSURL *)URL; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /ZhihuDaily/URLDownloadingOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // URLDownloadingOperation.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import "URLDownloadingOperation.h" 10 | 11 | @interface URLDownloadingOperation () 12 | 13 | @property (readwrite, copy, nonatomic) NSData *data; 14 | @property (strong, nonatomic) NSURLSessionDataTask *task; 15 | 16 | @end 17 | 18 | @implementation URLDownloadingOperation 19 | 20 | + (instancetype)operationWithURL:(NSURL *)URL { 21 | URLDownloadingOperation *op = [[URLDownloadingOperation alloc] init]; 22 | op.URL = URL; 23 | 24 | return op; 25 | } 26 | 27 | - (void)executeMain { 28 | __weak typeof(URLDownloadingOperation) *weakSelf = self; 29 | 30 | NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; 31 | config.timeoutIntervalForRequest = 3.0; 32 | NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; 33 | 34 | self.task = [session dataTaskWithURL:self.URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 35 | if (error) { 36 | [weakSelf finishWithError:error]; 37 | } else { 38 | weakSelf.data = data; 39 | [weakSelf finish]; 40 | } 41 | }]; 42 | 43 | [self.task resume]; 44 | } 45 | 46 | - (void)cancel { 47 | [self.task cancel]; 48 | 49 | [super cancel]; 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /ZhihuDaily/ZhihuDaily.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | ZhihuDaily.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /ZhihuDaily/ZhihuDaily.xcdatamodeld/ZhihuDaily.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ZhihuDaily/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ZhihuDaily 4 | // 5 | // Created by 杨弘宇 on 16/5/21. 6 | // Copyright © 2016年 Cyandev. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | --------------------------------------------------------------------------------