├── .DS_Store ├── .gitignore ├── PHPHub.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── MLS.xcuserdatad │ └── xcschemes │ ├── PHPHub.xcscheme │ └── xcschememanagement.plist ├── PHPHub.xcworkspace └── contents.xcworkspacedata ├── PHPHub ├── Apis │ ├── AuthorizeApi.swift │ ├── TopicApi.swift │ └── UserApi.swift ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_120-1.png │ │ ├── icon_120.png │ │ ├── icon_152.png │ │ ├── icon_180.png │ │ ├── icon_29.png │ │ ├── icon_40.png │ │ ├── icon_58-1.png │ │ ├── icon_58.png │ │ ├── icon_76.png │ │ ├── icon_80-1.png │ │ ├── icon_80.png │ │ └── icon_87.png │ ├── Base │ │ ├── Contents.json │ │ ├── back.imageset │ │ │ ├── Contents.json │ │ │ └── back.png │ │ ├── cancel.imageset │ │ │ ├── Contents.json │ │ │ └── cancel.png │ │ ├── icon_text.imageset │ │ │ ├── Contents.json │ │ │ └── PHPHub-icon.png │ │ ├── logo.imageset │ │ │ ├── Contents.json │ │ │ └── icon_180.png │ │ ├── more.imageset │ │ │ ├── Contents.json │ │ │ └── more_icon.png │ │ └── pure_icon.imageset │ │ │ ├── Contents.json │ │ │ └── pure_icon.png │ ├── Contents.json │ ├── Me │ │ ├── Contents.json │ │ ├── anonymous_logo.imageset │ │ │ ├── Contents.json │ │ │ └── anonymous_logo.png │ │ ├── blog_icon.imageset │ │ │ ├── Contents.json │ │ │ └── blog_icon.png │ │ ├── edit_profile_icon.imageset │ │ │ ├── Contents.json │ │ │ └── edit_profile_icon.png │ │ ├── github_icon.imageset │ │ │ ├── Contents.json │ │ │ └── github_icon.png │ │ ├── local_icon.imageset │ │ │ ├── Contents.json │ │ │ └── local_icon.png │ │ ├── ring_icon.imageset │ │ │ ├── Contents.json │ │ │ └── ring_icon.png │ │ ├── settings_icon.imageset │ │ │ ├── Contents.json │ │ │ └── settings_icon.png │ │ ├── topic_icon.imageset │ │ │ ├── Contents.json │ │ │ └── topic_icon.png │ │ └── twitter_icon.imageset │ │ │ ├── Contents.json │ │ │ └── twitter_icon.png │ ├── TabBar │ │ ├── Contents.json │ │ ├── comments_icon.imageset │ │ │ ├── Contents.json │ │ │ └── comments_icon.png │ │ ├── essential_icon.imageset │ │ │ ├── Contents.json │ │ │ └── essential_icon.png │ │ ├── essential_selected_icon.imageset │ │ │ ├── Contents.json │ │ │ └── essential_selected_icon.png │ │ ├── forum_icon.imageset │ │ │ ├── Contents.json │ │ │ └── forum_icon.png │ │ ├── forum_selected_icon.imageset │ │ │ ├── Contents.json │ │ │ └── forum_selected_icon.png │ │ ├── me_icon.imageset │ │ │ ├── Contents.json │ │ │ └── me_icon.png │ │ ├── me_selected_icon.imageset │ │ │ ├── Contents.json │ │ │ └── me_selected_icon.png │ │ ├── reply_icon.imageset │ │ │ ├── Contents.json │ │ │ └── reply_icon.png │ │ ├── tabbar_backgroud.imageset │ │ │ ├── Contents.json │ │ │ └── tabbar_background@2x.png │ │ ├── wiki_icon.imageset │ │ │ ├── Contents.json │ │ │ └── wiki_icon.png │ │ └── wiki_selected_icon.imageset │ │ │ ├── Contents.json │ │ │ └── wiki_selected_icon.png │ └── Topic │ │ ├── Contents.json │ │ ├── avatar_placeholder.imageset │ │ ├── Contents.json │ │ └── avatar_placeholder.png │ │ ├── comment_icon.imageset │ │ ├── Contents.json │ │ └── comment_icon.png │ │ ├── favorite_icon.imageset │ │ ├── Contents.json │ │ └── favorite_icon.png │ │ ├── pencil_icon.imageset │ │ ├── Contents.json │ │ └── pencil_icon.png │ │ ├── vote_icon.imageset │ │ ├── Contents.json │ │ └── vote_icon.png │ │ └── watch_icon.imageset │ │ ├── Contents.json │ │ └── watch_icon.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── Common │ ├── AppConfig.swift │ ├── Network │ │ ├── AlamofireSwiftyJSON.swift │ │ └── Router.swift │ └── Utils │ │ ├── NSDate+TimeAgoSince.swift │ │ └── SwiftNotice.swift ├── Controller │ ├── Fornum │ │ └── ForumViewController.swift │ ├── MainTabBar │ │ └── MainTabBarController.swift │ ├── Me │ │ ├── EditUserProfileViewController.swift │ │ ├── LoginGuideViewController.swift │ │ ├── LoginViewController.swift │ │ ├── MeTableViewController.swift │ │ ├── QRCodeViewController.swift │ │ └── UserProfileViewController.swift │ └── Topic │ │ ├── CommentViewController.swift │ │ └── TopicListTableViewController.swift ├── Handlers │ ├── AccessTokenHandler.swift │ ├── ApiHandler.swift │ └── CurrentUserHandler.swift ├── Info.plist ├── Models │ ├── Node.swift │ ├── Topic.swift │ └── User.swift ├── TopicDetailViewController.swift └── Views │ ├── Main.storyboard │ └── Topic │ └── TopicListTableViewCell.swift ├── PHPHubTests ├── Info.plist └── PHPHubTests.swift ├── Podfile ├── Podfile.lock ├── Pods └── Manifest.lock ├── README.md └── ScreenShot └── ScreenShot.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/.DS_Store -------------------------------------------------------------------------------- /.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 | # Swift Package Manager 31 | # 32 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 33 | # Packages/ 34 | .build/ 35 | 36 | # CocoaPods 37 | # 38 | # We recommend against adding the Pods directory to your .gitignore. However 39 | # you should judge for yourself, the pros and cons are mentioned at: 40 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 41 | # 42 | Pods/ 43 | 44 | # Carthage 45 | # 46 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 47 | # Carthage/Checkouts 48 | 49 | Carthage/Build 50 | 51 | # fastlane 52 | # 53 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 54 | # screenshots whenever they are needed. 55 | # For more information about the recommended setup visit: 56 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 57 | 58 | fastlane/report.xml 59 | fastlane/screenshots 60 | -------------------------------------------------------------------------------- /PHPHub.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0A0141ED91FCA81995BAD1E3 /* Pods_PHPHub.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CC11DB7644F1113CB2696DA /* Pods_PHPHub.framework */; }; 11 | 251E93E91C156E8800894F8C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E93E81C156E8800894F8C /* AppDelegate.swift */; }; 12 | 251E93F21C156E8800894F8C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 251E93F11C156E8800894F8C /* Assets.xcassets */; }; 13 | 251E93F51C156E8800894F8C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 251E93F31C156E8800894F8C /* LaunchScreen.storyboard */; }; 14 | 251E94001C156E8800894F8C /* PHPHubTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E93FF1C156E8800894F8C /* PHPHubTests.swift */; }; 15 | 251E941A1C17D64500894F8C /* ForumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E94191C17D64500894F8C /* ForumViewController.swift */; }; 16 | 251E941E1C17F2AA00894F8C /* TopicListTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E941D1C17F2AA00894F8C /* TopicListTableViewCell.swift */; }; 17 | 251E94211C180FB600894F8C /* TopicListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E94201C180FB600894F8C /* TopicListTableViewController.swift */; }; 18 | 251E942B1C181D4D00894F8C /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E94291C181D4D00894F8C /* Router.swift */; }; 19 | 251E94311C184FB800894F8C /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 251E94301C184FB800894F8C /* AppConfig.swift */; }; 20 | 2524FFA61C197E4300706D56 /* NSDate+TimeAgoSince.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2524FFA51C197E4300706D56 /* NSDate+TimeAgoSince.swift */; }; 21 | 2524FFAE1C19CF0900706D56 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2524FFAD1C19CF0900706D56 /* MainTabBarController.swift */; }; 22 | 253304771C1D97ED00E3C5F3 /* AuthorizeApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 253304761C1D97ED00E3C5F3 /* AuthorizeApi.swift */; }; 23 | 2533047A1C1E9E5C00E3C5F3 /* AccessTokenHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 253304791C1E9E5C00E3C5F3 /* AccessTokenHandler.swift */; }; 24 | 2533047D1C20079300E3C5F3 /* Topic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2533047C1C20079300E3C5F3 /* Topic.swift */; }; 25 | 2533047F1C20461900E3C5F3 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2533047E1C20461900E3C5F3 /* User.swift */; }; 26 | 253304811C20615B00E3C5F3 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 253304801C20615B00E3C5F3 /* Node.swift */; }; 27 | 253304831C20631300E3C5F3 /* TopicApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 253304821C20631300E3C5F3 /* TopicApi.swift */; }; 28 | 253304851C20636700E3C5F3 /* ApiHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 253304841C20636700E3C5F3 /* ApiHandler.swift */; }; 29 | 2557FECF1C24087800961EF9 /* TopicDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2557FECE1C24087800961EF9 /* TopicDetailViewController.swift */; }; 30 | 2557FED31C2A7EC900961EF9 /* CommentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2557FED21C2A7EC900961EF9 /* CommentViewController.swift */; }; 31 | 25B37E6A1C61E414003241E1 /* UserProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B37E691C61E414003241E1 /* UserProfileViewController.swift */; }; 32 | 25B37E6C1C62FD27003241E1 /* EditUserProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B37E6B1C62FD27003241E1 /* EditUserProfileViewController.swift */; }; 33 | 25B6FF181C2AA02900C16070 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B6FF171C2AA02900C16070 /* LoginViewController.swift */; }; 34 | 25B6FF1A1C2AA29800C16070 /* LoginGuideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B6FF191C2AA29800C16070 /* LoginGuideViewController.swift */; }; 35 | 25B6FF1C1C2ACB0300C16070 /* MeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B6FF1B1C2ACB0300C16070 /* MeTableViewController.swift */; }; 36 | 25B6FF1E1C2BBE4000C16070 /* QRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25B6FF1D1C2BBE4000C16070 /* QRCodeViewController.swift */; }; 37 | 25EC94E91C58B7B700F18013 /* SwiftNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25EC94E81C58B7B700F18013 /* SwiftNotice.swift */; }; 38 | 25EC94EB1C59B60900F18013 /* UserApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25EC94EA1C59B60900F18013 /* UserApi.swift */; }; 39 | 25EC94F31C5F09DE00F18013 /* CurrentUserHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25EC94F21C5F09DE00F18013 /* CurrentUserHandler.swift */; }; 40 | 25EC94F51C5F542800F18013 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 25EC94F41C5F542800F18013 /* Main.storyboard */; }; 41 | B1DE00241F211EC900F243F9 /* AlamofireSwiftyJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DE00231F211EC900F243F9 /* AlamofireSwiftyJSON.swift */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXContainerItemProxy section */ 45 | 251E93FC1C156E8800894F8C /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = 251E93DD1C156E8800894F8C /* Project object */; 48 | proxyType = 1; 49 | remoteGlobalIDString = 251E93E41C156E8800894F8C; 50 | remoteInfo = PHPHub; 51 | }; 52 | /* End PBXContainerItemProxy section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 0CC11DB7644F1113CB2696DA /* Pods_PHPHub.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PHPHub.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 251E93E51C156E8800894F8C /* PHPHub.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PHPHub.app; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 251E93E81C156E8800894F8C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 58 | 251E93F11C156E8800894F8C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 59 | 251E93F41C156E8800894F8C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 60 | 251E93F61C156E8800894F8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 61 | 251E93FB1C156E8800894F8C /* PHPHubTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PHPHubTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 251E93FF1C156E8800894F8C /* PHPHubTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPHubTests.swift; sourceTree = ""; }; 63 | 251E94011C156E8800894F8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 64 | 251E94191C17D64500894F8C /* ForumViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ForumViewController.swift; path = Controller/Fornum/ForumViewController.swift; sourceTree = ""; }; 65 | 251E941D1C17F2AA00894F8C /* TopicListTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TopicListTableViewCell.swift; path = Views/Topic/TopicListTableViewCell.swift; sourceTree = ""; }; 66 | 251E94201C180FB600894F8C /* TopicListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TopicListTableViewController.swift; path = Controller/Topic/TopicListTableViewController.swift; sourceTree = ""; }; 67 | 251E94291C181D4D00894F8C /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Router.swift; path = Common/Network/Router.swift; sourceTree = ""; }; 68 | 251E94301C184FB800894F8C /* AppConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppConfig.swift; path = Common/AppConfig.swift; sourceTree = ""; }; 69 | 2524FFA51C197E4300706D56 /* NSDate+TimeAgoSince.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "NSDate+TimeAgoSince.swift"; path = "Common/Utils/NSDate+TimeAgoSince.swift"; sourceTree = ""; }; 70 | 2524FFAD1C19CF0900706D56 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MainTabBarController.swift; path = Controller/MainTabBar/MainTabBarController.swift; sourceTree = ""; }; 71 | 253304761C1D97ED00E3C5F3 /* AuthorizeApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthorizeApi.swift; path = Apis/AuthorizeApi.swift; sourceTree = ""; }; 72 | 253304791C1E9E5C00E3C5F3 /* AccessTokenHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AccessTokenHandler.swift; path = Handlers/AccessTokenHandler.swift; sourceTree = ""; }; 73 | 2533047C1C20079300E3C5F3 /* Topic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Topic.swift; path = Models/Topic.swift; sourceTree = ""; }; 74 | 2533047E1C20461900E3C5F3 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = User.swift; path = Models/User.swift; sourceTree = ""; }; 75 | 253304801C20615B00E3C5F3 /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Node.swift; path = Models/Node.swift; sourceTree = ""; }; 76 | 253304821C20631300E3C5F3 /* TopicApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TopicApi.swift; path = Apis/TopicApi.swift; sourceTree = ""; }; 77 | 253304841C20636700E3C5F3 /* ApiHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ApiHandler.swift; path = Handlers/ApiHandler.swift; sourceTree = ""; }; 78 | 2557FECE1C24087800961EF9 /* TopicDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TopicDetailViewController.swift; sourceTree = ""; }; 79 | 2557FED21C2A7EC900961EF9 /* CommentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CommentViewController.swift; path = Controller/Topic/CommentViewController.swift; sourceTree = ""; }; 80 | 25B37E691C61E414003241E1 /* UserProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserProfileViewController.swift; path = Controller/Me/UserProfileViewController.swift; sourceTree = ""; }; 81 | 25B37E6B1C62FD27003241E1 /* EditUserProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = EditUserProfileViewController.swift; path = Controller/Me/EditUserProfileViewController.swift; sourceTree = ""; }; 82 | 25B6FF171C2AA02900C16070 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoginViewController.swift; path = Controller/Me/LoginViewController.swift; sourceTree = ""; }; 83 | 25B6FF191C2AA29800C16070 /* LoginGuideViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoginGuideViewController.swift; path = Controller/Me/LoginGuideViewController.swift; sourceTree = ""; }; 84 | 25B6FF1B1C2ACB0300C16070 /* MeTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MeTableViewController.swift; path = Controller/Me/MeTableViewController.swift; sourceTree = ""; }; 85 | 25B6FF1D1C2BBE4000C16070 /* QRCodeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QRCodeViewController.swift; path = Controller/Me/QRCodeViewController.swift; sourceTree = ""; }; 86 | 25EC94E81C58B7B700F18013 /* SwiftNotice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftNotice.swift; path = Common/Utils/SwiftNotice.swift; sourceTree = ""; }; 87 | 25EC94EA1C59B60900F18013 /* UserApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UserApi.swift; path = Apis/UserApi.swift; sourceTree = ""; }; 88 | 25EC94F21C5F09DE00F18013 /* CurrentUserHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CurrentUserHandler.swift; path = Handlers/CurrentUserHandler.swift; sourceTree = ""; }; 89 | 25EC94F41C5F542800F18013 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Views/Main.storyboard; sourceTree = ""; }; 90 | B1DE00231F211EC900F243F9 /* AlamofireSwiftyJSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AlamofireSwiftyJSON.swift; path = Common/Network/AlamofireSwiftyJSON.swift; sourceTree = ""; }; 91 | BA68FAF24C1178BE9EA27469 /* Pods-PHPHub.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PHPHub.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PHPHub/Pods-PHPHub.debug.xcconfig"; sourceTree = ""; }; 92 | D94635953E23DD59ACEC08BE /* Pods-PHPHub.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PHPHub.release.xcconfig"; path = "Pods/Target Support Files/Pods-PHPHub/Pods-PHPHub.release.xcconfig"; sourceTree = ""; }; 93 | /* End PBXFileReference section */ 94 | 95 | /* Begin PBXFrameworksBuildPhase section */ 96 | 251E93E21C156E8800894F8C /* Frameworks */ = { 97 | isa = PBXFrameworksBuildPhase; 98 | buildActionMask = 2147483647; 99 | files = ( 100 | 0A0141ED91FCA81995BAD1E3 /* Pods_PHPHub.framework in Frameworks */, 101 | ); 102 | runOnlyForDeploymentPostprocessing = 0; 103 | }; 104 | 251E93F81C156E8800894F8C /* Frameworks */ = { 105 | isa = PBXFrameworksBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | ); 109 | runOnlyForDeploymentPostprocessing = 0; 110 | }; 111 | /* End PBXFrameworksBuildPhase section */ 112 | 113 | /* Begin PBXGroup section */ 114 | 251E93DC1C156E8800894F8C = { 115 | isa = PBXGroup; 116 | children = ( 117 | 251E93E71C156E8800894F8C /* PHPHub */, 118 | 251E93FE1C156E8800894F8C /* PHPHubTests */, 119 | 251E93E61C156E8800894F8C /* Products */, 120 | BA5571A95D845C6656967A67 /* Pods */, 121 | B570D2B316BF236C557893BD /* Frameworks */, 122 | ); 123 | sourceTree = ""; 124 | }; 125 | 251E93E61C156E8800894F8C /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 251E93E51C156E8800894F8C /* PHPHub.app */, 129 | 251E93FB1C156E8800894F8C /* PHPHubTests.xctest */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | 251E93E71C156E8800894F8C /* PHPHub */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 253304781C1E9E3700E3C5F3 /* Handlers */, 138 | 253304751C1D97C000E3C5F3 /* Apis */, 139 | 251E94221C181BD900894F8C /* Models */, 140 | 251E94171C17D5F400894F8C /* Controllers */, 141 | 251E940B1C158EEC00894F8C /* Views */, 142 | 251E940A1C158EAE00894F8C /* Common */, 143 | 251E93E81C156E8800894F8C /* AppDelegate.swift */, 144 | 251E93F11C156E8800894F8C /* Assets.xcassets */, 145 | 251E93F61C156E8800894F8C /* Info.plist */, 146 | ); 147 | path = PHPHub; 148 | sourceTree = ""; 149 | }; 150 | 251E93FE1C156E8800894F8C /* PHPHubTests */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 251E93FF1C156E8800894F8C /* PHPHubTests.swift */, 154 | 251E94011C156E8800894F8C /* Info.plist */, 155 | ); 156 | path = PHPHubTests; 157 | sourceTree = ""; 158 | }; 159 | 251E940A1C158EAE00894F8C /* Common */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | 251E942C1C181D6300894F8C /* Utils */, 163 | 251E94231C181C5D00894F8C /* Network */, 164 | 251E94301C184FB800894F8C /* AppConfig.swift */, 165 | ); 166 | name = Common; 167 | sourceTree = ""; 168 | }; 169 | 251E940B1C158EEC00894F8C /* Views */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 251E93F31C156E8800894F8C /* LaunchScreen.storyboard */, 173 | 251E940D1C158F0400894F8C /* Topic */, 174 | 25EC94F41C5F542800F18013 /* Main.storyboard */, 175 | ); 176 | name = Views; 177 | sourceTree = ""; 178 | }; 179 | 251E940D1C158F0400894F8C /* Topic */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | 251E941D1C17F2AA00894F8C /* TopicListTableViewCell.swift */, 183 | ); 184 | name = Topic; 185 | sourceTree = ""; 186 | }; 187 | 251E94171C17D5F400894F8C /* Controllers */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | 25B6FF141C2A9E9300C16070 /* Me */, 191 | 2533047B1C1FE06A00E3C5F3 /* Essential */, 192 | 2524FFAA1C19CE3900706D56 /* MainTabBar */, 193 | 251E941F1C180F8A00894F8C /* Topic */, 194 | 251E94181C17D5FB00894F8C /* Forum */, 195 | ); 196 | name = Controllers; 197 | sourceTree = ""; 198 | }; 199 | 251E94181C17D5FB00894F8C /* Forum */ = { 200 | isa = PBXGroup; 201 | children = ( 202 | 251E94191C17D64500894F8C /* ForumViewController.swift */, 203 | ); 204 | name = Forum; 205 | sourceTree = ""; 206 | }; 207 | 251E941F1C180F8A00894F8C /* Topic */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | 251E94201C180FB600894F8C /* TopicListTableViewController.swift */, 211 | 2557FECE1C24087800961EF9 /* TopicDetailViewController.swift */, 212 | 2557FED21C2A7EC900961EF9 /* CommentViewController.swift */, 213 | ); 214 | name = Topic; 215 | sourceTree = ""; 216 | }; 217 | 251E94221C181BD900894F8C /* Models */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | 2533047C1C20079300E3C5F3 /* Topic.swift */, 221 | 2533047E1C20461900E3C5F3 /* User.swift */, 222 | 253304801C20615B00E3C5F3 /* Node.swift */, 223 | ); 224 | name = Models; 225 | sourceTree = ""; 226 | }; 227 | 251E94231C181C5D00894F8C /* Network */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | 251E94291C181D4D00894F8C /* Router.swift */, 231 | B1DE00231F211EC900F243F9 /* AlamofireSwiftyJSON.swift */, 232 | ); 233 | name = Network; 234 | sourceTree = ""; 235 | }; 236 | 251E942C1C181D6300894F8C /* Utils */ = { 237 | isa = PBXGroup; 238 | children = ( 239 | 2524FFA51C197E4300706D56 /* NSDate+TimeAgoSince.swift */, 240 | 25EC94E81C58B7B700F18013 /* SwiftNotice.swift */, 241 | ); 242 | name = Utils; 243 | sourceTree = ""; 244 | }; 245 | 2524FFAA1C19CE3900706D56 /* MainTabBar */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 2524FFAD1C19CF0900706D56 /* MainTabBarController.swift */, 249 | ); 250 | name = MainTabBar; 251 | sourceTree = ""; 252 | }; 253 | 253304751C1D97C000E3C5F3 /* Apis */ = { 254 | isa = PBXGroup; 255 | children = ( 256 | 253304761C1D97ED00E3C5F3 /* AuthorizeApi.swift */, 257 | 253304821C20631300E3C5F3 /* TopicApi.swift */, 258 | 25EC94EA1C59B60900F18013 /* UserApi.swift */, 259 | ); 260 | name = Apis; 261 | sourceTree = ""; 262 | }; 263 | 253304781C1E9E3700E3C5F3 /* Handlers */ = { 264 | isa = PBXGroup; 265 | children = ( 266 | 253304791C1E9E5C00E3C5F3 /* AccessTokenHandler.swift */, 267 | 253304841C20636700E3C5F3 /* ApiHandler.swift */, 268 | 25EC94F21C5F09DE00F18013 /* CurrentUserHandler.swift */, 269 | ); 270 | name = Handlers; 271 | sourceTree = ""; 272 | }; 273 | 2533047B1C1FE06A00E3C5F3 /* Essential */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | ); 277 | name = Essential; 278 | sourceTree = ""; 279 | }; 280 | 25B6FF141C2A9E9300C16070 /* Me */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | 25B6FF171C2AA02900C16070 /* LoginViewController.swift */, 284 | 25B6FF191C2AA29800C16070 /* LoginGuideViewController.swift */, 285 | 25B6FF1B1C2ACB0300C16070 /* MeTableViewController.swift */, 286 | 25B6FF1D1C2BBE4000C16070 /* QRCodeViewController.swift */, 287 | 25B37E691C61E414003241E1 /* UserProfileViewController.swift */, 288 | 25B37E6B1C62FD27003241E1 /* EditUserProfileViewController.swift */, 289 | ); 290 | name = Me; 291 | sourceTree = ""; 292 | }; 293 | B570D2B316BF236C557893BD /* Frameworks */ = { 294 | isa = PBXGroup; 295 | children = ( 296 | 0CC11DB7644F1113CB2696DA /* Pods_PHPHub.framework */, 297 | ); 298 | name = Frameworks; 299 | sourceTree = ""; 300 | }; 301 | BA5571A95D845C6656967A67 /* Pods */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | BA68FAF24C1178BE9EA27469 /* Pods-PHPHub.debug.xcconfig */, 305 | D94635953E23DD59ACEC08BE /* Pods-PHPHub.release.xcconfig */, 306 | ); 307 | name = Pods; 308 | sourceTree = ""; 309 | }; 310 | /* End PBXGroup section */ 311 | 312 | /* Begin PBXNativeTarget section */ 313 | 251E93E41C156E8800894F8C /* PHPHub */ = { 314 | isa = PBXNativeTarget; 315 | buildConfigurationList = 251E94041C156E8800894F8C /* Build configuration list for PBXNativeTarget "PHPHub" */; 316 | buildPhases = ( 317 | 36465A1F895DA6870542295B /* [CP] Check Pods Manifest.lock */, 318 | 251E93E11C156E8800894F8C /* Sources */, 319 | 251E93E21C156E8800894F8C /* Frameworks */, 320 | 251E93E31C156E8800894F8C /* Resources */, 321 | A9B5F9FC8FDE82D087E83DB0 /* [CP] Embed Pods Frameworks */, 322 | 7B81A5938271EA0ACA078FC6 /* [CP] Copy Pods Resources */, 323 | ); 324 | buildRules = ( 325 | ); 326 | dependencies = ( 327 | ); 328 | name = PHPHub; 329 | productName = PHPHub; 330 | productReference = 251E93E51C156E8800894F8C /* PHPHub.app */; 331 | productType = "com.apple.product-type.application"; 332 | }; 333 | 251E93FA1C156E8800894F8C /* PHPHubTests */ = { 334 | isa = PBXNativeTarget; 335 | buildConfigurationList = 251E94071C156E8800894F8C /* Build configuration list for PBXNativeTarget "PHPHubTests" */; 336 | buildPhases = ( 337 | 251E93F71C156E8800894F8C /* Sources */, 338 | 251E93F81C156E8800894F8C /* Frameworks */, 339 | 251E93F91C156E8800894F8C /* Resources */, 340 | ); 341 | buildRules = ( 342 | ); 343 | dependencies = ( 344 | 251E93FD1C156E8800894F8C /* PBXTargetDependency */, 345 | ); 346 | name = PHPHubTests; 347 | productName = PHPHubTests; 348 | productReference = 251E93FB1C156E8800894F8C /* PHPHubTests.xctest */; 349 | productType = "com.apple.product-type.bundle.unit-test"; 350 | }; 351 | /* End PBXNativeTarget section */ 352 | 353 | /* Begin PBXProject section */ 354 | 251E93DD1C156E8800894F8C /* Project object */ = { 355 | isa = PBXProject; 356 | attributes = { 357 | LastSwiftUpdateCheck = 0710; 358 | LastUpgradeCheck = 0810; 359 | ORGANIZATIONNAME = ninerec; 360 | TargetAttributes = { 361 | 251E93E41C156E8800894F8C = { 362 | CreatedOnToolsVersion = 7.1; 363 | LastSwiftMigration = 0800; 364 | }; 365 | 251E93FA1C156E8800894F8C = { 366 | CreatedOnToolsVersion = 7.1; 367 | LastSwiftMigration = 0800; 368 | TestTargetID = 251E93E41C156E8800894F8C; 369 | }; 370 | }; 371 | }; 372 | buildConfigurationList = 251E93E01C156E8800894F8C /* Build configuration list for PBXProject "PHPHub" */; 373 | compatibilityVersion = "Xcode 3.2"; 374 | developmentRegion = English; 375 | hasScannedForEncodings = 0; 376 | knownRegions = ( 377 | en, 378 | Base, 379 | ); 380 | mainGroup = 251E93DC1C156E8800894F8C; 381 | productRefGroup = 251E93E61C156E8800894F8C /* Products */; 382 | projectDirPath = ""; 383 | projectRoot = ""; 384 | targets = ( 385 | 251E93E41C156E8800894F8C /* PHPHub */, 386 | 251E93FA1C156E8800894F8C /* PHPHubTests */, 387 | ); 388 | }; 389 | /* End PBXProject section */ 390 | 391 | /* Begin PBXResourcesBuildPhase section */ 392 | 251E93E31C156E8800894F8C /* Resources */ = { 393 | isa = PBXResourcesBuildPhase; 394 | buildActionMask = 2147483647; 395 | files = ( 396 | 25EC94F51C5F542800F18013 /* Main.storyboard in Resources */, 397 | 251E93F51C156E8800894F8C /* LaunchScreen.storyboard in Resources */, 398 | 251E93F21C156E8800894F8C /* Assets.xcassets in Resources */, 399 | ); 400 | runOnlyForDeploymentPostprocessing = 0; 401 | }; 402 | 251E93F91C156E8800894F8C /* Resources */ = { 403 | isa = PBXResourcesBuildPhase; 404 | buildActionMask = 2147483647; 405 | files = ( 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | /* End PBXResourcesBuildPhase section */ 410 | 411 | /* Begin PBXShellScriptBuildPhase section */ 412 | 36465A1F895DA6870542295B /* [CP] Check Pods Manifest.lock */ = { 413 | isa = PBXShellScriptBuildPhase; 414 | buildActionMask = 2147483647; 415 | files = ( 416 | ); 417 | inputPaths = ( 418 | ); 419 | name = "[CP] Check Pods Manifest.lock"; 420 | outputPaths = ( 421 | ); 422 | runOnlyForDeploymentPostprocessing = 0; 423 | shellPath = /bin/sh; 424 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 425 | showEnvVarsInLog = 0; 426 | }; 427 | 7B81A5938271EA0ACA078FC6 /* [CP] Copy Pods Resources */ = { 428 | isa = PBXShellScriptBuildPhase; 429 | buildActionMask = 2147483647; 430 | files = ( 431 | ); 432 | inputPaths = ( 433 | ); 434 | name = "[CP] Copy Pods Resources"; 435 | outputPaths = ( 436 | ); 437 | runOnlyForDeploymentPostprocessing = 0; 438 | shellPath = /bin/sh; 439 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PHPHub/Pods-PHPHub-resources.sh\"\n"; 440 | showEnvVarsInLog = 0; 441 | }; 442 | A9B5F9FC8FDE82D087E83DB0 /* [CP] Embed Pods Frameworks */ = { 443 | isa = PBXShellScriptBuildPhase; 444 | buildActionMask = 2147483647; 445 | files = ( 446 | ); 447 | inputPaths = ( 448 | ); 449 | name = "[CP] Embed Pods Frameworks"; 450 | outputPaths = ( 451 | ); 452 | runOnlyForDeploymentPostprocessing = 0; 453 | shellPath = /bin/sh; 454 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PHPHub/Pods-PHPHub-frameworks.sh\"\n"; 455 | showEnvVarsInLog = 0; 456 | }; 457 | /* End PBXShellScriptBuildPhase section */ 458 | 459 | /* Begin PBXSourcesBuildPhase section */ 460 | 251E93E11C156E8800894F8C /* Sources */ = { 461 | isa = PBXSourcesBuildPhase; 462 | buildActionMask = 2147483647; 463 | files = ( 464 | 2533047F1C20461900E3C5F3 /* User.swift in Sources */, 465 | B1DE00241F211EC900F243F9 /* AlamofireSwiftyJSON.swift in Sources */, 466 | 253304831C20631300E3C5F3 /* TopicApi.swift in Sources */, 467 | 25B6FF1E1C2BBE4000C16070 /* QRCodeViewController.swift in Sources */, 468 | 25B6FF1C1C2ACB0300C16070 /* MeTableViewController.swift in Sources */, 469 | 251E94311C184FB800894F8C /* AppConfig.swift in Sources */, 470 | 253304851C20636700E3C5F3 /* ApiHandler.swift in Sources */, 471 | 25B6FF181C2AA02900C16070 /* LoginViewController.swift in Sources */, 472 | 25EC94E91C58B7B700F18013 /* SwiftNotice.swift in Sources */, 473 | 2557FECF1C24087800961EF9 /* TopicDetailViewController.swift in Sources */, 474 | 25EC94EB1C59B60900F18013 /* UserApi.swift in Sources */, 475 | 2533047D1C20079300E3C5F3 /* Topic.swift in Sources */, 476 | 251E942B1C181D4D00894F8C /* Router.swift in Sources */, 477 | 25EC94F31C5F09DE00F18013 /* CurrentUserHandler.swift in Sources */, 478 | 2524FFAE1C19CF0900706D56 /* MainTabBarController.swift in Sources */, 479 | 25B37E6C1C62FD27003241E1 /* EditUserProfileViewController.swift in Sources */, 480 | 2533047A1C1E9E5C00E3C5F3 /* AccessTokenHandler.swift in Sources */, 481 | 251E941E1C17F2AA00894F8C /* TopicListTableViewCell.swift in Sources */, 482 | 251E93E91C156E8800894F8C /* AppDelegate.swift in Sources */, 483 | 253304771C1D97ED00E3C5F3 /* AuthorizeApi.swift in Sources */, 484 | 2557FED31C2A7EC900961EF9 /* CommentViewController.swift in Sources */, 485 | 251E94211C180FB600894F8C /* TopicListTableViewController.swift in Sources */, 486 | 25B37E6A1C61E414003241E1 /* UserProfileViewController.swift in Sources */, 487 | 2524FFA61C197E4300706D56 /* NSDate+TimeAgoSince.swift in Sources */, 488 | 251E941A1C17D64500894F8C /* ForumViewController.swift in Sources */, 489 | 253304811C20615B00E3C5F3 /* Node.swift in Sources */, 490 | 25B6FF1A1C2AA29800C16070 /* LoginGuideViewController.swift in Sources */, 491 | ); 492 | runOnlyForDeploymentPostprocessing = 0; 493 | }; 494 | 251E93F71C156E8800894F8C /* Sources */ = { 495 | isa = PBXSourcesBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | 251E94001C156E8800894F8C /* PHPHubTests.swift in Sources */, 499 | ); 500 | runOnlyForDeploymentPostprocessing = 0; 501 | }; 502 | /* End PBXSourcesBuildPhase section */ 503 | 504 | /* Begin PBXTargetDependency section */ 505 | 251E93FD1C156E8800894F8C /* PBXTargetDependency */ = { 506 | isa = PBXTargetDependency; 507 | target = 251E93E41C156E8800894F8C /* PHPHub */; 508 | targetProxy = 251E93FC1C156E8800894F8C /* PBXContainerItemProxy */; 509 | }; 510 | /* End PBXTargetDependency section */ 511 | 512 | /* Begin PBXVariantGroup section */ 513 | 251E93F31C156E8800894F8C /* LaunchScreen.storyboard */ = { 514 | isa = PBXVariantGroup; 515 | children = ( 516 | 251E93F41C156E8800894F8C /* Base */, 517 | ); 518 | name = LaunchScreen.storyboard; 519 | sourceTree = ""; 520 | }; 521 | /* End PBXVariantGroup section */ 522 | 523 | /* Begin XCBuildConfiguration section */ 524 | 251E94021C156E8800894F8C /* Debug */ = { 525 | isa = XCBuildConfiguration; 526 | buildSettings = { 527 | ALWAYS_SEARCH_USER_PATHS = NO; 528 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 529 | CLANG_CXX_LIBRARY = "libc++"; 530 | CLANG_ENABLE_MODULES = YES; 531 | CLANG_ENABLE_OBJC_ARC = YES; 532 | CLANG_WARN_BOOL_CONVERSION = YES; 533 | CLANG_WARN_CONSTANT_CONVERSION = YES; 534 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 535 | CLANG_WARN_EMPTY_BODY = YES; 536 | CLANG_WARN_ENUM_CONVERSION = YES; 537 | CLANG_WARN_INFINITE_RECURSION = YES; 538 | CLANG_WARN_INT_CONVERSION = YES; 539 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 540 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 541 | CLANG_WARN_UNREACHABLE_CODE = YES; 542 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 543 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 544 | COPY_PHASE_STRIP = NO; 545 | DEBUG_INFORMATION_FORMAT = dwarf; 546 | ENABLE_STRICT_OBJC_MSGSEND = YES; 547 | ENABLE_TESTABILITY = YES; 548 | GCC_C_LANGUAGE_STANDARD = gnu99; 549 | GCC_DYNAMIC_NO_PIC = NO; 550 | GCC_NO_COMMON_BLOCKS = YES; 551 | GCC_OPTIMIZATION_LEVEL = 0; 552 | GCC_PREPROCESSOR_DEFINITIONS = ( 553 | "DEBUG=1", 554 | "$(inherited)", 555 | ); 556 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 557 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 558 | GCC_WARN_UNDECLARED_SELECTOR = YES; 559 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 560 | GCC_WARN_UNUSED_FUNCTION = YES; 561 | GCC_WARN_UNUSED_VARIABLE = YES; 562 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 563 | MTL_ENABLE_DEBUG_INFO = YES; 564 | ONLY_ACTIVE_ARCH = YES; 565 | SDKROOT = iphoneos; 566 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 567 | TARGETED_DEVICE_FAMILY = "1,2"; 568 | }; 569 | name = Debug; 570 | }; 571 | 251E94031C156E8800894F8C /* Release */ = { 572 | isa = XCBuildConfiguration; 573 | buildSettings = { 574 | ALWAYS_SEARCH_USER_PATHS = NO; 575 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 576 | CLANG_CXX_LIBRARY = "libc++"; 577 | CLANG_ENABLE_MODULES = YES; 578 | CLANG_ENABLE_OBJC_ARC = YES; 579 | CLANG_WARN_BOOL_CONVERSION = YES; 580 | CLANG_WARN_CONSTANT_CONVERSION = YES; 581 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 582 | CLANG_WARN_EMPTY_BODY = YES; 583 | CLANG_WARN_ENUM_CONVERSION = YES; 584 | CLANG_WARN_INFINITE_RECURSION = YES; 585 | CLANG_WARN_INT_CONVERSION = YES; 586 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 587 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 588 | CLANG_WARN_UNREACHABLE_CODE = YES; 589 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 590 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 591 | COPY_PHASE_STRIP = NO; 592 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 593 | ENABLE_NS_ASSERTIONS = NO; 594 | ENABLE_STRICT_OBJC_MSGSEND = YES; 595 | GCC_C_LANGUAGE_STANDARD = gnu99; 596 | GCC_NO_COMMON_BLOCKS = YES; 597 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 598 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 599 | GCC_WARN_UNDECLARED_SELECTOR = YES; 600 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 601 | GCC_WARN_UNUSED_FUNCTION = YES; 602 | GCC_WARN_UNUSED_VARIABLE = YES; 603 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 604 | MTL_ENABLE_DEBUG_INFO = NO; 605 | SDKROOT = iphoneos; 606 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 607 | TARGETED_DEVICE_FAMILY = "1,2"; 608 | VALIDATE_PRODUCT = YES; 609 | }; 610 | name = Release; 611 | }; 612 | 251E94051C156E8800894F8C /* Debug */ = { 613 | isa = XCBuildConfiguration; 614 | baseConfigurationReference = BA68FAF24C1178BE9EA27469 /* Pods-PHPHub.debug.xcconfig */; 615 | buildSettings = { 616 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 617 | CODE_SIGN_IDENTITY = "iPhone Developer"; 618 | DEVELOPMENT_TEAM = ""; 619 | INFOPLIST_FILE = PHPHub/Info.plist; 620 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 621 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 622 | PRODUCT_BUNDLE_IDENTIFIER = com.ninerec.PHPHub; 623 | PRODUCT_NAME = "$(TARGET_NAME)"; 624 | SWIFT_VERSION = 3.0.1; 625 | }; 626 | name = Debug; 627 | }; 628 | 251E94061C156E8800894F8C /* Release */ = { 629 | isa = XCBuildConfiguration; 630 | baseConfigurationReference = D94635953E23DD59ACEC08BE /* Pods-PHPHub.release.xcconfig */; 631 | buildSettings = { 632 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 633 | CODE_SIGN_IDENTITY = "iPhone Developer"; 634 | DEVELOPMENT_TEAM = ""; 635 | INFOPLIST_FILE = PHPHub/Info.plist; 636 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 637 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 638 | PRODUCT_BUNDLE_IDENTIFIER = com.ninerec.PHPHub; 639 | PRODUCT_NAME = "$(TARGET_NAME)"; 640 | SWIFT_VERSION = 3.0.1; 641 | }; 642 | name = Release; 643 | }; 644 | 251E94081C156E8800894F8C /* Debug */ = { 645 | isa = XCBuildConfiguration; 646 | buildSettings = { 647 | BUNDLE_LOADER = "$(TEST_HOST)"; 648 | INFOPLIST_FILE = PHPHubTests/Info.plist; 649 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 650 | PRODUCT_BUNDLE_IDENTIFIER = com.ninerec.PHPHubTests; 651 | PRODUCT_NAME = "$(TARGET_NAME)"; 652 | SWIFT_VERSION = 3.0; 653 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PHPHub.app/PHPHub"; 654 | }; 655 | name = Debug; 656 | }; 657 | 251E94091C156E8800894F8C /* Release */ = { 658 | isa = XCBuildConfiguration; 659 | buildSettings = { 660 | BUNDLE_LOADER = "$(TEST_HOST)"; 661 | INFOPLIST_FILE = PHPHubTests/Info.plist; 662 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 663 | PRODUCT_BUNDLE_IDENTIFIER = com.ninerec.PHPHubTests; 664 | PRODUCT_NAME = "$(TARGET_NAME)"; 665 | SWIFT_VERSION = 3.0; 666 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PHPHub.app/PHPHub"; 667 | }; 668 | name = Release; 669 | }; 670 | /* End XCBuildConfiguration section */ 671 | 672 | /* Begin XCConfigurationList section */ 673 | 251E93E01C156E8800894F8C /* Build configuration list for PBXProject "PHPHub" */ = { 674 | isa = XCConfigurationList; 675 | buildConfigurations = ( 676 | 251E94021C156E8800894F8C /* Debug */, 677 | 251E94031C156E8800894F8C /* Release */, 678 | ); 679 | defaultConfigurationIsVisible = 0; 680 | defaultConfigurationName = Debug; 681 | }; 682 | 251E94041C156E8800894F8C /* Build configuration list for PBXNativeTarget "PHPHub" */ = { 683 | isa = XCConfigurationList; 684 | buildConfigurations = ( 685 | 251E94051C156E8800894F8C /* Debug */, 686 | 251E94061C156E8800894F8C /* Release */, 687 | ); 688 | defaultConfigurationIsVisible = 0; 689 | defaultConfigurationName = Debug; 690 | }; 691 | 251E94071C156E8800894F8C /* Build configuration list for PBXNativeTarget "PHPHubTests" */ = { 692 | isa = XCConfigurationList; 693 | buildConfigurations = ( 694 | 251E94081C156E8800894F8C /* Debug */, 695 | 251E94091C156E8800894F8C /* Release */, 696 | ); 697 | defaultConfigurationIsVisible = 0; 698 | defaultConfigurationName = Debug; 699 | }; 700 | /* End XCConfigurationList section */ 701 | }; 702 | rootObject = 251E93DD1C156E8800894F8C /* Project object */; 703 | } 704 | -------------------------------------------------------------------------------- /PHPHub.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PHPHub.xcodeproj/xcuserdata/MLS.xcuserdatad/xcschemes/PHPHub.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /PHPHub.xcodeproj/xcuserdata/MLS.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PHPHub.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 251E93E41C156E8800894F8C 16 | 17 | primary 18 | 19 | 20 | 251E93FA1C156E8800894F8C 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /PHPHub.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /PHPHub/Apis/AuthorizeApi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AuthorizeApi.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/13. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import SwiftyJSON 11 | 12 | class AuthorizeApi { 13 | class func getClientAccessToken(_ callback: @escaping (JSON) -> Void) { 14 | let parameters:[String: AnyObject] = [ 15 | "grant_type" : "client_credentials" as AnyObject, 16 | "client_id": AppConfig.Api.Client_id as AnyObject, 17 | "client_secret": AppConfig.Api.Client_secret as AnyObject 18 | ] 19 | 20 | Alamofire.request(Router.authorize(parameters)) 21 | .responseSwiftyJSON { response in 22 | switch response.result { 23 | case .success(let value): 24 | callback(value) 25 | case .failure(let error): 26 | debugPrint(error) 27 | } 28 | } 29 | } 30 | 31 | class func getLoginAccessToken(username: String, loginToken: String, callback: @escaping (JSON) -> Void) { 32 | let parameters:[String: AnyObject] = [ 33 | "grant_type" : "login_token" as AnyObject, 34 | "client_id": AppConfig.Api.Client_id as AnyObject, 35 | "client_secret": AppConfig.Api.Client_secret as AnyObject, 36 | "username": username as AnyObject, 37 | "login_token": loginToken as AnyObject 38 | ] 39 | 40 | Alamofire.request(Router.authorize(parameters)) 41 | .responseSwiftyJSON { response in 42 | switch response.result { 43 | case .success(let value): 44 | callback(value) 45 | case .failure(let error): 46 | debugPrint(error) 47 | } 48 | } 49 | } 50 | 51 | class func refreshLoginAccessToken(_ callback: @escaping (JSON) -> Void) { 52 | let parameters:[String: AnyObject] = [ 53 | "grant_type" : "refresh_token" as AnyObject, 54 | "client_id": AppConfig.Api.Client_id as AnyObject, 55 | "client_secret": AppConfig.Api.Client_secret as AnyObject 56 | ] 57 | 58 | Alamofire.request(Router.authorize(parameters)) 59 | .responseSwiftyJSON { response in 60 | switch response.result { 61 | case .success(let value): 62 | callback(value) 63 | case .failure(let error): 64 | debugPrint(error) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /PHPHub/Apis/TopicApi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Topic.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/15. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import SwiftyJSON 11 | 12 | enum TopicListApi { 13 | case essential 14 | case newest 15 | case hotest 16 | case jobs 17 | case wiki 18 | case user(Int) // 用户发布的帖子 19 | case attention(Int) // 关注 20 | case favorite(Int) // 收藏 21 | 22 | var filter: String? { 23 | switch self { 24 | case .essential: 25 | return "excellent" 26 | case .newest: 27 | return "newest" 28 | case .hotest: 29 | return "vote" 30 | case .jobs: 31 | return "jobs" 32 | case .wiki: 33 | return "wiki" 34 | default: 35 | return nil 36 | } 37 | } 38 | 39 | func getTopicListAtPage(_ atPage:Int, callback: @escaping ([Topic]) -> Void) { 40 | var parameters:[String: AnyObject] = [ 41 | "include" : "node,last_reply_user,user" as AnyObject, 42 | "per_page": 20 as AnyObject, 43 | "page": atPage as AnyObject, 44 | "columns": "user(signature)" as AnyObject 45 | ] 46 | 47 | switch self { 48 | case .essential, .newest, .hotest, .jobs, .wiki: 49 | parameters["filters"] = self.filter! as AnyObject? 50 | ApiHandler.sharedInstance.CollectionRequest(Router.topicList(parameters), callback: callback) 51 | case .user(let userId): 52 | ApiHandler.sharedInstance.CollectionRequest(Router.userTopiclist(userId, parameters), callback: callback) 53 | case .attention(let userId): 54 | ApiHandler.sharedInstance.CollectionRequest(Router.userAttentionTopiclist(userId, parameters), callback: callback) 55 | case .favorite(let userId): 56 | ApiHandler.sharedInstance.CollectionRequest(Router.userFavoriteTopiclist(userId, parameters), callback: callback) 57 | } 58 | } 59 | } 60 | 61 | 62 | class TopicApi { 63 | class func getTopicListByFilter(_ filter: String, atPage: Int, callback: @escaping ([Topic]) -> Void){ 64 | let parameters:[String: AnyObject] = [ 65 | "include" : "node,last_reply_user,user" as AnyObject, 66 | "filters": filter as AnyObject, 67 | "per_page": 20 as AnyObject, 68 | "page": atPage as AnyObject, 69 | "columns": "user(signature)" as AnyObject 70 | ] 71 | 72 | ApiHandler.sharedInstance.CollectionRequest(Router.topicList(parameters), callback: callback) 73 | } 74 | 75 | class func getEssentialTopicList(_ atPage: Int, callback: @escaping ([Topic]) -> Void) { 76 | getTopicListByFilter("excellent", atPage: atPage, callback: callback) 77 | } 78 | 79 | class func getNewestTopicList(_ atPage: Int, callback: @escaping ([Topic]) -> Void) { 80 | getTopicListByFilter("newest", atPage: atPage, callback: callback) 81 | } 82 | 83 | class func getHotestTopicList(_ atPage: Int, callback: @escaping ([Topic]) -> Void) { 84 | getTopicListByFilter("vot", atPage: atPage, callback: callback) 85 | } 86 | 87 | class func getJobTopicList(_ atPage: Int, callback: @escaping ([Topic]) -> Void) { 88 | getTopicListByFilter("jobs", atPage: atPage, callback: callback) 89 | } 90 | 91 | class func getWikiTopicList(_ atPage: Int, callback: @escaping ([Topic]) -> Void) { 92 | getTopicListByFilter("wiki", atPage: atPage, callback: callback) 93 | } 94 | 95 | class func getTopicDetails(_ topicId: Int, callback: @escaping (String) -> Void) { 96 | ApiHandler.sharedInstance.StringRequest(Router.topicDetails(topicId), callback: callback) 97 | } 98 | 99 | class func getTopicReplies(_ topicId: Int, callback: @escaping (String) -> Void) { 100 | ApiHandler.sharedInstance.StringRequest(Router.topicDetails(topicId), callback: callback) 101 | } 102 | 103 | class func getAttentionTopicListByUser(_ userId: Int, atPage: Int, callback: @escaping ([Topic]) -> Void) { 104 | getTopicListByFilter("attention", atPage: atPage, callback: callback) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /PHPHub/Apis/UserApi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserApi.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 16/1/28. 6 | // Copyright © 2016年 ninerec. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import SwiftyJSON 11 | 12 | class UserApi { 13 | class func getCurrentUser(_ callback: @escaping (User) -> Void) { 14 | ApiHandler.sharedInstance.ObjectRequest(Router.currentUser, callback: callback) 15 | } 16 | 17 | class func updateCurrentUser(_ userId: Int, parameters:[String: AnyObject], callback: @escaping (User) -> Void) { 18 | ApiHandler.sharedInstance.ObjectRequest(Router.updateUser(userId, parameters), callback: callback) 19 | } 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /PHPHub/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/7. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let appDelegate = UIApplication.shared.delegate as! AppDelegate 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | var tabBarController: UITabBarController { 19 | return self.window!.rootViewController as! UITabBarController 20 | } 21 | 22 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 23 | 24 | CurrentUserHandler.setDefaultHandler(CurrentUserHandler()) 25 | return true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "icon_58.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "icon_87.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "icon_80.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "icon_120.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "icon_120-1.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "icon_180.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "size" : "29x29", 61 | "idiom" : "ipad", 62 | "filename" : "icon_29.png", 63 | "scale" : "1x" 64 | }, 65 | { 66 | "size" : "29x29", 67 | "idiom" : "ipad", 68 | "filename" : "icon_58-1.png", 69 | "scale" : "2x" 70 | }, 71 | { 72 | "size" : "40x40", 73 | "idiom" : "ipad", 74 | "filename" : "icon_40.png", 75 | "scale" : "1x" 76 | }, 77 | { 78 | "size" : "40x40", 79 | "idiom" : "ipad", 80 | "filename" : "icon_80-1.png", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "size" : "76x76", 85 | "idiom" : "ipad", 86 | "filename" : "icon_76.png", 87 | "scale" : "1x" 88 | }, 89 | { 90 | "size" : "76x76", 91 | "idiom" : "ipad", 92 | "filename" : "icon_152.png", 93 | "scale" : "2x" 94 | }, 95 | { 96 | "idiom" : "ipad", 97 | "size" : "83.5x83.5", 98 | "scale" : "2x" 99 | } 100 | ], 101 | "info" : { 102 | "version" : 1, 103 | "author" : "xcode" 104 | } 105 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_120-1.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_120.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_152.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_180.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_29.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_40.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_58-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_58-1.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_58.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_76.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_80-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_80-1.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_80.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/AppIcon.appiconset/icon_87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/AppIcon.appiconset/icon_87.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "back.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/back.imageset/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Base/back.imageset/back.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/cancel.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "cancel.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/cancel.imageset/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Base/cancel.imageset/cancel.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/icon_text.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "PHPHub-icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/icon_text.imageset/PHPHub-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Base/icon_text.imageset/PHPHub-icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_180.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/logo.imageset/icon_180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Base/logo.imageset/icon_180.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/more.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "more_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/more.imageset/more_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Base/more.imageset/more_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/pure_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "pure_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Base/pure_icon.imageset/pure_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Base/pure_icon.imageset/pure_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/anonymous_logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "anonymous_logo.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/anonymous_logo.imageset/anonymous_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/anonymous_logo.imageset/anonymous_logo.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/blog_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "blog_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/blog_icon.imageset/blog_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/blog_icon.imageset/blog_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/edit_profile_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "edit_profile_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/edit_profile_icon.imageset/edit_profile_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/edit_profile_icon.imageset/edit_profile_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/github_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "github_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/github_icon.imageset/github_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/github_icon.imageset/github_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/local_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "local_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/local_icon.imageset/local_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/local_icon.imageset/local_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/ring_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "ring_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/ring_icon.imageset/ring_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/ring_icon.imageset/ring_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/settings_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "settings_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/settings_icon.imageset/settings_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/settings_icon.imageset/settings_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/topic_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "topic_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/topic_icon.imageset/topic_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/topic_icon.imageset/topic_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/twitter_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "twitter_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Me/twitter_icon.imageset/twitter_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Me/twitter_icon.imageset/twitter_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/comments_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "comments_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/comments_icon.imageset/comments_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/comments_icon.imageset/comments_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/essential_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "essential_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/essential_icon.imageset/essential_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/essential_icon.imageset/essential_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/essential_selected_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "essential_selected_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/essential_selected_icon.imageset/essential_selected_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/essential_selected_icon.imageset/essential_selected_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/forum_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "forum_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/forum_icon.imageset/forum_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/forum_icon.imageset/forum_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/forum_selected_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "forum_selected_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/forum_selected_icon.imageset/forum_selected_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/forum_selected_icon.imageset/forum_selected_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/me_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "me_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/me_icon.imageset/me_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/me_icon.imageset/me_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/me_selected_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "me_selected_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/me_selected_icon.imageset/me_selected_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/me_selected_icon.imageset/me_selected_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/reply_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "reply_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/reply_icon.imageset/reply_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/reply_icon.imageset/reply_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/tabbar_backgroud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "tabbar_background@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/tabbar_backgroud.imageset/tabbar_background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/tabbar_backgroud.imageset/tabbar_background@2x.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/wiki_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "wiki_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/wiki_icon.imageset/wiki_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/wiki_icon.imageset/wiki_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/wiki_selected_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "wiki_selected_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/TabBar/wiki_selected_icon.imageset/wiki_selected_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/TabBar/wiki_selected_icon.imageset/wiki_selected_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/avatar_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "avatar_placeholder.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/avatar_placeholder.imageset/avatar_placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Topic/avatar_placeholder.imageset/avatar_placeholder.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/comment_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "comment_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/comment_icon.imageset/comment_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Topic/comment_icon.imageset/comment_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/favorite_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "favorite_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/favorite_icon.imageset/favorite_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Topic/favorite_icon.imageset/favorite_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/pencil_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "pencil_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/pencil_icon.imageset/pencil_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Topic/pencil_icon.imageset/pencil_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/vote_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "vote_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/vote_icon.imageset/vote_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Topic/vote_icon.imageset/vote_icon.png -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/watch_icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "watch_icon.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /PHPHub/Assets.xcassets/Topic/watch_icon.imageset/watch_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/PHPHub/Assets.xcassets/Topic/watch_icon.imageset/watch_icon.png -------------------------------------------------------------------------------- /PHPHub/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 | 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 | -------------------------------------------------------------------------------- /PHPHub/Common/AppConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConfig.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/9. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | struct AppConfig { 10 | static let Debug = true 11 | 12 | struct Api { 13 | static var BasicUrl:String { 14 | return Debug ? "https://staging_api.phphub.org/v1" : "https://api.phphub.org/v1" 15 | } 16 | static let Client_id = "kHOugsx4dmcXwvVbmLkd" 17 | static let Client_secret = "PuuFCrF94MloSbSkxpwS" 18 | 19 | static let TimeoutIntervals = 10 20 | } 21 | 22 | static let viewControllerClassesThatRequireLogin: [AnyObject.Type] = [MeTableViewController.self] 23 | 24 | static let KeyChainService = "PHPHubService" 25 | static let KeyChainClientAccount = "com.PHPHub.client" 26 | static let KeyChainLoginAccount = "com.PHPHub.login" 27 | // static let keychainExpire = "com.PHPHub.expire" 28 | 29 | static let LoginGuide = "http://7xnqwn.com1.z0.glb.clouddn.com/index.html" 30 | static let TwitterUrl = "https://twitter.com/" 31 | static let GithubURL = "https://github.com/" 32 | } 33 | -------------------------------------------------------------------------------- /PHPHub/Common/Network/AlamofireSwiftyJSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlamofireSwiftyJSON.swift 3 | // AlamofireSwiftyJSON 4 | // 5 | // Created by Pinglin Tang on 14-9-22. 6 | // Copyright (c) 2014 SwiftyJSON. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Alamofire 12 | import SwiftyJSON 13 | 14 | // MARK: - Request for Swift JSON 15 | 16 | extension Request { 17 | /// Returns a SwiftyJSON object contained in a result type constructed from the response data using `JSONSerialization` 18 | /// with the specified reading options. 19 | /// 20 | /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. 21 | /// - parameter response: The response from the server. 22 | /// - parameter data: The data returned from the server. 23 | /// - parameter error: The error already encountered if it exists. 24 | /// 25 | /// - returns: The result data type. 26 | public static func serializeResponseSwiftyJSON( 27 | options: JSONSerialization.ReadingOptions, 28 | response: HTTPURLResponse?, 29 | data: Data?, 30 | error: Error?) 31 | -> Result 32 | { 33 | guard error == nil else { return .failure(error!) } 34 | 35 | if let response = response, emptyDataStatusCodes.contains(response.statusCode) { return .success(JSON.null) } 36 | 37 | guard let validData = data, validData.count > 0 else { 38 | return .failure(AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)) 39 | } 40 | 41 | do { 42 | let json = try JSONSerialization.jsonObject(with: validData, options: options) 43 | return .success(JSON(json)) 44 | } catch { 45 | return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error))) 46 | } 47 | } 48 | } 49 | 50 | extension DataRequest { 51 | /// Creates a response serializer that returns a SwiftyJSON object result type constructed from the response data using 52 | /// `JSONSerialization` with the specified reading options. 53 | /// 54 | /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. 55 | /// 56 | /// - returns: A JSON object response serializer. 57 | public static func swiftyJSONResponseSerializer( 58 | options: JSONSerialization.ReadingOptions = .allowFragments) 59 | -> DataResponseSerializer 60 | { 61 | return DataResponseSerializer { _, response, data, error in 62 | return Request.serializeResponseSwiftyJSON(options: options, response: response, data: data, error: error) 63 | } 64 | } 65 | 66 | /// Adds a handler to be called once the request has finished. 67 | /// 68 | /// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`. 69 | /// - parameter completionHandler: A closure to be executed once the request has finished. 70 | /// 71 | /// - returns: The request. 72 | @discardableResult 73 | public func responseSwiftyJSON( 74 | queue: DispatchQueue? = nil, 75 | options: JSONSerialization.ReadingOptions = .allowFragments, 76 | completionHandler: @escaping (DataResponse) -> Void) 77 | -> Self 78 | { 79 | return response( 80 | queue: queue, 81 | responseSerializer: DataRequest.swiftyJSONResponseSerializer(options: options), 82 | completionHandler: completionHandler 83 | ) 84 | } 85 | } 86 | 87 | private let emptyDataStatusCodes: Set = [204, 205] 88 | -------------------------------------------------------------------------------- /PHPHub/Common/Network/Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Router.swift 3 | // SpeedSwift 4 | // 5 | // Created by 2014-104 on 15/12/1. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import KeychainAccess 11 | 12 | enum Router: URLRequestConvertible { 13 | static var AccessToken: String? 14 | 15 | // ImV2 API 16 | case authorize([String: AnyObject]) 17 | case topicList([String: AnyObject]) 18 | case topicDetails(Int) 19 | case topicReplies(Int) 20 | case currentUser 21 | case updateUser(Int, [String: AnyObject]) 22 | case userTopiclist(Int, [String: AnyObject]) 23 | case userAttentionTopiclist(Int, [String: AnyObject]) 24 | case userFavoriteTopiclist(Int, [String: AnyObject]) 25 | 26 | var method: HTTPMethod { 27 | switch self { 28 | case .authorize: 29 | return .post 30 | case .topicList: 31 | return .get 32 | case .updateUser: 33 | return .put 34 | default: 35 | return .get 36 | } 37 | } 38 | 39 | var path: String { 40 | switch self { 41 | case .authorize: 42 | return "/oauth/access_token" 43 | case .topicList: 44 | return "/topics" 45 | case .topicDetails(let topicId): 46 | return "/topics/\(topicId)/web_view" 47 | case .topicReplies(let topicId): 48 | return "/topics/\(topicId)/replies/web_view" 49 | case .currentUser: 50 | return "/me" 51 | case .updateUser(let userId, _): 52 | return "/users/\(userId)" 53 | case .userTopiclist(let userId, _): 54 | return "/user/\(userId)/topics" 55 | case .userAttentionTopiclist(let userId, _): 56 | return "/user/\(userId)/attention/topics" 57 | case .userFavoriteTopiclist(let userId, _): 58 | return "/user/\(userId)/favorite/topics" 59 | } 60 | } 61 | 62 | // MARK: URLRequestConvertible 63 | 64 | func asURLRequest() throws -> URLRequest { 65 | switch self { 66 | case .authorize(let parameters): 67 | return try URLEncoding.default.encode(getClientRequest(), with: parameters) 68 | case .topicList(let parameters): 69 | return try URLEncoding.default.encode(getClientRequest(), with: parameters) 70 | case .userTopiclist(_, let parameters): 71 | return try URLEncoding.default.encode(getLoginRequest(), with: parameters) 72 | case .userAttentionTopiclist(_, let parameters): 73 | return try URLEncoding.default.encode(getLoginRequest(), with: parameters) 74 | case .userFavoriteTopiclist(_, let parameters): 75 | return try URLEncoding.default.encode(getLoginRequest(), with: parameters) 76 | case .currentUser: 77 | return try URLEncoding.default.encode(getLoginRequest(), with: nil) 78 | case .updateUser(_, let parameters): 79 | return try URLEncoding.default.encode(getLoginRequest(), with: parameters) 80 | default: 81 | return try URLEncoding.default.encode(getClientRequest(), with: nil) 82 | } 83 | } 84 | 85 | func getClientRequest() -> Foundation.URLRequest { 86 | let URL = Foundation.URL(string: AppConfig.Api.BasicUrl)! 87 | let mutableURLRequest = NSMutableURLRequest(url: URL.appendingPathComponent(path)) 88 | mutableURLRequest.httpMethod = method.rawValue 89 | 90 | // Set the Header 91 | let keychain = Keychain(service: AppConfig.KeyChainService) 92 | if let token = keychain[AppConfig.KeyChainClientAccount] { 93 | mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") 94 | } 95 | mutableURLRequest.setValue("application/vnd.PHPHub.v1+json", forHTTPHeaderField: "Accept") 96 | mutableURLRequest.setValue("iOS", forHTTPHeaderField: "X-Client-Platform") 97 | 98 | return mutableURLRequest as URLRequest 99 | } 100 | 101 | func getLoginRequest() -> Foundation.URLRequest { 102 | let URL = Foundation.URL(string: AppConfig.Api.BasicUrl)! 103 | let mutableURLRequest = NSMutableURLRequest(url: URL.appendingPathComponent(path)) 104 | mutableURLRequest.httpMethod = method.rawValue 105 | 106 | // Set the Header 107 | let accessTokenHandler = AccessTokenHandler() 108 | if let token = accessTokenHandler.getLocalLoginAccessToken() { 109 | mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") 110 | } 111 | mutableURLRequest.setValue("application/vnd.PHPHub.v1+json", forHTTPHeaderField: "Accept") 112 | mutableURLRequest.setValue("iOS", forHTTPHeaderField: "X-Client-Platform") 113 | 114 | return mutableURLRequest as URLRequest 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /PHPHub/Common/Utils/NSDate+TimeAgoSince.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+TimeAgoSince.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/10. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Date { 12 | func timeAgoSinceNow() -> String { 13 | return timeAgoSinceDate(self, numericDates: true) 14 | } 15 | 16 | func timeAgoSinceDate(_ date: Date, numericDates: Bool) -> String { 17 | 18 | let calendar = Calendar.current 19 | let unitFlags: NSCalendar.Unit = [ .year, .month, .weekOfYear, .day, .hour, .minute, .second ] 20 | let now = Date() 21 | let earliest = (now as NSDate).earlierDate(date) 22 | let latest = earliest == now ? date : now 23 | let components = (calendar as NSCalendar).components(unitFlags, from: earliest, to: latest, options: []) 24 | 25 | if components.year! >= 2{ 26 | return "\(components.year)年前" 27 | } else if components.year! >= 1 { 28 | return numericDates ? "1年前" : "去年" 29 | } else if components.month! >= 2{ 30 | return "\(components.month)月前" 31 | } else if components.month! >= 1 { 32 | return numericDates ? "1月前" : "上个月" 33 | } else if components.weekOfYear! >= 2 { 34 | return "\(components.weekOfYear)周前" 35 | } else if components.weekOfYear! >= 1 { 36 | return numericDates ? "1周前" : "上周" 37 | } else if components.day! >= 2 { 38 | return "\(components.day)天前" 39 | } else if components.day! >= 1 { 40 | return numericDates ? "1天前" : "昨天" 41 | } else if components.hour! >= 2 { 42 | return "\(components.hour)小时前" 43 | } else if components.hour! >= 1 { 44 | return numericDates ? "1小时前" : "一小时前" 45 | } else if components.minute! >= 2 { 46 | return "\(components.minute)分钟前" 47 | } else if components.minute! >= 1 { 48 | return numericDates ? "1分钟前" : "一分钟前" 49 | } else if components.second! >= 3 { 50 | return "\(components.second)秒前" 51 | } 52 | 53 | return "刚刚" 54 | } 55 | 56 | static func convertFromString(_ stringData: String) -> Date? { 57 | let dateFormatter = DateFormatter() 58 | dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss" 59 | 60 | let date = dateFormatter.date(from: stringData) 61 | return date 62 | } 63 | 64 | func convertToString() -> String { 65 | let dateFormatter = DateFormatter() 66 | dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss" 67 | 68 | let dateString = dateFormatter.string(from: self) 69 | return dateString 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /PHPHub/Common/Utils/SwiftNotice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftNotice.swift 3 | // SwiftNotice 4 | // 5 | // Created by JohnLui on 15/4/15. 6 | // Copyright (c) 2015年 com.lvwenhan. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | private let sn_topBar: Int = 1001 13 | 14 | extension UIResponder { 15 | /// wait with your own animated images 16 | @discardableResult 17 | func pleaseWaitWithImages(_ imageNames: Array, timeInterval: Int) -> UIWindow{ 18 | return SwiftNotice.wait(imageNames, timeInterval: timeInterval) 19 | } 20 | // api changed from v3.3 21 | @discardableResult 22 | func noticeTop(_ text: String, autoClear: Bool = true, autoClearTime: Int = 1) -> UIWindow{ 23 | return SwiftNotice.noticeOnStatusBar(text, autoClear: autoClear, autoClearTime: autoClearTime) 24 | } 25 | 26 | // new apis from v3.3 27 | @discardableResult 28 | func noticeSuccess(_ text: String, autoClear: Bool = false, autoClearTime: Int = 3) -> UIWindow{ 29 | return SwiftNotice.showNoticeWithText(NoticeType.success, text: text, autoClear: autoClear, autoClearTime: autoClearTime) 30 | } 31 | @discardableResult 32 | func noticeError(_ text: String, autoClear: Bool = false, autoClearTime: Int = 3) -> UIWindow{ 33 | return SwiftNotice.showNoticeWithText(NoticeType.error, text: text, autoClear: autoClear, autoClearTime: autoClearTime) 34 | } 35 | @discardableResult 36 | func noticeInfo(_ text: String, autoClear: Bool = false, autoClearTime: Int = 3) -> UIWindow{ 37 | return SwiftNotice.showNoticeWithText(NoticeType.info, text: text, autoClear: autoClear, autoClearTime: autoClearTime) 38 | } 39 | 40 | // old apis 41 | @discardableResult 42 | func successNotice(_ text: String, autoClear: Bool = true) -> UIWindow{ 43 | return SwiftNotice.showNoticeWithText(NoticeType.success, text: text, autoClear: autoClear, autoClearTime: 3) 44 | } 45 | @discardableResult 46 | func errorNotice(_ text: String, autoClear: Bool = true) -> UIWindow{ 47 | return SwiftNotice.showNoticeWithText(NoticeType.error, text: text, autoClear: autoClear, autoClearTime: 3) 48 | } 49 | @discardableResult 50 | func infoNotice(_ text: String, autoClear: Bool = true) -> UIWindow{ 51 | return SwiftNotice.showNoticeWithText(NoticeType.info, text: text, autoClear: autoClear, autoClearTime: 3) 52 | } 53 | @discardableResult 54 | func notice(_ text: String, type: NoticeType, autoClear: Bool, autoClearTime: Int = 3) -> UIWindow{ 55 | return SwiftNotice.showNoticeWithText(type, text: text, autoClear: autoClear, autoClearTime: autoClearTime) 56 | } 57 | @discardableResult 58 | func pleaseWait() -> UIWindow{ 59 | return SwiftNotice.wait() 60 | } 61 | @discardableResult 62 | func noticeOnlyText(_ text: String) -> UIWindow{ 63 | return SwiftNotice.showText(text) 64 | } 65 | func clearAllNotice() { 66 | SwiftNotice.clear() 67 | } 68 | } 69 | 70 | enum NoticeType{ 71 | case success 72 | case error 73 | case info 74 | } 75 | 76 | class SwiftNotice: NSObject { 77 | 78 | static var windows = Array() 79 | static let rv = UIApplication.shared.keyWindow?.subviews.first as UIView! 80 | static var timer: DispatchSource! 81 | static var timerTimes = 0 82 | 83 | /* just for iOS 8 84 | */ 85 | static var degree: Double { 86 | get { 87 | return [0, 0, 180, 270, 90][UIApplication.shared.statusBarOrientation.hashValue] as Double 88 | } 89 | } 90 | 91 | // fix https://github.com/johnlui/SwiftNotice/issues/2 92 | // thanks broccolii(https://github.com/broccolii) and his PR https://github.com/johnlui/SwiftNotice/pull/5 93 | static func clear() { 94 | self.cancelPreviousPerformRequests(withTarget: self) 95 | if let _ = timer { 96 | timer.cancel() 97 | timer = nil 98 | timerTimes = 0 99 | } 100 | windows.removeAll(keepingCapacity: false) 101 | } 102 | 103 | @discardableResult 104 | static func noticeOnStatusBar(_ text: String, autoClear: Bool, autoClearTime: Int) -> UIWindow{ 105 | let frame = UIApplication.shared.statusBarFrame 106 | let window = UIWindow() 107 | window.backgroundColor = UIColor.clear 108 | let view = UIView() 109 | view.backgroundColor = UIColor(red: 0x6a/0x100, green: 0xb4/0x100, blue: 0x9f/0x100, alpha: 1) 110 | 111 | let label = UILabel(frame: frame) 112 | label.textAlignment = NSTextAlignment.center 113 | label.font = UIFont.systemFont(ofSize: 12) 114 | label.textColor = UIColor.white 115 | label.text = text 116 | view.addSubview(label) 117 | 118 | window.frame = frame 119 | view.frame = frame 120 | 121 | if let version = Double(UIDevice.current.systemVersion), 122 | version < 9.0 { 123 | // change center 124 | var array = [UIScreen.main.bounds.width, UIScreen.main.bounds.height] 125 | array = array.sorted(by: <) 126 | let screenWidth = array[0] 127 | let screenHeight = array[1] 128 | let x = [0, screenWidth/2, screenWidth/2, 10, screenWidth-10][UIApplication.shared.statusBarOrientation.hashValue] as CGFloat 129 | let y = [0, 10, screenHeight-10, screenHeight/2, screenHeight/2][UIApplication.shared.statusBarOrientation.hashValue] as CGFloat 130 | window.center = CGPoint(x: x, y: y) 131 | 132 | // change direction 133 | window.transform = CGAffineTransform(rotationAngle: CGFloat(degree * Double.pi / 180)) 134 | } 135 | 136 | window.windowLevel = UIWindowLevelStatusBar 137 | window.isHidden = false 138 | window.addSubview(view) 139 | windows.append(window) 140 | 141 | var origPoint = view.frame.origin 142 | origPoint.y = -(view.frame.size.height) 143 | let destPoint = view.frame.origin 144 | view.tag = sn_topBar 145 | 146 | view.frame = CGRect(origin: origPoint, size: view.frame.size) 147 | UIView.animate(withDuration: 0.3, animations: { 148 | view.frame = CGRect(origin: destPoint, size: view.frame.size) 149 | }, completion: { b in 150 | if autoClear { 151 | let selector = #selector(SwiftNotice.hideNotice(_:)) 152 | self.perform(selector, with: window, afterDelay: TimeInterval(autoClearTime)) 153 | } 154 | }) 155 | return window 156 | } 157 | 158 | @discardableResult 159 | static func wait(_ imageNames: Array = Array(), timeInterval: Int = 0) -> UIWindow { 160 | let frame = CGRect(x: 0, y: 0, width: 78, height: 78) 161 | let window = UIWindow() 162 | window.backgroundColor = UIColor.clear 163 | let mainView = UIView() 164 | mainView.layer.cornerRadius = 12 165 | mainView.backgroundColor = UIColor(red:0, green:0, blue:0, alpha: 0.8) 166 | 167 | if imageNames.count > 0 { 168 | if imageNames.count > timerTimes { 169 | let iv = UIImageView(frame: frame) 170 | iv.image = imageNames.first! 171 | iv.contentMode = UIViewContentMode.scaleAspectFit 172 | mainView.addSubview(iv) 173 | timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: UInt(0)), queue: DispatchQueue.main) as! DispatchSource 174 | timer.scheduleRepeating(deadline: DispatchTime.now(), interval: DispatchTimeInterval.milliseconds(timeInterval)) 175 | timer.setEventHandler(handler: { () -> Void in 176 | let name = imageNames[timerTimes % imageNames.count] 177 | iv.image = name 178 | timerTimes += 1 179 | }) 180 | timer.resume() 181 | } 182 | } else { 183 | let ai = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.whiteLarge) 184 | ai.frame = CGRect(x: 21, y: 21, width: 36, height: 36) 185 | ai.startAnimating() 186 | mainView.addSubview(ai) 187 | } 188 | 189 | window.frame = frame 190 | mainView.frame = frame 191 | window.center = rv!.center 192 | 193 | if let version = Double(UIDevice.current.systemVersion), 194 | version < 9.0 { 195 | // change center 196 | window.center = getRealCenter() 197 | // change direction 198 | window.transform = CGAffineTransform(rotationAngle: CGFloat(degree * Double.pi / 180)) 199 | } 200 | 201 | window.windowLevel = UIWindowLevelAlert 202 | window.isHidden = false 203 | window.addSubview(mainView) 204 | windows.append(window) 205 | 206 | mainView.alpha = 0.0 207 | UIView.animate(withDuration: 0.2, animations: { 208 | mainView.alpha = 1 209 | }) 210 | return window 211 | } 212 | 213 | @discardableResult 214 | static func showText(_ text: String, autoClear: Bool=true, autoClearTime: Int=2) -> UIWindow { 215 | let window = UIWindow() 216 | window.backgroundColor = UIColor.clear 217 | let mainView = UIView() 218 | mainView.layer.cornerRadius = 12 219 | mainView.backgroundColor = UIColor(red:0, green:0, blue:0, alpha: 0.8) 220 | 221 | let label = UILabel() 222 | label.text = text 223 | label.numberOfLines = 0 224 | label.font = UIFont.systemFont(ofSize: 13) 225 | label.textAlignment = NSTextAlignment.center 226 | label.textColor = UIColor.white 227 | let size = label.sizeThatFits(CGSize(width: UIScreen.main.bounds.width-82, height: CGFloat.greatestFiniteMagnitude)) 228 | label.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height) 229 | mainView.addSubview(label) 230 | 231 | let superFrame = CGRect(x: 0, y: 0, width: label.frame.width + 50 , height: label.frame.height + 30) 232 | window.frame = superFrame 233 | mainView.frame = superFrame 234 | 235 | label.center = mainView.center 236 | window.center = rv!.center 237 | 238 | if let version = Double(UIDevice.current.systemVersion), 239 | version < 9.0 { 240 | // change center 241 | window.center = getRealCenter() 242 | // change direction 243 | window.transform = CGAffineTransform(rotationAngle: CGFloat(degree * Double.pi / 180)) 244 | } 245 | 246 | window.windowLevel = UIWindowLevelAlert 247 | window.isHidden = false 248 | window.addSubview(mainView) 249 | windows.append(window) 250 | 251 | if autoClear { 252 | let selector = #selector(SwiftNotice.hideNotice(_:)) 253 | self.perform(selector, with: window, afterDelay: TimeInterval(autoClearTime)) 254 | } 255 | return window 256 | } 257 | 258 | @discardableResult 259 | static func showNoticeWithText(_ type: NoticeType,text: String, autoClear: Bool, autoClearTime: Int) -> UIWindow { 260 | let frame = CGRect(x: 0, y: 0, width: 90, height: 90) 261 | let window = UIWindow() 262 | window.backgroundColor = UIColor.clear 263 | let mainView = UIView() 264 | mainView.layer.cornerRadius = 10 265 | mainView.backgroundColor = UIColor(red:0, green:0, blue:0, alpha: 0.7) 266 | 267 | var image = UIImage() 268 | switch type { 269 | case .success: 270 | image = SwiftNoticeSDK.imageOfCheckmark 271 | case .error: 272 | image = SwiftNoticeSDK.imageOfCross 273 | case .info: 274 | image = SwiftNoticeSDK.imageOfInfo 275 | } 276 | let checkmarkView = UIImageView(image: image) 277 | checkmarkView.frame = CGRect(x: 27, y: 15, width: 36, height: 36) 278 | mainView.addSubview(checkmarkView) 279 | 280 | let label = UILabel(frame: CGRect(x: 0, y: 60, width: 90, height: 16)) 281 | label.font = UIFont.systemFont(ofSize: 13) 282 | label.textColor = UIColor.white 283 | label.text = text 284 | label.textAlignment = NSTextAlignment.center 285 | mainView.addSubview(label) 286 | 287 | window.frame = frame 288 | mainView.frame = frame 289 | window.center = rv!.center 290 | 291 | if let version = Double(UIDevice.current.systemVersion), 292 | version < 9.0 { 293 | // change center 294 | window.center = getRealCenter() 295 | // change direction 296 | window.transform = CGAffineTransform(rotationAngle: CGFloat(degree * Double.pi / 180)) 297 | } 298 | 299 | window.windowLevel = UIWindowLevelAlert 300 | window.center = rv!.center 301 | window.isHidden = false 302 | window.addSubview(mainView) 303 | windows.append(window) 304 | 305 | mainView.alpha = 0.0 306 | UIView.animate(withDuration: 0.2, animations: { 307 | mainView.alpha = 1 308 | }) 309 | 310 | if autoClear { 311 | let selector = #selector(SwiftNotice.hideNotice(_:)) 312 | self.perform(selector, with: window, afterDelay: TimeInterval(autoClearTime)) 313 | } 314 | return window 315 | } 316 | 317 | // fix https://github.com/johnlui/SwiftNotice/issues/2 318 | static func hideNotice(_ sender: AnyObject) { 319 | if let window = sender as? UIWindow { 320 | 321 | if let v = window.subviews.first { 322 | UIView.animate(withDuration: 0.2, animations: { 323 | 324 | if v.tag == sn_topBar { 325 | v.frame = CGRect(x: 0, y: -v.frame.height, width: v.frame.width, height: v.frame.height) 326 | } 327 | v.alpha = 0 328 | }, completion: { b in 329 | 330 | if let index = windows.index(where: { (item) -> Bool in 331 | return item == window 332 | }) { 333 | windows.remove(at: index) 334 | } 335 | }) 336 | } 337 | 338 | } 339 | } 340 | 341 | // just for iOS 8 342 | static func getRealCenter() -> CGPoint { 343 | if UIApplication.shared.statusBarOrientation.hashValue >= 3 { 344 | return CGPoint(x: rv!.center.y, y: rv!.center.x) 345 | } else { 346 | return rv!.center 347 | } 348 | } 349 | } 350 | 351 | class SwiftNoticeSDK { 352 | struct Cache { 353 | static var imageOfCheckmark: UIImage? 354 | static var imageOfCross: UIImage? 355 | static var imageOfInfo: UIImage? 356 | } 357 | class func draw(_ type: NoticeType) { 358 | let checkmarkShapePath = UIBezierPath() 359 | 360 | // draw circle 361 | checkmarkShapePath.move(to: CGPoint(x: 36, y: 18)) 362 | checkmarkShapePath.addArc(withCenter: CGPoint(x: 18, y: 18), radius: 17.5, startAngle: 0, endAngle: CGFloat(Double.pi*2), clockwise: true) 363 | checkmarkShapePath.close() 364 | 365 | switch type { 366 | case .success: // draw checkmark 367 | checkmarkShapePath.move(to: CGPoint(x: 10, y: 18)) 368 | checkmarkShapePath.addLine(to: CGPoint(x: 16, y: 24)) 369 | checkmarkShapePath.addLine(to: CGPoint(x: 27, y: 13)) 370 | checkmarkShapePath.move(to: CGPoint(x: 10, y: 18)) 371 | checkmarkShapePath.close() 372 | case .error: // draw X 373 | checkmarkShapePath.move(to: CGPoint(x: 10, y: 10)) 374 | checkmarkShapePath.addLine(to: CGPoint(x: 26, y: 26)) 375 | checkmarkShapePath.move(to: CGPoint(x: 10, y: 26)) 376 | checkmarkShapePath.addLine(to: CGPoint(x: 26, y: 10)) 377 | checkmarkShapePath.move(to: CGPoint(x: 10, y: 10)) 378 | checkmarkShapePath.close() 379 | case .info: 380 | checkmarkShapePath.move(to: CGPoint(x: 18, y: 6)) 381 | checkmarkShapePath.addLine(to: CGPoint(x: 18, y: 22)) 382 | checkmarkShapePath.move(to: CGPoint(x: 18, y: 6)) 383 | checkmarkShapePath.close() 384 | 385 | UIColor.white.setStroke() 386 | checkmarkShapePath.stroke() 387 | 388 | let checkmarkShapePath = UIBezierPath() 389 | checkmarkShapePath.move(to: CGPoint(x: 18, y: 27)) 390 | checkmarkShapePath.addArc(withCenter: CGPoint(x: 18, y: 27), radius: 1, startAngle: 0, endAngle: CGFloat(Double.pi*2), clockwise: true) 391 | checkmarkShapePath.close() 392 | 393 | UIColor.white.setFill() 394 | checkmarkShapePath.fill() 395 | } 396 | 397 | UIColor.white.setStroke() 398 | checkmarkShapePath.stroke() 399 | } 400 | class var imageOfCheckmark: UIImage { 401 | if (Cache.imageOfCheckmark != nil) { 402 | return Cache.imageOfCheckmark! 403 | } 404 | UIGraphicsBeginImageContextWithOptions(CGSize(width: 36, height: 36), false, 0) 405 | 406 | SwiftNoticeSDK.draw(NoticeType.success) 407 | 408 | Cache.imageOfCheckmark = UIGraphicsGetImageFromCurrentImageContext() 409 | UIGraphicsEndImageContext() 410 | return Cache.imageOfCheckmark! 411 | } 412 | class var imageOfCross: UIImage { 413 | if (Cache.imageOfCross != nil) { 414 | return Cache.imageOfCross! 415 | } 416 | UIGraphicsBeginImageContextWithOptions(CGSize(width: 36, height: 36), false, 0) 417 | 418 | SwiftNoticeSDK.draw(NoticeType.error) 419 | 420 | Cache.imageOfCross = UIGraphicsGetImageFromCurrentImageContext() 421 | UIGraphicsEndImageContext() 422 | return Cache.imageOfCross! 423 | } 424 | class var imageOfInfo: UIImage { 425 | if (Cache.imageOfInfo != nil) { 426 | return Cache.imageOfInfo! 427 | } 428 | UIGraphicsBeginImageContextWithOptions(CGSize(width: 36, height: 36), false, 0) 429 | 430 | SwiftNoticeSDK.draw(NoticeType.info) 431 | 432 | Cache.imageOfInfo = UIGraphicsGetImageFromCurrentImageContext() 433 | UIGraphicsEndImageContext() 434 | return Cache.imageOfInfo! 435 | } 436 | } 437 | 438 | extension UIWindow{ 439 | func hide(){ 440 | SwiftNotice.hideNotice(self) 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /PHPHub/Controller/Fornum/ForumViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForumViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/9. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PageMenu 11 | 12 | class ForumViewController: UIViewController { 13 | 14 | var pageMenu: CAPSPageMenu? 15 | var controllerArray: [UIViewController] = [] 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | // hide the navigationBar first 21 | navigationController?.setNavigationBarHidden(true, animated: true) 22 | 23 | // Customize menu (Optional) 24 | let parameters: [CAPSPageMenuOption] = [ 25 | .scrollMenuBackgroundColor(UIColor.white), 26 | .viewBackgroundColor(UIColor(red: 240/255.0, green: 240.0/255.0, blue: 240.0/255.0, alpha: 1.0)), 27 | .selectionIndicatorColor(UIColor(red: 18.0/255.0, green: 150.0/255.0, blue: 225.0/255.0, alpha: 1.0)), 28 | .bottomMenuHairlineColor(UIColor(red: 20.0/255.0, green: 20.0/255.0, blue: 20.0/255.0, alpha: 0.1)), 29 | .selectedMenuItemLabelColor(UIColor(red: 18.0/255.0, green: 150.0/255.0, blue: 225.0/255.0, alpha: 1.0)), 30 | .unselectedMenuItemLabelColor(UIColor(red: 40.0/255.0, green: 40.0/255.0, blue: 40.0/255.0, alpha: 1.0)), 31 | .menuItemFont(UIFont(name: "HelveticaNeue", size: 13.0)!), 32 | .menuHeight(44.0), 33 | .menuItemWidth(90.0), 34 | .centerMenuItems(true) 35 | ] 36 | 37 | let newestVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TopicList") as! TopicListTableViewController 38 | newestVC.title = "最新" 39 | newestVC.topicListApi = .newest 40 | let hotestVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TopicList") as! TopicListTableViewController 41 | hotestVC.title = "热门" 42 | hotestVC.topicListApi = .hotest 43 | let jobVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TopicList") as! TopicListTableViewController 44 | jobVC.title = "招聘" 45 | jobVC.topicListApi = .jobs 46 | 47 | 48 | controllerArray = [newestVC, hotestVC, jobVC] 49 | 50 | // Configure the scroll menu 51 | pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: CGRect(x: 0.0, y: 20.0, width: self.view.frame.width, height: self.view.frame.height - 20.0), pageMenuOptions: parameters) 52 | pageMenu!.delegate = self 53 | 54 | self.addChildViewController(pageMenu!) 55 | self.view.addSubview(pageMenu!.view) 56 | } 57 | 58 | override func viewWillAppear(_ animated: Bool) { 59 | // hide navigation bar 60 | navigationController?.setNavigationBarHidden(true, animated: animated) 61 | 62 | super.viewWillAppear(animated) 63 | } 64 | 65 | override func viewWillDisappear(_ animated: Bool) { 66 | navigationController?.setNavigationBarHidden(false, animated: animated) 67 | 68 | super.viewWillDisappear(animated) 69 | } 70 | } 71 | 72 | extension ForumViewController: CAPSPageMenuDelegate { 73 | func willMoveToPage(_ controller: UIViewController, index: Int) { 74 | return 75 | } 76 | 77 | func didMoveToPage(_ controller: UIViewController, index: Int) { 78 | return 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /PHPHub/Controller/MainTabBar/MainTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabBarController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/10. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MainTabBarController: UITabBarController { 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | self.setupTabBarItems() 16 | self.delegate = self 17 | } 18 | 19 | fileprivate func setupTabBarItems() { 20 | let essentialNC = self.viewControllers![0] as! UINavigationController 21 | let essentialVC = essentialNC.topViewController! as! TopicListTableViewController 22 | essentialVC.title = "精华" 23 | essentialVC.topicListApi = .essential 24 | 25 | let wikiNC = self.viewControllers![2] as! UINavigationController 26 | let wikiVC = wikiNC.topViewController! as! TopicListTableViewController 27 | wikiVC.title = "社区WIKI" 28 | wikiVC.topicListApi = .wiki 29 | } 30 | } 31 | 32 | extension MainTabBarController: UITabBarControllerDelegate { 33 | func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { 34 | guard !CurrentUserHandler.defaultHandler.isLoggedIn else { 35 | return true 36 | } 37 | 38 | let visibleController: UIViewController 39 | 40 | if let navigationController = viewController as? UINavigationController { 41 | visibleController = navigationController.topViewController ?? viewController 42 | } else { 43 | visibleController = viewController 44 | } 45 | 46 | let shouldPresentSignInScreen = AppConfig.viewControllerClassesThatRequireLogin.contains { $0 == type(of: visibleController) } 47 | 48 | if shouldPresentSignInScreen { 49 | LoginViewController.presentLoginViewController() { success in 50 | if success { 51 | CurrentUserHandler.defaultHandler.refreshUserInfo() 52 | self.selectedViewController = viewController 53 | } 54 | } 55 | 56 | return false 57 | } 58 | 59 | return true 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /PHPHub/Controller/Me/EditUserProfileViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditUserProfileViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 16/2/4. 6 | // Copyright © 2016年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EditUserProfileViewController: UITableViewController { 12 | 13 | @IBOutlet weak var realNameTextFiled: UITextField! 14 | @IBOutlet weak var cityTextField: UITextField! 15 | @IBOutlet weak var twitterTextField: UITextField! 16 | @IBOutlet weak var githubTextField: UITextField! 17 | @IBOutlet weak var blogTextField: UITextField! 18 | @IBOutlet weak var userIntroTextView: UITextView! 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | self.loadCurrentUserInfo(); 24 | } 25 | 26 | @IBAction func updateUserInfo(_ sender: UIBarButtonItem) { 27 | updateCurrentUserInfo() 28 | navigationController?.popViewController(animated: true) 29 | } 30 | 31 | fileprivate func loadCurrentUserInfo() { 32 | if let user = CurrentUserHandler.defaultHandler.user { 33 | realNameTextFiled.text = user.realName 34 | cityTextField.text = user.city 35 | twitterTextField.text = user.twitterAccount 36 | githubTextField.text = user.githubName 37 | blogTextField.text = user.blogURL 38 | userIntroTextView.text = user.signature 39 | } 40 | } 41 | 42 | fileprivate func updateCurrentUserInfo() { 43 | let parameters = [ 44 | "real_name": realNameTextFiled.text!, 45 | "city": cityTextField.text!, 46 | "twitter_account": twitterTextField.text!, 47 | "github_url": githubTextField.text!, 48 | "personal_website": blogTextField.text!, 49 | "signature": userIntroTextView.text! 50 | ] 51 | 52 | self.pleaseWait() 53 | CurrentUserHandler.defaultHandler.updateUserInfo(parameters) { user in 54 | self.clearAllNotice() 55 | self.loadCurrentUserInfo() 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PHPHub/Controller/Me/LoginGuideViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginGuideViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/23. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class LoginGuideViewController: UIViewController { 13 | var webView: WKWebView? 14 | var guideUrl: String? { 15 | didSet { 16 | loadURLContent() 17 | } 18 | } 19 | 20 | override func loadView() { 21 | super.loadView() 22 | 23 | webView = WKWebView() 24 | view = webView 25 | } 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | navigationItem.title = "登陆说明" 31 | loadURLContent() 32 | } 33 | 34 | fileprivate func loadURLContent() { 35 | if let stringURL = guideUrl, let webView = webView { 36 | let url = URL(string: stringURL)! 37 | webView.load(URLRequest(url: url)) 38 | webView.allowsBackForwardNavigationGestures = true 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PHPHub/Controller/Me/LoginViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/23. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LoginViewController: UIViewController { 12 | static let storyboardIdentifier = "login" 13 | 14 | fileprivate var completion: ((_ success: Bool) -> Void)? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | // navigationItem.title = "请登录" 20 | } 21 | 22 | @IBAction func scanLogin() { 23 | let scannerVC = QRCodeViewController() 24 | scannerVC.completion = self.completion 25 | navigationController?.pushViewController(scannerVC, animated: true) 26 | } 27 | 28 | @IBAction func introduceLogin() { 29 | let loginGuideVC = LoginGuideViewController() 30 | loginGuideVC.guideUrl = AppConfig.LoginGuide 31 | loginGuideVC.hidesBottomBarWhenPushed = true 32 | navigationController?.pushViewController(loginGuideVC, animated: false) 33 | } 34 | 35 | @IBAction func closeLoginViewController() { 36 | appDelegate.tabBarController.dismiss(animated: true) { } 37 | } 38 | 39 | static func presentLoginViewController(withCompletion completion: @escaping ((Bool) -> Void)) { 40 | let loginNC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginNC") as! UINavigationController 41 | let loginVC = loginNC.topViewController! as! LoginViewController 42 | loginVC.completion = completion 43 | 44 | // Customize the login view controller presentation and transition styles. 45 | loginNC.modalPresentationStyle = .overCurrentContext 46 | loginNC.modalTransitionStyle = .crossDissolve 47 | 48 | // Present the login view controller. 49 | appDelegate.tabBarController.present(loginNC, animated: true, completion: nil) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PHPHub/Controller/Me/MeTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MeTableViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/23. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Kingfisher 11 | 12 | class MeTableViewController: UITableViewController { 13 | 14 | // MARK: - Properties 15 | @IBOutlet weak var avatarImageView: UIImageView! 16 | @IBOutlet weak var usernameLabel: UILabel! 17 | @IBOutlet weak var userIntroLabel: UILabel! 18 | @IBOutlet weak var unreadCountLabel: UILabel! 19 | 20 | var user: User? { 21 | didSet { 22 | if let user = user { 23 | self.avatarImageView.kf.setImage( 24 | with: URL(string: user.avatar)!, 25 | placeholder: UIImage(named: "avatar_placeholder")) 26 | self.usernameLabel.text = user.username 27 | self.userIntroLabel.text = user.signature 28 | } 29 | } 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | setupCornerView(unreadCountLabel) 36 | setupCornerView(avatarImageView) 37 | 38 | self.user = CurrentUserHandler.defaultHandler.user 39 | } 40 | 41 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 42 | // let section = indexPath.section 43 | // let row = indexPath.row 44 | // 45 | // var nextVC: UIViewController 46 | // 47 | // if section == 0 && row == 0 { 48 | // 49 | // } else if section == 1 { 50 | // 51 | // } else if section == 2 { 52 | // 53 | // } 54 | } 55 | 56 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 57 | if let identifier = segue.identifier { 58 | switch identifier { 59 | case "ShowUserProfile": 60 | let userProfileVC = segue.destination as! UserProfileViewController 61 | userProfileVC.user = self.user 62 | userProfileVC.title = "个人信息" 63 | userProfileVC.hidesBottomBarWhenPushed = true 64 | case "ShowAttention": 65 | let attentionVC = segue.destination as! TopicListTableViewController 66 | if let user = CurrentUserHandler.defaultHandler.user { 67 | attentionVC.topicListApi = .attention(user.userId) 68 | attentionVC.title = "我的关注" 69 | attentionVC.hidesBottomBarWhenPushed = true 70 | } 71 | case "ShowFavorite": 72 | let favoriteVC = segue.destination as! TopicListTableViewController 73 | if let user = CurrentUserHandler.defaultHandler.user { 74 | favoriteVC.topicListApi = .favorite(user.userId) 75 | favoriteVC.title = "我的收藏" 76 | favoriteVC.hidesBottomBarWhenPushed = true 77 | } 78 | case "ShowUser": 79 | let userPublicedVC = segue.destination as! TopicListTableViewController 80 | if let user = CurrentUserHandler.defaultHandler.user { 81 | userPublicedVC.topicListApi = .user(user.userId) 82 | userPublicedVC.title = "我的帖子" 83 | userPublicedVC.hidesBottomBarWhenPushed = true 84 | } 85 | default: 86 | break 87 | } 88 | } 89 | } 90 | 91 | fileprivate func setupCornerView(_ view: UIView) { 92 | view.layer.cornerRadius = view.frame.height / 2 93 | view.layer.masksToBounds = true 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /PHPHub/Controller/Me/QRCodeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QRCodeViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/24. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftQRCode 11 | 12 | class QRCodeViewController: UIViewController { 13 | lazy var scanner = QRCode() 14 | var completion: ((_ success: Bool) -> Void)? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | scanner.prepareScan(view) { stringValue in 20 | let loginInfo = stringValue.characters.split{$0 == ","}.map{String($0)} 21 | 22 | if loginInfo.count == 2 { 23 | CurrentUserHandler.defaultHandler.login(username: loginInfo[0], loginToken: loginInfo[1], callback: self.completion!) 24 | debugPrint("QRCodeViewController") 25 | } else { 26 | self.noticeInfo("扫描的二维码不能识别呢", autoClear: true) 27 | } 28 | 29 | self.scanner.stopScan() 30 | self.navigationController?.popViewController(animated: true) 31 | } 32 | scanner.scanFrame = view.bounds 33 | } 34 | 35 | override func viewDidAppear(_ animated: Bool) { 36 | super.viewDidAppear(animated) 37 | scanner.startScan() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /PHPHub/Controller/Me/UserProfileViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserProfileViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 16/2/3. 6 | // Copyright © 2016年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SafariServices 11 | 12 | class UserProfileViewController: UITableViewController { 13 | 14 | @IBOutlet weak var avatarImageView: UIImageView! 15 | @IBOutlet weak var usernameLabel: UILabel! 16 | @IBOutlet weak var realnameLabel: UILabel! 17 | @IBOutlet weak var userIntroLabel: UILabel! 18 | @IBOutlet weak var localLabel: UILabel! 19 | @IBOutlet weak var githubLabel: UILabel! 20 | @IBOutlet weak var twitterLabel: UILabel! 21 | @IBOutlet weak var blogLabel: UILabel! 22 | @IBOutlet weak var createAtLabel: UILabel! 23 | 24 | @IBOutlet weak var topicCountLabel: UILabel! 25 | @IBOutlet weak var repliesCountLablel: UILabel! 26 | 27 | 28 | var user: User? 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | if let user = self.user { 34 | self.avatarImageView.kf.setImage( 35 | with: URL(string: user.avatar)!, 36 | placeholder: UIImage(named: "avatar_placeholder")) 37 | self.usernameLabel.text = user.username 38 | self.realnameLabel.text = user.realName 39 | self.userIntroLabel.text = user.signature 40 | self.localLabel.text = user.city 41 | self.githubLabel.text = user.githubName 42 | self.twitterLabel.text = user.twitterAccount 43 | self.blogLabel.text = user.blogURL 44 | self.createAtLabel.text = user.createdAtDate 45 | 46 | self.topicCountLabel.text = String(user.topicCount) 47 | self.repliesCountLablel.text = String(user.replyCount) 48 | 49 | if let currentUser = CurrentUserHandler.defaultHandler.user { 50 | if currentUser.userId == user.userId { 51 | let editProfileImage = UIImage(named: "edit_profile_icon") 52 | let rightBarButtonItem = UIBarButtonItem(image: editProfileImage, style: .plain, target: self, action: Selector("showEditUserProfile")) 53 | navigationController?.navigationItem.rightBarButtonItem = rightBarButtonItem 54 | } 55 | } 56 | } 57 | 58 | setupCornerView(self.avatarImageView) 59 | } 60 | 61 | // MARK: - Delegate TableView 62 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 63 | if let user = self.user { 64 | let section = (indexPath as NSIndexPath).section 65 | 66 | switch section { 67 | case 3 where !user.githubURL.isEmpty : 68 | jumpToWebView(user.githubURL) 69 | case 4 where !user.twitterAccount.isEmpty: 70 | jumpToWebView(AppConfig.TwitterUrl + user.twitterAccount) 71 | case 5 where !user.blogURL.isEmpty : 72 | jumpToWebView(user.blogURL) 73 | default: 74 | return 75 | } 76 | } 77 | } 78 | 79 | // MARK: - Navigation 80 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 81 | if let identifier = segue.identifier, let user = self.user { 82 | switch identifier { 83 | case "ShowAttention": 84 | let attentionVC = segue.destination as! TopicListTableViewController 85 | attentionVC.topicListApi = .attention(user.userId) 86 | attentionVC.hidesBottomBarWhenPushed = true 87 | 88 | if let currentUser = CurrentUserHandler.defaultHandler.user , currentUser.userId == user.userId { 89 | attentionVC.title = "我的关注" 90 | } else { 91 | attentionVC.title = "TA的关注" 92 | } 93 | case "ShowFavorite": 94 | let favoriteVC = segue.destination as! TopicListTableViewController 95 | favoriteVC.topicListApi = .favorite(user.userId) 96 | favoriteVC.hidesBottomBarWhenPushed = true 97 | 98 | if let currentUser = CurrentUserHandler.defaultHandler.user , currentUser.userId == user.userId { 99 | favoriteVC.title = "我的收藏" 100 | } else { 101 | favoriteVC.title = "TA的收藏" 102 | } 103 | case "ShowUser": 104 | let userPublicedVC = segue.destination as! TopicListTableViewController 105 | userPublicedVC.topicListApi = .user(user.userId) 106 | userPublicedVC.hidesBottomBarWhenPushed = true 107 | 108 | if let currentUser = CurrentUserHandler.defaultHandler.user , currentUser.userId == user.userId { 109 | userPublicedVC.title = "我的帖子" 110 | } else { 111 | userPublicedVC.title = "TA的帖子" 112 | } 113 | default: 114 | break 115 | } 116 | } 117 | 118 | } 119 | 120 | func jumpToWebView(_ urlString: String) { 121 | let safariViewController = SFSafariViewController(url: URL(string: urlString)!) 122 | self.present(safariViewController, animated: true, completion: nil) 123 | } 124 | 125 | fileprivate func setupCornerView(_ view: UIView) { 126 | view.layer.cornerRadius = view.frame.height / 2 127 | view.layer.masksToBounds = true 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /PHPHub/Controller/Topic/CommentViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommentViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/23. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class CommentViewController: UIViewController { 13 | var webView: WKWebView? 14 | var topic: Topic? 15 | 16 | override func loadView() { 17 | super.loadView() 18 | 19 | webView = WKWebView() 20 | view = webView 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | navigationItem.title = "评论列表" 27 | 28 | if let topic = topic, let webView = webView { 29 | let request = Router.topicReplies(topic.topicId).URLRequest 30 | webView.load(request as URLRequest) 31 | webView.allowsBackForwardNavigationGestures = true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /PHPHub/Controller/Topic/TopicListTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicListTableViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/9. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Kingfisher 11 | 12 | class TopicListTableViewController: UITableViewController { 13 | var topicList = [Topic]() { 14 | didSet { 15 | tableView.reloadData() 16 | } 17 | } 18 | 19 | var atPage = 1 20 | var topicListApi: TopicListApi? = nil { 21 | didSet { 22 | if let topicListApi = self.topicListApi { 23 | self.atPage = 1 24 | topicListApi.getTopicListAtPage(self.atPage) { topicList in 25 | self.topicList = topicList 26 | self.atPage += 1 27 | } 28 | } 29 | } 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | self.clearsSelectionOnViewWillAppear = true 36 | 37 | // pull to refresh 38 | refreshControl?.addTarget(self, action: #selector(TopicListTableViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged) 39 | } 40 | 41 | func handleRefresh(_ refreshControl: UIRefreshControl) { 42 | if let topicListApi = self.topicListApi { 43 | self.atPage = 1 44 | topicListApi.getTopicListAtPage(self.atPage) { topicList in 45 | self.topicList = topicList 46 | self.atPage += 1 47 | refreshControl.endRefreshing() 48 | } 49 | } 50 | } 51 | 52 | func handleLoadMore() { 53 | if let topicListApi = self.topicListApi { 54 | topicListApi.getTopicListAtPage(self.atPage) { topicList in 55 | self.topicList += topicList 56 | self.atPage += 1 57 | } 58 | } 59 | } 60 | 61 | // MARK: - Table view data source 62 | override func numberOfSections(in tableView: UITableView) -> Int { 63 | return 1 64 | } 65 | 66 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 67 | return topicList.count 68 | } 69 | 70 | 71 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 72 | let cellIdentifier = "TopicListCell" 73 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! TopicListTableViewCell 74 | 75 | let topic = topicList[(indexPath as NSIndexPath).row] 76 | cell.topicTitleLabel.text = topic.topicTitle 77 | cell.topicInfoLabel.text = "\(topic.node.nodeName) • 最后由 \(topic.lastReplyUser.username) • \(topic.updateAt.timeAgoSinceNow())" 78 | cell.topicRepliesCountLabel.text = String(topic.topicRepliesCount) 79 | cell.avatarImageView.kf.setImage(with: URL(string: topic.user.avatar)!, placeholder: UIImage(named: "avatar_placeholder")) 80 | 81 | if (indexPath as NSIndexPath).row == topicList.count - 5 { 82 | handleLoadMore() 83 | } 84 | 85 | return cell 86 | } 87 | 88 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 89 | return false 90 | } 91 | 92 | // MARK: - Navigation 93 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 94 | if segue.identifier == "TopicDetail" { 95 | let topicDetail = segue.destination as! TopicDetailViewController 96 | 97 | if let selectedTopicCell = sender as? TopicListTableViewCell { 98 | let indexPath = tableView.indexPath(for: selectedTopicCell)! 99 | let selectedTopic = topicList[(indexPath as NSIndexPath).row] 100 | topicDetail.topic = selectedTopic 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /PHPHub/Handlers/AccessTokenHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AccessTokenHandler.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/14. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import KeychainAccess 10 | import SwiftyJSON 11 | 12 | class AccessTokenHandler { 13 | let keychain = Keychain(service: AppConfig.KeyChainService) 14 | 15 | func getLocalClientAccessToken() -> String? { 16 | return keychain[AppConfig.KeyChainClientAccount] 17 | } 18 | 19 | func getServerClientAccessToken() { 20 | AuthorizeApi.getClientAccessToken { json in 21 | self.storeClientAccessToken(json["access_token"].stringValue) 22 | } 23 | } 24 | 25 | func getLocalLoginAccessToken() -> String? { 26 | return keychain[AppConfig.KeyChainLoginAccount] 27 | } 28 | 29 | func getServerLoginAccessToken(_ username: String, loginToken: String) { 30 | AuthorizeApi.getLoginAccessToken(username: username, loginToken: loginToken) { json in 31 | self.storeLoginAccessToken(json["access_token"].stringValue) 32 | } 33 | } 34 | 35 | func storeClientAccessToken(_ accessToken: String) { 36 | keychain[AppConfig.KeyChainClientAccount] = accessToken 37 | } 38 | 39 | func storeLoginAccessToken(_ accessToken: String) { 40 | keychain[AppConfig.KeyChainLoginAccount] = accessToken 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /PHPHub/Handlers/ApiHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApiHandler.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/15. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import SwiftyJSON 11 | 12 | class ApiHandler { 13 | static let sharedInstance = ApiHandler() 14 | 15 | fileprivate let manager: Alamofire.SessionManager 16 | 17 | fileprivate init() { 18 | let configures = URLSessionConfiguration.default 19 | configures.timeoutIntervalForRequest = Double(AppConfig.Api.TimeoutIntervals) 20 | 21 | manager = Alamofire.SessionManager(configuration: configures) 22 | } 23 | 24 | func StringRequest(_ URLRequest: URLRequestConvertible, callback: @escaping (String) -> Void) { 25 | manager.request(URLRequest) 26 | .validate(statusCode: 200..<300) 27 | .responseString { response in 28 | switch response.result { 29 | case .success(let value): 30 | callback(value) 31 | case .failure(let error): 32 | debugPrint(error) 33 | 34 | if let URLResponse = response.response { 35 | if URLResponse.statusCode == 401 { 36 | self.regainClientAccessToken() 37 | self.StringRequest(URLRequest, callback: callback) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | func SwiftyJSONRequest(_ URLRequest: URLRequestConvertible, callback: @escaping (JSON) -> Void) { 45 | manager.request(URLRequest) 46 | .validate(statusCode: 200..<300) 47 | .responseSwiftyJSON { response in 48 | switch response.result { 49 | case .success(let value): 50 | callback(value) 51 | case .failure(let error): 52 | debugPrint(error) 53 | 54 | if let URLResponse = response.response { 55 | if URLResponse.statusCode == 401 { 56 | self.regainClientAccessToken() 57 | self.SwiftyJSONRequest(URLRequest, callback: callback) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | func CollectionRequest( 65 | _ URLRequest: URLRequestConvertible, 66 | callback: @escaping ([T]) -> Void, 67 | failure: @escaping (NSError) -> Void = { debugPrint($0) }) 68 | { 69 | manager.request(URLRequest) 70 | .validate(statusCode: 200..<300) 71 | .responseCollection { response in 72 | switch response.result { 73 | case .success(let value): 74 | callback(value) 75 | case .failure(let error): 76 | failure(error) 77 | 78 | if let URLResponse = response.response { 79 | if URLResponse.statusCode == 401 { 80 | self.regainClientAccessToken() 81 | self.CollectionRequest(URLRequest, callback: callback, failure: failure) 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | func ObjectRequest( 89 | _ URLRequest: URLRequestConvertible, 90 | callback: @escaping (T) -> Void, 91 | failure: @escaping (NSError) -> Void = { debugPrint($0) }) 92 | { 93 | manager.request(URLRequest) 94 | .validate(statusCode: 200..<300) 95 | .responseObject { response in 96 | switch response.result { 97 | case .success(let value): 98 | callback(value) 99 | case .failure(let error): 100 | debugPrint(error) 101 | failure(error) 102 | } 103 | } 104 | } 105 | 106 | fileprivate func regainClientAccessToken() { 107 | let accessTokenHandler = AccessTokenHandler() 108 | accessTokenHandler.getServerClientAccessToken() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /PHPHub/Handlers/CurrentUserHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentUserHandler.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 16/2/1. 6 | // Copyright © 2016年 ninerec. All rights reserved. 7 | // 8 | 9 | import KeychainAccess 10 | import SwiftyJSON 11 | 12 | class CurrentUserHandler { 13 | static var defaultHandler: CurrentUserHandler! 14 | var user: User? 15 | 16 | fileprivate let keychain = Keychain(service: AppConfig.KeyChainService) 17 | 18 | static func setDefaultHandler(_ defaultHandler: CurrentUserHandler) { 19 | self.defaultHandler = defaultHandler 20 | } 21 | 22 | var isLoggedIn: Bool { 23 | return user != nil; 24 | } 25 | 26 | init () { 27 | guard let _ = keychain[AppConfig.KeyChainLoginAccount] else { 28 | self.user = nil 29 | return 30 | } 31 | 32 | refreshUserInfo() 33 | } 34 | 35 | func login(username: String, loginToken: String, callback: @escaping (Bool) -> Void) { 36 | AuthorizeApi.getLoginAccessToken(username: username, loginToken: loginToken) { json in 37 | debugPrint(json.rawString()) 38 | if let loginToken = json["access_token"].string { 39 | self.keychain[AppConfig.KeyChainLoginAccount] = loginToken 40 | callback(true) 41 | } else { 42 | debugPrint("login => wrong return") 43 | callback(false) 44 | } 45 | } 46 | } 47 | 48 | func logout() { 49 | 50 | } 51 | 52 | func refreshUserInfo() { 53 | UserApi.getCurrentUser{ user in 54 | self.user = user 55 | } 56 | } 57 | 58 | func updateUserInfo(_ parameters: [String: AnyObject], callback: @escaping (User) -> Void) { 59 | if let user = self.user { 60 | UserApi.updateCurrentUser(user.userId, parameters: parameters) { user in 61 | self.user = user 62 | callback(user) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /PHPHub/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIStatusBarTintParameters 39 | 40 | UINavigationBar 41 | 42 | Style 43 | UIBarStyleDefault 44 | Translucent 45 | 46 | 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | 53 | UISupportedInterfaceOrientations~ipad 54 | 55 | UIInterfaceOrientationPortrait 56 | UIInterfaceOrientationPortraitUpsideDown 57 | UIInterfaceOrientationLandscapeLeft 58 | UIInterfaceOrientationLandscapeRight 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /PHPHub/Models/Node.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Node.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/15. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import SwiftyJSON 10 | 11 | final class Node: ResponseObjectSerializable{ 12 | let nodeId: Int 13 | let nodeName: String 14 | let parentNode: Int 15 | 16 | init?(jsonData: JSON) { 17 | nodeId = jsonData["id"].intValue 18 | nodeName = jsonData["name"].stringValue 19 | parentNode = jsonData["parent_node"].intValue 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PHPHub/Models/Topic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Topic.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/15. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import SwiftyJSON 10 | 11 | final class Topic: ResponseObjectSerializable, ResponseCollectionSerializable { 12 | let topicId: Int 13 | let topicTitle: String 14 | // let topicBody: String 15 | let topicRepliesCount: Int 16 | let voteCount: Int 17 | let user: User 18 | let lastReplyUser: User 19 | let node: Node 20 | let topicContentUrl: String 21 | let topicRepliesUrl: String 22 | let updateAt: Date 23 | 24 | init?(jsonData: JSON) { 25 | topicId = jsonData["id"].intValue 26 | topicTitle = jsonData["title"].stringValue 27 | topicRepliesCount = jsonData["reply_count"].intValue 28 | voteCount = jsonData["vote_count"].intValue 29 | topicContentUrl = jsonData["links"]["details_web_view"].stringValue 30 | topicRepliesUrl = jsonData["links"]["replies_web_view"].stringValue 31 | updateAt = Date.convertFromString(jsonData["updated_at"].stringValue)! 32 | 33 | node = Node(jsonData: jsonData["node"]["data"])! 34 | user = User(jsonData: jsonData["user"]["data"])! 35 | lastReplyUser = User(jsonData: jsonData["last_reply_user"]["data"])! 36 | } 37 | 38 | static func collection(jsonData: JSON) -> [Topic] { 39 | var topics: [Topic] = [] 40 | 41 | for (_, subJson) in jsonData { 42 | if let topic = Topic(jsonData: subJson) { 43 | topics.append(topic) 44 | } 45 | } 46 | 47 | return topics 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /PHPHub/Models/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/15. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import SwiftyJSON 10 | 11 | final class User: ResponseObjectSerializable{ 12 | let userId: Int 13 | let githubURL: String 14 | let username: String 15 | let avatar: String 16 | let topicCount: Int 17 | let replyCount: Int 18 | let notificationCount: Int 19 | let twitterAccount: String 20 | let blogURL: String 21 | let company: String 22 | let city: String 23 | let email: String 24 | let signature: String 25 | let githubName: String 26 | let realName: String 27 | let createdAtDate: String 28 | let updatedAtDate: String 29 | 30 | init?(jsonData: JSON) { 31 | userId = jsonData["id"].intValue 32 | username = jsonData["name"].stringValue 33 | avatar = jsonData["avatar"].stringValue 34 | topicCount = jsonData["topic_count"].intValue 35 | replyCount = jsonData["reply_count"].intValue 36 | notificationCount = jsonData["notification_count"].intValue 37 | twitterAccount = jsonData["twitter_account"].stringValue 38 | company = jsonData["company"].stringValue 39 | city = jsonData["city"].stringValue 40 | email = jsonData["emai"].stringValue 41 | signature = jsonData["signature"].stringValue 42 | githubName = jsonData["github_name"].stringValue 43 | githubURL = jsonData["github_url"].stringValue 44 | realName = jsonData["real_name"].stringValue 45 | blogURL = jsonData["personal_website"].stringValue 46 | createdAtDate = jsonData["created_at"].stringValue 47 | updatedAtDate = jsonData["updated_at"].stringValue 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /PHPHub/TopicDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicDetailViewController.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/18. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | import Kingfisher 12 | 13 | class TopicDetailViewController: UIViewController { 14 | @IBOutlet weak var avatarImageView: UIImageView! 15 | @IBOutlet weak var usernameLabel: UILabel! 16 | @IBOutlet weak var signatureLabel: UILabel! 17 | @IBOutlet weak var voteButton: UIButton! 18 | @IBOutlet weak var contentView: UIView! 19 | @IBOutlet weak var authorInfoView: UIView! 20 | 21 | @IBOutlet weak var progressView: UIProgressView! 22 | 23 | @IBOutlet weak var topicToolBarView: UIView! 24 | @IBOutlet weak var commentButton: UIButton! 25 | 26 | var webView: WKWebView! 27 | var topic: Topic? 28 | 29 | deinit { 30 | webView.removeObserver(self, forKeyPath: "estimatedProgress") 31 | } 32 | 33 | override func viewDidLoad() { 34 | super.viewDidLoad() 35 | 36 | webView = WKWebView(frame: contentView.bounds) 37 | contentView.addSubview(webView) 38 | // observe the web loading 39 | webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil) 40 | 41 | // circle the avatar 42 | avatarImageView.layer.cornerRadius = avatarImageView.frame.height / 2 43 | avatarImageView.clipsToBounds = true 44 | 45 | updateTopicDetail() 46 | } 47 | 48 | override func viewDidLayoutSubviews() { 49 | super.viewDidLayoutSubviews() 50 | webView.frame = contentView.bounds 51 | } 52 | 53 | fileprivate func updateTopicDetail() { 54 | if let topic = topic { 55 | let user = topic.user 56 | avatarImageView.kf.setImage(with: URL(string: user.avatar)!, placeholder: UIImage(named: "avatar_placeholder")) 57 | usernameLabel.text = user.username 58 | signatureLabel.text = user.signature 59 | 60 | voteButton.setTitle(" \(topic.voteCount)", for: UIControlState()) 61 | 62 | let request = Router.topicDetails(topic.topicId).URLRequest 63 | webView.load(request as URLRequest) 64 | webView.allowsBackForwardNavigationGestures = true 65 | 66 | commentButton.setTitle(" \(topic.topicRepliesCount)", for: UIControlState()) 67 | } 68 | } 69 | 70 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 71 | if let keyPath = keyPath { 72 | switch keyPath { 73 | case "estimatedProgress": 74 | progressView.setProgress(Float(webView.estimatedProgress), animated: true) 75 | if webView.estimatedProgress == 1 { 76 | progressView.isHidden = true 77 | let jsString = "var metaTag=document.createElement('meta');" + 78 | "metaTag.name='viewport';metaTag.content ='width=device-width,initial-scale=1.0';" + 79 | "document.getElementsByTagName('head')[0].appendChild(metaTag);" 80 | webView.evaluateJavaScript(jsString, completionHandler: nil) 81 | } 82 | case "title": 83 | if let title = webView.title { 84 | self.title = title 85 | } 86 | default: 87 | break 88 | } 89 | } 90 | } 91 | 92 | @IBAction func didTouchCommentButton() { 93 | if let topic = topic { 94 | let commentVC = CommentViewController() 95 | commentVC.topic = topic 96 | navigationController?.pushViewController(commentVC, animated: true) 97 | } 98 | } 99 | } 100 | 101 | extension TopicDetailViewController: WKNavigationDelegate { 102 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 103 | progressView.setProgress(0.0, animated: false) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /PHPHub/Views/Topic/TopicListTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TopicListTableViewCell.swift 3 | // PHPHub 4 | // 5 | // Created by 2014-104 on 15/12/9. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TopicListTableViewCell: UITableViewCell { 12 | 13 | @IBOutlet weak var basicView: UIView! 14 | @IBOutlet weak var topicTitleLabel: UILabel! 15 | @IBOutlet weak var topicInfoLabel: UILabel! 16 | @IBOutlet weak var topicRepliesCountLabel: UILabel! 17 | @IBOutlet weak var avatarImageView: UIImageView! 18 | 19 | override func awakeFromNib() { 20 | super.awakeFromNib() 21 | 22 | // 头像和回复数 23 | avatarImageView.layer.cornerRadius = avatarImageView.frame.height / 2 24 | avatarImageView.clipsToBounds = true 25 | 26 | topicRepliesCountLabel.layer.cornerRadius = topicRepliesCountLabel.frame.height / 2 27 | topicRepliesCountLabel.clipsToBounds = true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /PHPHubTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /PHPHubTests/PHPHubTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PHPHubTests.swift 3 | // PHPHubTests 4 | // 5 | // Created by 2014-104 on 15/12/7. 6 | // Copyright © 2015年 ninerec. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PHPHub 11 | 12 | class PHPHubTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '10.0' 3 | use_frameworks! 4 | 5 | target 'PHPHub' do 6 | pod 'Alamofire', '~> 4.4' 7 | pod 'SwiftyJSON' 8 | pod 'Kingfisher', '~> 3.0' 9 | pod 'KeychainAccess' 10 | pod 'Reachability' 11 | pod 'PageMenu' 12 | pod 'SwiftQRCode' 13 | end 14 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.5.0) 3 | - KeychainAccess (3.0.2) 4 | - Kingfisher (3.10.3) 5 | - PageMenu (2.0.0) 6 | - Reachability (3.2) 7 | - SwiftQRCode (3.0.2) 8 | - SwiftyJSON (3.1.4) 9 | 10 | DEPENDENCIES: 11 | - Alamofire (~> 4.4) 12 | - KeychainAccess 13 | - Kingfisher (~> 3.0) 14 | - PageMenu 15 | - Reachability 16 | - SwiftQRCode 17 | - SwiftyJSON 18 | 19 | SPEC CHECKSUMS: 20 | Alamofire: f28cdffd29de33a7bfa022cbd63ae95a27fae140 21 | KeychainAccess: a986406022dfc7c634c691ad3bec670cc6a32002 22 | Kingfisher: a0ac82dc5bcf0388dd74b294cf2cc13730c63436 23 | PageMenu: 9c38fd7e0d9ef4b1408320f55bb907bbc0e538dc 24 | Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 25 | SwiftQRCode: 280589699d84195c2c573011aa52f2c015c31c34 26 | SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220 27 | 28 | PODFILE CHECKSUM: 3e769f005276a095ab7bbb88e958747cce92769a 29 | 30 | COCOAPODS: 1.1.1 31 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.5.0) 3 | - KeychainAccess (3.0.2) 4 | - Kingfisher (3.10.3) 5 | - PageMenu (2.0.0) 6 | - Reachability (3.2) 7 | - SwiftQRCode (3.0.2) 8 | - SwiftyJSON (3.1.4) 9 | 10 | DEPENDENCIES: 11 | - Alamofire (~> 4.4) 12 | - KeychainAccess 13 | - Kingfisher (~> 3.0) 14 | - PageMenu 15 | - Reachability 16 | - SwiftQRCode 17 | - SwiftyJSON 18 | 19 | SPEC CHECKSUMS: 20 | Alamofire: f28cdffd29de33a7bfa022cbd63ae95a27fae140 21 | KeychainAccess: a986406022dfc7c634c691ad3bec670cc6a32002 22 | Kingfisher: a0ac82dc5bcf0388dd74b294cf2cc13730c63436 23 | PageMenu: 9c38fd7e0d9ef4b1408320f55bb907bbc0e538dc 24 | Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 25 | SwiftQRCode: 280589699d84195c2c573011aa52f2c015c31c34 26 | SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220 27 | 28 | PODFILE CHECKSUM: 3e769f005276a095ab7bbb88e958747cce92769a 29 | 30 | COCOAPODS: 1.1.1 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHPHub-Swift 2 | 3 | PHPHub 已经有了iOS 客户端,本项目是对现有版本的一个 Swift 复刻。 4 | 5 | 其主要目的是锻炼自己的 Swift 技能。 6 | 7 | ### 主要任务 8 | 9 | - [x] Initialize the framework 10 | - [x] WKWebView 11 | - [ ] Apple Push Notification Service 12 | - [ ] Scan to login 13 | 14 | PHPHub-Swift is the iOS application for PHPHub written with Swift. 15 | 16 | ### 当前进度 17 | 18 | ![截屏](https://github.com/nine-rec/PHPHub-Swift/blob/master/ScreenShot/ScreenShot.png?raw=true) 19 | 20 | ### 登陆二维码 21 | 22 | 测试环境下扫描二维码登陆(Copyed from phphub-ios) 23 | 24 | ![](http://ww3.sinaimg.cn/large/76dc7f1bgw1exrg86f5ubj20ml0dsq45.jpg) 25 | 26 | ### CocoaPods 27 | 28 | * Alamofire => Network 29 | * SwiftyJSON => JSON 30 | * KeychainAccess => Keychain 31 | * Kingfisher => Image Cache 32 | * PageMenu => 论坛 tab 的 Page 展示 33 | * Reachability => Network reachability (Future) 34 | -------------------------------------------------------------------------------- /ScreenShot/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NineRec/PHPHub-Swift/d14407906f4d830ecde3be86696cf3504d491f33/ScreenShot/ScreenShot.png --------------------------------------------------------------------------------