├── .DS_Store ├── .gitignore ├── .swiftlint.yml ├── .vscode └── settings.json ├── Gank.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Gank.xcworkspace └── contents.xcworkspacedata ├── Gank ├── .DS_Store ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-60@2x.png │ │ └── Icon-60@3x.png │ ├── Contents.json │ ├── button_send.imageset │ │ ├── Contents.json │ │ └── button_send.pdf │ └── navigation_menu_normal.imageset │ │ ├── Contents.json │ │ └── navigation_menu_normal.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Config │ ├── Config.swift │ └── Gank-Bridging-Header.h ├── Controllers │ ├── Home │ │ └── HomeViewController.swift │ ├── Navigate │ │ ├── BaseNavigationController.swift │ │ └── SideMenuNavigationController.swift │ ├── Side │ │ └── SideViewController.swift │ └── Web │ │ └── BrowserWebViewController.swift ├── Extensions │ └── Response+ObjectMapper.swift ├── Info.plist ├── Models │ └── Home │ │ └── Brick.swift ├── Protocol │ └── ViewModelType.swift ├── Services │ └── Network │ │ └── GankAPI.swift ├── ViewModels │ └── Home │ │ └── HomeViewModel.swift └── Views │ └── home │ ├── HomeTableViewCell.swift │ └── HomeTableViewCell.xib ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md └── Screenshot ├── 01.png ├── 02.png └── 03.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/.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 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: # paths to include during linting. `--path` is ignored if present. 2 | - Gank 3 | # excluded: # paths to ignore during linting. Takes precedence over `included`. 4 | # - Pods/ 5 | # - Gank/Vendors 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.quickSuggestions": true 3 | } -------------------------------------------------------------------------------- /Gank.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AF3C0C221E0AA003002644DD /* BrowserWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF3C0C211E0AA003002644DD /* BrowserWebViewController.swift */; }; 11 | AF3C0C241E0AA851002644DD /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF3C0C231E0AA851002644DD /* Config.swift */; }; 12 | AF69F0EB1E0E055D00735284 /* SideMenuNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69F0EA1E0E055D00735284 /* SideMenuNavigationController.swift */; }; 13 | AF69F0ED1E0E08DE00735284 /* BaseNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69F0EC1E0E08DE00735284 /* BaseNavigationController.swift */; }; 14 | AF69F0F01E0E0C9400735284 /* SideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69F0EF1E0E0C9400735284 /* SideViewController.swift */; }; 15 | AF9F0FD11DF1A6CD00FF8EC5 /* Brick.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9F0FD01DF1A6CD00FF8EC5 /* Brick.swift */; }; 16 | AF9F0FD41DF1A91300FF8EC5 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9F0FD31DF1A91300FF8EC5 /* HomeViewController.swift */; }; 17 | AF9F0FD91DF1A9E900FF8EC5 /* HomeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9F0FD71DF1A9E900FF8EC5 /* HomeTableViewCell.swift */; }; 18 | AF9F0FDA1DF1A9E900FF8EC5 /* HomeTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = AF9F0FD81DF1A9E900FF8EC5 /* HomeTableViewCell.xib */; }; 19 | AFA7EC6E1DEEDEDC00138AEE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA7EC6D1DEEDEDC00138AEE /* AppDelegate.swift */; }; 20 | AFA7EC731DEEDEDC00138AEE /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AFA7EC711DEEDEDC00138AEE /* Main.storyboard */; }; 21 | AFA7EC751DEEDEDC00138AEE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFA7EC741DEEDEDC00138AEE /* Assets.xcassets */; }; 22 | AFA7EC781DEEDEDC00138AEE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AFA7EC761DEEDEDC00138AEE /* LaunchScreen.storyboard */; }; 23 | AFBBA5CD1E06F783003605D1 /* Response+ObjectMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBBA5CC1E06F783003605D1 /* Response+ObjectMapper.swift */; }; 24 | AFBDB2621DF58F23003E8EE2 /* GankAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBDB2611DF58F23003E8EE2 /* GankAPI.swift */; }; 25 | AFCB661E1DF844DB00916E1B /* HomeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCB661D1DF844DB00916E1B /* HomeViewModel.swift */; }; 26 | AFE2BA9A1E8144000066474E /* ViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE2BA991E8144000066474E /* ViewModelType.swift */; }; 27 | F2322ED0BDDB5547F4A1D973 /* Pods_Gank.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECF6CC27851F092E4B160AF6 /* Pods_Gank.framework */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 8549F4BE32D5A4600A90FD47 /* Pods-Gank.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Gank.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Gank/Pods-Gank.debug.xcconfig"; sourceTree = ""; }; 32 | 8D471DB23A1F471D67A6B0EE /* Pods-Gank.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Gank.release.xcconfig"; path = "Pods/Target Support Files/Pods-Gank/Pods-Gank.release.xcconfig"; sourceTree = ""; }; 33 | AF3C0C211E0AA003002644DD /* BrowserWebViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserWebViewController.swift; sourceTree = ""; }; 34 | AF3C0C231E0AA851002644DD /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 35 | AF69F0EA1E0E055D00735284 /* SideMenuNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideMenuNavigationController.swift; sourceTree = ""; }; 36 | AF69F0EC1E0E08DE00735284 /* BaseNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseNavigationController.swift; sourceTree = ""; }; 37 | AF69F0EF1E0E0C9400735284 /* SideViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SideViewController.swift; sourceTree = ""; }; 38 | AF9F0FD01DF1A6CD00FF8EC5 /* Brick.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Brick.swift; sourceTree = ""; }; 39 | AF9F0FD31DF1A91300FF8EC5 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 40 | AF9F0FD71DF1A9E900FF8EC5 /* HomeTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeTableViewCell.swift; sourceTree = ""; }; 41 | AF9F0FD81DF1A9E900FF8EC5 /* HomeTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HomeTableViewCell.xib; sourceTree = ""; }; 42 | AFA7EC6A1DEEDEDC00138AEE /* Gank.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Gank.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | AFA7EC6D1DEEDEDC00138AEE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 44 | AFA7EC721DEEDEDC00138AEE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | AFA7EC741DEEDEDC00138AEE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | AFA7EC771DEEDEDC00138AEE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | AFA7EC791DEEDEDC00138AEE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | AFA7EC831DEF4D5900138AEE /* Gank-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Gank-Bridging-Header.h"; sourceTree = ""; }; 49 | AFBBA5CC1E06F783003605D1 /* Response+ObjectMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Response+ObjectMapper.swift"; sourceTree = ""; }; 50 | AFBDB2611DF58F23003E8EE2 /* GankAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GankAPI.swift; sourceTree = ""; }; 51 | AFCB661D1DF844DB00916E1B /* HomeViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewModel.swift; sourceTree = ""; }; 52 | AFE2BA991E8144000066474E /* ViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModelType.swift; sourceTree = ""; }; 53 | ECF6CC27851F092E4B160AF6 /* Pods_Gank.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Gank.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | /* End PBXFileReference section */ 55 | 56 | /* Begin PBXFrameworksBuildPhase section */ 57 | AFA7EC671DEEDEDC00138AEE /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | F2322ED0BDDB5547F4A1D973 /* Pods_Gank.framework in Frameworks */, 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 76D86625E43062857DEBD5A2 /* Frameworks */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | ECF6CC27851F092E4B160AF6 /* Pods_Gank.framework */, 72 | ); 73 | name = Frameworks; 74 | sourceTree = ""; 75 | }; 76 | AF3C0C201E0A9FBC002644DD /* Web */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | AF3C0C211E0AA003002644DD /* BrowserWebViewController.swift */, 80 | ); 81 | path = Web; 82 | sourceTree = ""; 83 | }; 84 | AF69F0E91E0E052D00735284 /* Navigate */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | AF69F0EA1E0E055D00735284 /* SideMenuNavigationController.swift */, 88 | AF69F0EC1E0E08DE00735284 /* BaseNavigationController.swift */, 89 | ); 90 | path = Navigate; 91 | sourceTree = ""; 92 | }; 93 | AF69F0EE1E0E0C6D00735284 /* Side */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | AF69F0EF1E0E0C9400735284 /* SideViewController.swift */, 97 | ); 98 | path = Side; 99 | sourceTree = ""; 100 | }; 101 | AF9F0FCE1DF1A66300FF8EC5 /* Models */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | AF9F0FCF1DF1A66E00FF8EC5 /* Home */, 105 | ); 106 | path = Models; 107 | sourceTree = ""; 108 | }; 109 | AF9F0FCF1DF1A66E00FF8EC5 /* Home */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | AF9F0FD01DF1A6CD00FF8EC5 /* Brick.swift */, 113 | ); 114 | path = Home; 115 | sourceTree = ""; 116 | }; 117 | AF9F0FD21DF1A8FA00FF8EC5 /* Home */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | AF9F0FD31DF1A91300FF8EC5 /* HomeViewController.swift */, 121 | ); 122 | path = Home; 123 | sourceTree = ""; 124 | }; 125 | AF9F0FD51DF1A9B300FF8EC5 /* Views */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | AF9F0FD61DF1A9C100FF8EC5 /* Home */, 129 | ); 130 | path = Views; 131 | sourceTree = ""; 132 | }; 133 | AF9F0FD61DF1A9C100FF8EC5 /* Home */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | AF9F0FD71DF1A9E900FF8EC5 /* HomeTableViewCell.swift */, 137 | AF9F0FD81DF1A9E900FF8EC5 /* HomeTableViewCell.xib */, 138 | ); 139 | name = Home; 140 | path = home; 141 | sourceTree = ""; 142 | }; 143 | AFA7EC611DEEDEDC00138AEE = { 144 | isa = PBXGroup; 145 | children = ( 146 | AFA7EC6C1DEEDEDC00138AEE /* Gank */, 147 | AFA7EC6B1DEEDEDC00138AEE /* Products */, 148 | B934ED3498AB176D331F2919 /* Pods */, 149 | 76D86625E43062857DEBD5A2 /* Frameworks */, 150 | ); 151 | sourceTree = ""; 152 | }; 153 | AFA7EC6B1DEEDEDC00138AEE /* Products */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | AFA7EC6A1DEEDEDC00138AEE /* Gank.app */, 157 | ); 158 | name = Products; 159 | sourceTree = ""; 160 | }; 161 | AFA7EC6C1DEEDEDC00138AEE /* Gank */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | AFA7EC821DEF4CA600138AEE /* Config */, 165 | AF9F0FD51DF1A9B300FF8EC5 /* Views */, 166 | AF9F0FCE1DF1A66300FF8EC5 /* Models */, 167 | AFCB661B1DF844B100916E1B /* ViewModels */, 168 | AFA7EC7F1DEF4BB400138AEE /* Controllers */, 169 | AFBDB25F1DF58ED9003E8EE2 /* Services */, 170 | AFBBA5CB1E06F76C003605D1 /* Extensions */, 171 | AFE2BA981E8143CD0066474E /* Protocol */, 172 | AFA7EC6D1DEEDEDC00138AEE /* AppDelegate.swift */, 173 | AFA7EC741DEEDEDC00138AEE /* Assets.xcassets */, 174 | AFA7EC711DEEDEDC00138AEE /* Main.storyboard */, 175 | AFA7EC761DEEDEDC00138AEE /* LaunchScreen.storyboard */, 176 | AFA7EC791DEEDEDC00138AEE /* Info.plist */, 177 | ); 178 | path = Gank; 179 | sourceTree = ""; 180 | }; 181 | AFA7EC7F1DEF4BB400138AEE /* Controllers */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | AF69F0EE1E0E0C6D00735284 /* Side */, 185 | AF69F0E91E0E052D00735284 /* Navigate */, 186 | AF3C0C201E0A9FBC002644DD /* Web */, 187 | AF9F0FD21DF1A8FA00FF8EC5 /* Home */, 188 | ); 189 | path = Controllers; 190 | sourceTree = ""; 191 | }; 192 | AFA7EC821DEF4CA600138AEE /* Config */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | AF3C0C231E0AA851002644DD /* Config.swift */, 196 | AFA7EC831DEF4D5900138AEE /* Gank-Bridging-Header.h */, 197 | ); 198 | path = Config; 199 | sourceTree = ""; 200 | }; 201 | AFBBA5CB1E06F76C003605D1 /* Extensions */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | AFBBA5CC1E06F783003605D1 /* Response+ObjectMapper.swift */, 205 | ); 206 | path = Extensions; 207 | sourceTree = ""; 208 | }; 209 | AFBDB25F1DF58ED9003E8EE2 /* Services */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | AFBDB2601DF58EF1003E8EE2 /* Network */, 213 | ); 214 | path = Services; 215 | sourceTree = ""; 216 | }; 217 | AFBDB2601DF58EF1003E8EE2 /* Network */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | AFBDB2611DF58F23003E8EE2 /* GankAPI.swift */, 221 | ); 222 | path = Network; 223 | sourceTree = ""; 224 | }; 225 | AFCB661B1DF844B100916E1B /* ViewModels */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | AFCB661C1DF844C200916E1B /* Home */, 229 | ); 230 | path = ViewModels; 231 | sourceTree = ""; 232 | }; 233 | AFCB661C1DF844C200916E1B /* Home */ = { 234 | isa = PBXGroup; 235 | children = ( 236 | AFCB661D1DF844DB00916E1B /* HomeViewModel.swift */, 237 | ); 238 | path = Home; 239 | sourceTree = ""; 240 | }; 241 | AFE2BA981E8143CD0066474E /* Protocol */ = { 242 | isa = PBXGroup; 243 | children = ( 244 | AFE2BA991E8144000066474E /* ViewModelType.swift */, 245 | ); 246 | path = Protocol; 247 | sourceTree = ""; 248 | }; 249 | B934ED3498AB176D331F2919 /* Pods */ = { 250 | isa = PBXGroup; 251 | children = ( 252 | 8549F4BE32D5A4600A90FD47 /* Pods-Gank.debug.xcconfig */, 253 | 8D471DB23A1F471D67A6B0EE /* Pods-Gank.release.xcconfig */, 254 | ); 255 | name = Pods; 256 | sourceTree = ""; 257 | }; 258 | /* End PBXGroup section */ 259 | 260 | /* Begin PBXNativeTarget section */ 261 | AFA7EC691DEEDEDC00138AEE /* Gank */ = { 262 | isa = PBXNativeTarget; 263 | buildConfigurationList = AFA7EC7C1DEEDEDC00138AEE /* Build configuration list for PBXNativeTarget "Gank" */; 264 | buildPhases = ( 265 | DDD2283C8D4D0F238A575BF0 /* [CP] Check Pods Manifest.lock */, 266 | AFA7EC661DEEDEDC00138AEE /* Sources */, 267 | AFA7EC671DEEDEDC00138AEE /* Frameworks */, 268 | AFA7EC681DEEDEDC00138AEE /* Resources */, 269 | 29E82F0B854670A68B44D42B /* [CP] Embed Pods Frameworks */, 270 | FF63BD155A33A5B4498E7539 /* [CP] Copy Pods Resources */, 271 | ); 272 | buildRules = ( 273 | ); 274 | dependencies = ( 275 | ); 276 | name = Gank; 277 | productName = Gank; 278 | productReference = AFA7EC6A1DEEDEDC00138AEE /* Gank.app */; 279 | productType = "com.apple.product-type.application"; 280 | }; 281 | /* End PBXNativeTarget section */ 282 | 283 | /* Begin PBXProject section */ 284 | AFA7EC621DEEDEDC00138AEE /* Project object */ = { 285 | isa = PBXProject; 286 | attributes = { 287 | LastSwiftUpdateCheck = 0810; 288 | LastUpgradeCheck = 0810; 289 | ORGANIZATIONNAME = Maru; 290 | TargetAttributes = { 291 | AFA7EC691DEEDEDC00138AEE = { 292 | CreatedOnToolsVersion = 8.1; 293 | DevelopmentTeam = 678WD436L9; 294 | ProvisioningStyle = Automatic; 295 | }; 296 | }; 297 | }; 298 | buildConfigurationList = AFA7EC651DEEDEDC00138AEE /* Build configuration list for PBXProject "Gank" */; 299 | compatibilityVersion = "Xcode 3.2"; 300 | developmentRegion = English; 301 | hasScannedForEncodings = 0; 302 | knownRegions = ( 303 | en, 304 | Base, 305 | ); 306 | mainGroup = AFA7EC611DEEDEDC00138AEE; 307 | productRefGroup = AFA7EC6B1DEEDEDC00138AEE /* Products */; 308 | projectDirPath = ""; 309 | projectRoot = ""; 310 | targets = ( 311 | AFA7EC691DEEDEDC00138AEE /* Gank */, 312 | ); 313 | }; 314 | /* End PBXProject section */ 315 | 316 | /* Begin PBXResourcesBuildPhase section */ 317 | AFA7EC681DEEDEDC00138AEE /* Resources */ = { 318 | isa = PBXResourcesBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | AFA7EC781DEEDEDC00138AEE /* LaunchScreen.storyboard in Resources */, 322 | AFA7EC751DEEDEDC00138AEE /* Assets.xcassets in Resources */, 323 | AF9F0FDA1DF1A9E900FF8EC5 /* HomeTableViewCell.xib in Resources */, 324 | AFA7EC731DEEDEDC00138AEE /* Main.storyboard in Resources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | /* End PBXResourcesBuildPhase section */ 329 | 330 | /* Begin PBXShellScriptBuildPhase section */ 331 | 29E82F0B854670A68B44D42B /* [CP] Embed Pods Frameworks */ = { 332 | isa = PBXShellScriptBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | inputPaths = ( 337 | "${SRCROOT}/Pods/Target Support Files/Pods-Gank/Pods-Gank-frameworks.sh", 338 | "${BUILT_PRODUCTS_DIR}/Action/Action.framework", 339 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 340 | "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework", 341 | "${BUILT_PRODUCTS_DIR}/EZSwiftExtensions/EZSwiftExtensions.framework", 342 | "${BUILT_PRODUCTS_DIR}/HMSegmentedControl/HMSegmentedControl.framework", 343 | "${BUILT_PRODUCTS_DIR}/Kingfisher/Kingfisher.framework", 344 | "${BUILT_PRODUCTS_DIR}/Moya/Moya.framework", 345 | "${BUILT_PRODUCTS_DIR}/NSObject+Rx/NSObject_Rx.framework", 346 | "${BUILT_PRODUCTS_DIR}/NoticeBar/NoticeBar.framework", 347 | "${BUILT_PRODUCTS_DIR}/ObjectMapper/ObjectMapper.framework", 348 | "${BUILT_PRODUCTS_DIR}/PullToRefresher/PullToRefresh.framework", 349 | "${BUILT_PRODUCTS_DIR}/Result/Result.framework", 350 | "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework", 351 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 352 | "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework", 353 | "${BUILT_PRODUCTS_DIR}/RxOptional/RxOptional.framework", 354 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 355 | "${BUILT_PRODUCTS_DIR}/SideMenu/SideMenu.framework", 356 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 357 | "${BUILT_PRODUCTS_DIR}/SwiftWebVC/SwiftWebVC.framework", 358 | "${BUILT_PRODUCTS_DIR}/SwiftyBeaver/SwiftyBeaver.framework", 359 | "${BUILT_PRODUCTS_DIR}/Then/Then.framework", 360 | ); 361 | name = "[CP] Embed Pods Frameworks"; 362 | outputPaths = ( 363 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Action.framework", 364 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 365 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework", 366 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EZSwiftExtensions.framework", 367 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HMSegmentedControl.framework", 368 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Kingfisher.framework", 369 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Moya.framework", 370 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NSObject_Rx.framework", 371 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/NoticeBar.framework", 372 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectMapper.framework", 373 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PullToRefresh.framework", 374 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Result.framework", 375 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reusable.framework", 376 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 377 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework", 378 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxOptional.framework", 379 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 380 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SideMenu.framework", 381 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 382 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftWebVC.framework", 383 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyBeaver.framework", 384 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Then.framework", 385 | ); 386 | runOnlyForDeploymentPostprocessing = 0; 387 | shellPath = /bin/sh; 388 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Gank/Pods-Gank-frameworks.sh\"\n"; 389 | showEnvVarsInLog = 0; 390 | }; 391 | DDD2283C8D4D0F238A575BF0 /* [CP] Check Pods Manifest.lock */ = { 392 | isa = PBXShellScriptBuildPhase; 393 | buildActionMask = 2147483647; 394 | files = ( 395 | ); 396 | inputPaths = ( 397 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 398 | "${PODS_ROOT}/Manifest.lock", 399 | ); 400 | name = "[CP] Check Pods Manifest.lock"; 401 | outputPaths = ( 402 | "$(DERIVED_FILE_DIR)/Pods-Gank-checkManifestLockResult.txt", 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | shellPath = /bin/sh; 406 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/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# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 407 | showEnvVarsInLog = 0; 408 | }; 409 | FF63BD155A33A5B4498E7539 /* [CP] Copy Pods Resources */ = { 410 | isa = PBXShellScriptBuildPhase; 411 | buildActionMask = 2147483647; 412 | files = ( 413 | ); 414 | inputPaths = ( 415 | ); 416 | name = "[CP] Copy Pods Resources"; 417 | outputPaths = ( 418 | ); 419 | runOnlyForDeploymentPostprocessing = 0; 420 | shellPath = /bin/sh; 421 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Gank/Pods-Gank-resources.sh\"\n"; 422 | showEnvVarsInLog = 0; 423 | }; 424 | /* End PBXShellScriptBuildPhase section */ 425 | 426 | /* Begin PBXSourcesBuildPhase section */ 427 | AFA7EC661DEEDEDC00138AEE /* Sources */ = { 428 | isa = PBXSourcesBuildPhase; 429 | buildActionMask = 2147483647; 430 | files = ( 431 | AFBDB2621DF58F23003E8EE2 /* GankAPI.swift in Sources */, 432 | AF3C0C221E0AA003002644DD /* BrowserWebViewController.swift in Sources */, 433 | AFCB661E1DF844DB00916E1B /* HomeViewModel.swift in Sources */, 434 | AFA7EC6E1DEEDEDC00138AEE /* AppDelegate.swift in Sources */, 435 | AF9F0FD11DF1A6CD00FF8EC5 /* Brick.swift in Sources */, 436 | AFBBA5CD1E06F783003605D1 /* Response+ObjectMapper.swift in Sources */, 437 | AFE2BA9A1E8144000066474E /* ViewModelType.swift in Sources */, 438 | AF9F0FD91DF1A9E900FF8EC5 /* HomeTableViewCell.swift in Sources */, 439 | AF69F0EB1E0E055D00735284 /* SideMenuNavigationController.swift in Sources */, 440 | AF69F0ED1E0E08DE00735284 /* BaseNavigationController.swift in Sources */, 441 | AF69F0F01E0E0C9400735284 /* SideViewController.swift in Sources */, 442 | AF3C0C241E0AA851002644DD /* Config.swift in Sources */, 443 | AF9F0FD41DF1A91300FF8EC5 /* HomeViewController.swift in Sources */, 444 | ); 445 | runOnlyForDeploymentPostprocessing = 0; 446 | }; 447 | /* End PBXSourcesBuildPhase section */ 448 | 449 | /* Begin PBXVariantGroup section */ 450 | AFA7EC711DEEDEDC00138AEE /* Main.storyboard */ = { 451 | isa = PBXVariantGroup; 452 | children = ( 453 | AFA7EC721DEEDEDC00138AEE /* Base */, 454 | ); 455 | name = Main.storyboard; 456 | sourceTree = ""; 457 | }; 458 | AFA7EC761DEEDEDC00138AEE /* LaunchScreen.storyboard */ = { 459 | isa = PBXVariantGroup; 460 | children = ( 461 | AFA7EC771DEEDEDC00138AEE /* Base */, 462 | ); 463 | name = LaunchScreen.storyboard; 464 | sourceTree = ""; 465 | }; 466 | /* End PBXVariantGroup section */ 467 | 468 | /* Begin XCBuildConfiguration section */ 469 | AFA7EC7A1DEEDEDC00138AEE /* Debug */ = { 470 | isa = XCBuildConfiguration; 471 | buildSettings = { 472 | ALWAYS_SEARCH_USER_PATHS = NO; 473 | CLANG_ANALYZER_NONNULL = YES; 474 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 475 | CLANG_CXX_LIBRARY = "libc++"; 476 | CLANG_ENABLE_MODULES = YES; 477 | CLANG_ENABLE_OBJC_ARC = YES; 478 | CLANG_WARN_BOOL_CONVERSION = YES; 479 | CLANG_WARN_CONSTANT_CONVERSION = YES; 480 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 481 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 482 | CLANG_WARN_EMPTY_BODY = YES; 483 | CLANG_WARN_ENUM_CONVERSION = YES; 484 | CLANG_WARN_INFINITE_RECURSION = YES; 485 | CLANG_WARN_INT_CONVERSION = YES; 486 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 487 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 488 | CLANG_WARN_UNREACHABLE_CODE = YES; 489 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 490 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 491 | COPY_PHASE_STRIP = NO; 492 | DEBUG_INFORMATION_FORMAT = dwarf; 493 | ENABLE_STRICT_OBJC_MSGSEND = YES; 494 | ENABLE_TESTABILITY = YES; 495 | GCC_C_LANGUAGE_STANDARD = gnu99; 496 | GCC_DYNAMIC_NO_PIC = NO; 497 | GCC_NO_COMMON_BLOCKS = YES; 498 | GCC_OPTIMIZATION_LEVEL = 0; 499 | GCC_PREPROCESSOR_DEFINITIONS = ( 500 | "DEBUG=1", 501 | "$(inherited)", 502 | ); 503 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 504 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 505 | GCC_WARN_UNDECLARED_SELECTOR = YES; 506 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 507 | GCC_WARN_UNUSED_FUNCTION = YES; 508 | GCC_WARN_UNUSED_VARIABLE = YES; 509 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 510 | MTL_ENABLE_DEBUG_INFO = YES; 511 | ONLY_ACTIVE_ARCH = YES; 512 | SDKROOT = iphoneos; 513 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 514 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 515 | }; 516 | name = Debug; 517 | }; 518 | AFA7EC7B1DEEDEDC00138AEE /* Release */ = { 519 | isa = XCBuildConfiguration; 520 | buildSettings = { 521 | ALWAYS_SEARCH_USER_PATHS = NO; 522 | CLANG_ANALYZER_NONNULL = YES; 523 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 524 | CLANG_CXX_LIBRARY = "libc++"; 525 | CLANG_ENABLE_MODULES = YES; 526 | CLANG_ENABLE_OBJC_ARC = YES; 527 | CLANG_WARN_BOOL_CONVERSION = YES; 528 | CLANG_WARN_CONSTANT_CONVERSION = YES; 529 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 530 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 531 | CLANG_WARN_EMPTY_BODY = YES; 532 | CLANG_WARN_ENUM_CONVERSION = YES; 533 | CLANG_WARN_INFINITE_RECURSION = YES; 534 | CLANG_WARN_INT_CONVERSION = YES; 535 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 536 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 537 | CLANG_WARN_UNREACHABLE_CODE = YES; 538 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 539 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 540 | COPY_PHASE_STRIP = NO; 541 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 542 | ENABLE_NS_ASSERTIONS = NO; 543 | ENABLE_STRICT_OBJC_MSGSEND = YES; 544 | GCC_C_LANGUAGE_STANDARD = gnu99; 545 | GCC_NO_COMMON_BLOCKS = YES; 546 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 547 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 548 | GCC_WARN_UNDECLARED_SELECTOR = YES; 549 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 550 | GCC_WARN_UNUSED_FUNCTION = YES; 551 | GCC_WARN_UNUSED_VARIABLE = YES; 552 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 553 | MTL_ENABLE_DEBUG_INFO = NO; 554 | SDKROOT = iphoneos; 555 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 556 | VALIDATE_PRODUCT = YES; 557 | }; 558 | name = Release; 559 | }; 560 | AFA7EC7D1DEEDEDC00138AEE /* Debug */ = { 561 | isa = XCBuildConfiguration; 562 | baseConfigurationReference = 8549F4BE32D5A4600A90FD47 /* Pods-Gank.debug.xcconfig */; 563 | buildSettings = { 564 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 565 | DEVELOPMENT_TEAM = 678WD436L9; 566 | INFOPLIST_FILE = Gank/Info.plist; 567 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 568 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 569 | PRODUCT_BUNDLE_IDENTIFIER = alloc.studio.Gank; 570 | PRODUCT_NAME = "$(TARGET_NAME)"; 571 | SWIFT_OBJC_BRIDGING_HEADER = "Gank/Config/Gank-Bridging-Header.h"; 572 | SWIFT_VERSION = 3.0; 573 | }; 574 | name = Debug; 575 | }; 576 | AFA7EC7E1DEEDEDC00138AEE /* Release */ = { 577 | isa = XCBuildConfiguration; 578 | baseConfigurationReference = 8D471DB23A1F471D67A6B0EE /* Pods-Gank.release.xcconfig */; 579 | buildSettings = { 580 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 581 | DEVELOPMENT_TEAM = 678WD436L9; 582 | INFOPLIST_FILE = Gank/Info.plist; 583 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 584 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 585 | PRODUCT_BUNDLE_IDENTIFIER = alloc.studio.Gank; 586 | PRODUCT_NAME = "$(TARGET_NAME)"; 587 | SWIFT_OBJC_BRIDGING_HEADER = "Gank/Config/Gank-Bridging-Header.h"; 588 | SWIFT_VERSION = 3.0; 589 | }; 590 | name = Release; 591 | }; 592 | /* End XCBuildConfiguration section */ 593 | 594 | /* Begin XCConfigurationList section */ 595 | AFA7EC651DEEDEDC00138AEE /* Build configuration list for PBXProject "Gank" */ = { 596 | isa = XCConfigurationList; 597 | buildConfigurations = ( 598 | AFA7EC7A1DEEDEDC00138AEE /* Debug */, 599 | AFA7EC7B1DEEDEDC00138AEE /* Release */, 600 | ); 601 | defaultConfigurationIsVisible = 0; 602 | defaultConfigurationName = Release; 603 | }; 604 | AFA7EC7C1DEEDEDC00138AEE /* Build configuration list for PBXNativeTarget "Gank" */ = { 605 | isa = XCConfigurationList; 606 | buildConfigurations = ( 607 | AFA7EC7D1DEEDEDC00138AEE /* Debug */, 608 | AFA7EC7E1DEEDEDC00138AEE /* Release */, 609 | ); 610 | defaultConfigurationIsVisible = 0; 611 | defaultConfigurationName = Release; 612 | }; 613 | /* End XCConfigurationList section */ 614 | }; 615 | rootObject = AFA7EC621DEEDEDC00138AEE /* Project object */; 616 | } 617 | -------------------------------------------------------------------------------- /Gank.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Gank.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Gank/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Gank/.DS_Store -------------------------------------------------------------------------------- /Gank/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable line_length 2 | 3 | import UIKit 4 | import Kingfisher 5 | import SideMenu 6 | 7 | @UIApplicationMain 8 | class AppDelegate: UIResponder, UIApplicationDelegate { 9 | 10 | var window: UIWindow? 11 | 12 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 13 | // Override point for customization after application launch. 14 | setupConfig() 15 | return true 16 | } 17 | } 18 | 19 | extension AppDelegate { 20 | 21 | fileprivate func setupConfig() { 22 | 23 | do /** KingFisher Config */ { 24 | ImageCache.`default`.maxMemoryCost = UInt(30 * 1024 * 1024) 25 | } 26 | 27 | do /** Navgation Config */ { 28 | UINavigationBar.appearance().tintColor = Config.UI.titleColor 29 | UINavigationBar.appearance().isTranslucent = false 30 | UINavigationBar.appearance().barTintColor = Config.UI.themeColor 31 | UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: Config.UI.titleColor] 32 | } 33 | 34 | do /** SideMenu Config */ { 35 | SideMenuManager.menuAnimationBackgroundColor = Config.UI.themeColor 36 | SideMenuManager.menuWidth = 120.0 37 | SideMenuManager.menuFadeStatusBar = false 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Gank/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 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-60@3x.png", 43 | "scale" : "3x" 44 | } 45 | ], 46 | "info" : { 47 | "version" : 1, 48 | "author" : "xcode" 49 | } 50 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Gank/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Gank/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Gank/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/button_send.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "button_send.pdf", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/button_send.imageset/button_send.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Gank/Assets.xcassets/button_send.imageset/button_send.pdf -------------------------------------------------------------------------------- /Gank/Assets.xcassets/navigation_menu_normal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "navigation_menu_normal.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | }, 21 | "properties" : { 22 | "template-rendering-intent" : "original" 23 | } 24 | } -------------------------------------------------------------------------------- /Gank/Assets.xcassets/navigation_menu_normal.imageset/navigation_menu_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Gank/Assets.xcassets/navigation_menu_normal.imageset/navigation_menu_normal.png -------------------------------------------------------------------------------- /Gank/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 | -------------------------------------------------------------------------------- /Gank/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /Gank/Config/Config.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable type_name 2 | 3 | import Foundation 4 | 5 | struct Config { 6 | 7 | struct UI { 8 | /// Gank‘s ThemeColor 9 | static let themeColor = UIColor(r: 63.0, g: 63.0, b: 63.0, a: 1) 10 | /// Gank's Navgation Title Color 11 | static let titleColor = UIColor(r: 255, g: 255, b: 255, a: 1) 12 | } 13 | } 14 | 15 | extension Notification.Name { 16 | /// Gank post when home category change 17 | static let category = Notification.Name(rawValue: "homeCategory") 18 | } 19 | -------------------------------------------------------------------------------- /Gank/Config/Gank-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Gank-Bridging-Header.h 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/1. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | #import 10 | -------------------------------------------------------------------------------- /Gank/Controllers/Home/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable function_body_length 2 | 3 | import UIKit 4 | import SwiftWebVC 5 | import HMSegmentedControl 6 | import EZSwiftExtensions 7 | import Then 8 | import SnapKit 9 | import Reusable 10 | import RxSwift 11 | import RxCocoa 12 | import Kingfisher 13 | import NoticeBar 14 | import SideMenu 15 | import PullToRefresh 16 | import RxDataSources 17 | 18 | final class HomeViewController: UIViewController { 19 | 20 | let tableView = UITableView().then { 21 | $0.register(cellType: HomeTableViewCell.self) 22 | } 23 | 24 | let refreshControl = PullToRefresh() 25 | 26 | let homeVM = HomeViewModel() 27 | 28 | let dataSource = RxTableViewSectionedReloadDataSource() 29 | 30 | // MARK: - Life Cycle 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | configUI() 35 | configBinding() 36 | configNotification() 37 | } 38 | } 39 | 40 | extension HomeViewController { 41 | 42 | // MARK: - Private Method 43 | 44 | fileprivate func configUI() { 45 | title = "Gank" 46 | tableView.estimatedRowHeight = 100 47 | tableView.separatorStyle = .none 48 | tableView.refreshControl = UIRefreshControl() 49 | view.addSubview(tableView) 50 | tableView.snp.makeConstraints { (make) in 51 | make.edges.equalTo(view) 52 | } 53 | } 54 | fileprivate func configBinding() { 55 | 56 | // Input 57 | let inputStuff = HomeViewModel.HomeInput() 58 | // Output 59 | let outputStuff = homeVM.transform(input: inputStuff) 60 | 61 | // DataBinding 62 | tableView.refreshControl?.rx.controlEvent(.allEvents) 63 | .flatMap({ inputStuff.category.asObservable() }) 64 | .bind(to: outputStuff.refreshCommand) 65 | .addDisposableTo(rx.disposeBag) 66 | 67 | NotificationCenter.default.rx.notification(Notification.Name.category) 68 | .map({ (notification) -> Int in 69 | let indexPath = (notification.object as? IndexPath) ?? IndexPath(item: 0, section: 0) 70 | return indexPath.row 71 | }) 72 | .bind(to: inputStuff.category) 73 | .addDisposableTo(rx.disposeBag) 74 | 75 | NotificationCenter.default.rx.notification(Notification.Name.category) 76 | .map({ (notification) -> Int in 77 | let indexPath = (notification.object as? IndexPath) ?? IndexPath(item: 0, section: 0) 78 | return indexPath.row 79 | }) 80 | .observeOn(MainScheduler.asyncInstance) 81 | .do(onNext: { (_) in 82 | SideMenuManager.menuLeftNavigationController?.dismiss(animated: true, completion: { 83 | DispatchQueue.main.async(execute: { 84 | self.tableView.refreshControl?.beginRefreshing() 85 | }) 86 | }) 87 | }, onError: nil, onCompleted: nil, onSubscribe: nil, onDispose: nil) 88 | .bind(to: outputStuff.refreshCommand) 89 | .addDisposableTo(rx.disposeBag) 90 | 91 | // Configure 92 | dataSource.configureCell = { dataSource, tableView, indexPath, item in 93 | let cell = tableView.dequeueReusableCell(for: indexPath, cellType: HomeTableViewCell.self) 94 | cell.gankTitle?.text = item.desc 95 | cell.gankAuthor.text = item.who 96 | cell.gankTime.text = item.publishedAt.toString(format: "YYYY/MM/DD") 97 | return cell 98 | } 99 | 100 | outputStuff.section 101 | .drive(tableView.rx.items(dataSource: dataSource)) 102 | .addDisposableTo(rx.disposeBag) 103 | 104 | tableView.rx.setDelegate(self) 105 | .addDisposableTo(rx.disposeBag) 106 | 107 | outputStuff.refreshTrigger 108 | .observeOn(MainScheduler.instance) 109 | .subscribe { [unowned self] (event) in 110 | self.tableView.refreshControl?.endRefreshing() 111 | switch event { 112 | case .error(_): 113 | NoticeBar(title: "Network Disconnect!", defaultType: .error).show(duration: 2.0, completed: nil) 114 | break 115 | case .next(_): 116 | self.tableView.reloadData() 117 | break 118 | default: 119 | break 120 | } 121 | } 122 | .addDisposableTo(rx.disposeBag) 123 | } 124 | 125 | fileprivate func configNotification() { 126 | NotificationCenter.default.post(name: Notification.Name.category, object: IndexPath(row: 0, section: 0)) 127 | } 128 | } 129 | 130 | extension HomeViewController { 131 | 132 | // MARK: - Private Methpd 133 | 134 | override var preferredStatusBarStyle: UIStatusBarStyle { 135 | return .lightContent 136 | } 137 | } 138 | 139 | extension HomeViewController: UITableViewDelegate { 140 | 141 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 142 | return HomeTableViewCell.height 143 | } 144 | 145 | func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { 146 | return UIView() 147 | } 148 | 149 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 150 | tableView.deselectRow(at: indexPath, animated: true) 151 | let webActivity = BrowserWebViewController(url: homeVM.itemURLs.value[indexPath.row]) 152 | navigationController?.pushViewController(webActivity, animated: true) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Gank/Controllers/Navigate/BaseNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseNavigationController.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/24. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseNavigationController: UINavigationController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | override func didReceiveMemoryWarning() { 20 | super.didReceiveMemoryWarning() 21 | // Dispose of any resources that can be recreated. 22 | } 23 | 24 | override var childViewControllerForStatusBarStyle: UIViewController? { 25 | return topViewController 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /Gank/Controllers/Navigate/SideMenuNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SideMenuNavigationController.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/24. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SideMenu 11 | 12 | class SideMenuNavigationController: UISideMenuNavigationController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | // Do any additional setup after loading the view. 18 | } 19 | 20 | override func didReceiveMemoryWarning() { 21 | super.didReceiveMemoryWarning() 22 | // Dispose of any resources that can be recreated. 23 | } 24 | 25 | override var childViewControllerForStatusBarStyle: UIViewController? { 26 | return topViewController 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Gank/Controllers/Side/SideViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SideViewController.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/24. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SideViewController: UITableViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | 16 | // Do any additional setup after loading the view. 17 | } 18 | 19 | override func didReceiveMemoryWarning() { 20 | super.didReceiveMemoryWarning() 21 | // Dispose of any resources that can be recreated. 22 | } 23 | } 24 | 25 | extension SideViewController { 26 | 27 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 28 | NotificationCenter.default.post(name: NSNotification.Name.category, object: indexPath) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Gank/Controllers/Web/BrowserWebViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BrowserWebViewController.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/21. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SnapKit 11 | import WebKit 12 | 13 | final class BrowserWebViewController: UIViewController { 14 | 15 | let webView = WKWebView() 16 | var webURL: URL 17 | init(url: URL) { 18 | webURL = url 19 | super.init(nibName: nil, bundle: nil) 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | // Do any additional setup after loading the view. 29 | do /** Config UI */ { 30 | webView.navigationDelegate = self 31 | webView.uiDelegate = self 32 | view.addSubview(webView) 33 | webView.snp.makeConstraints({ (make) in 34 | make.edges.equalTo(view) 35 | }) 36 | } 37 | do /** Config Data */ { 38 | webView.load(URLRequest(url: webURL)) 39 | } 40 | } 41 | 42 | override func viewWillAppear(_ animated: Bool) { 43 | super.viewWillAppear(animated) 44 | navigationController?.setNavigationBarHidden(false, animated: true) 45 | } 46 | 47 | override var preferredStatusBarStyle: UIStatusBarStyle { 48 | return .lightContent 49 | } 50 | 51 | } 52 | 53 | extension BrowserWebViewController: WKUIDelegate { 54 | 55 | } 56 | 57 | extension BrowserWebViewController: WKNavigationDelegate { 58 | 59 | func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { 60 | UIApplication.shared.isNetworkActivityIndicatorVisible = true 61 | } 62 | 63 | func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { 64 | UIApplication.shared.isNetworkActivityIndicatorVisible = false 65 | webView.evaluateJavaScript("document.title", completionHandler: {(response, _) in 66 | self.title = response as? String 67 | }) 68 | } 69 | 70 | func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { 71 | UIApplication.shared.isNetworkActivityIndicatorVisible = false 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Gank/Extensions/Response+ObjectMapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Observable+ObjectMapper.swift 3 | // 4 | // Created by Ivan Bruel on 09/12/15. 5 | // Copyright © 2015 Ivan Bruel. All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import Moya 10 | import ObjectMapper 11 | 12 | public extension Response { 13 | 14 | /// Maps data received from the signal into an object which implements the Mappable protocol. 15 | /// If the conversion fails, the signal errors. 16 | public func mapObject(_ type: T.Type) throws -> T { 17 | guard let object = Mapper().map(JSONObject: try mapJSON()) else { 18 | throw MoyaError.jsonMapping(self) 19 | } 20 | return object 21 | } 22 | 23 | /// Maps data received from the signal into an array of objects which implement the Mappable 24 | /// protocol. 25 | /// If the conversion fails, the signal errors. 26 | public func mapArray(_ type: T.Type) throws -> [T] { 27 | 28 | guard let json = try mapJSON() as? [String: Any], 29 | let result = json["results"] as? [[String : Any]] 30 | else { 31 | throw MoyaError.jsonMapping(self) 32 | } 33 | return Mapper().mapArray(JSONArray: (result)) 34 | } 35 | 36 | } 37 | 38 | // MARK: - ImmutableMappable 39 | 40 | public extension Response { 41 | 42 | /// Maps data received from the signal into an object which implements the ImmutableMappable 43 | /// protocol. 44 | /// If the conversion fails, the signal errors. 45 | public func mapObject(_ type: T.Type) throws -> T { 46 | return try Mapper().map(JSONObject: try mapJSON()) 47 | } 48 | 49 | /// Maps data received from the signal into an array of objects which implement the ImmutableMappable 50 | /// protocol. 51 | /// If the conversion fails, the signal errors. 52 | public func mapArray(_ type: T.Type) throws -> [T] { 53 | guard let array = try mapJSON() as? [[String : Any]] else { 54 | throw MoyaError.jsonMapping(self) 55 | } 56 | return try Mapper().mapArray(JSONArray: array) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Gank/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIStatusBarStyle 37 | UIStatusBarStyleLightContent 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Gank/Models/Home/Brick.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Brick.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/2. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxDataSources 11 | import ObjectMapper 12 | 13 | struct Brick: Equatable, Mappable { 14 | 15 | var id: String = "" 16 | var createdAt: Date = Date() 17 | var desc: String = "" 18 | var publishedAt: Date = Date() 19 | var source: String = "" 20 | var type: String = "" 21 | var url: String = "" 22 | var used: String = "" 23 | var who: String = "" 24 | var images: [String] = [] 25 | 26 | public static func == (lhs: Brick, rhs: Brick) -> Bool { 27 | return lhs.id == rhs.id ? true : false 28 | } 29 | 30 | init?(map: Map) { } 31 | 32 | init() { } 33 | 34 | mutating func mapping(map: Map) { 35 | id <- map["id"] 36 | createdAt <- map["createdAt"] 37 | desc <- map["desc"] 38 | publishedAt <- map["publishedAt"] 39 | source <- map["source"] 40 | type <- map["type"] 41 | url <- map["url"] 42 | used <- map["used"] 43 | who <- map["who"] 44 | images <- map["images"] 45 | } 46 | 47 | } 48 | 49 | struct HomeSection { 50 | 51 | var items: [Item] 52 | } 53 | 54 | extension HomeSection: SectionModelType { 55 | 56 | typealias Item = Brick 57 | 58 | init(original: HomeSection, items: [HomeSection.Item]) { 59 | self = original 60 | self.items = items 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Gank/Protocol/ViewModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModelType.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2017/3/21. 6 | // Copyright © 2017年 Maru. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ViewModelType { 12 | 13 | associatedtype Input 14 | associatedtype Output 15 | 16 | func transform(input: Input) -> Output 17 | } 18 | -------------------------------------------------------------------------------- /Gank/Services/Network/GankAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkService.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/5. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Moya 11 | 12 | enum GankAPI { 13 | 14 | enum GankCategory: String { 15 | 16 | case all = "all" 17 | case android = "Android" 18 | case iOS = "iOS" 19 | case video = "休息视频" 20 | case welfare = "福利" 21 | case resource = "拓展资源" 22 | case frontEnd = "前端" 23 | case mass = "瞎推荐" 24 | case app = "App" 25 | 26 | static func mapCategory(with hashValue: Int) -> GankCategory { 27 | switch hashValue { 28 | case 0: 29 | return .all 30 | case 1: 31 | return .android 32 | case 2: 33 | return .iOS 34 | case 3: 35 | return .video 36 | case 4: 37 | return .welfare 38 | case 5: 39 | return .resource 40 | case 6: 41 | return .frontEnd 42 | case 7: 43 | return .mass 44 | case 8: 45 | return .app 46 | default: 47 | return .all 48 | } 49 | } 50 | } 51 | 52 | case data(type: GankCategory, size: Int64, index: Int64) 53 | } 54 | 55 | extension GankAPI: TargetType { 56 | 57 | /// The method used for parameter encoding. 58 | var parameterEncoding: ParameterEncoding { 59 | return URLEncoding.default 60 | } 61 | 62 | 63 | var baseURL: URL { return URL(string: "http://gank.io")! } 64 | 65 | var path: String { 66 | switch self { 67 | case .data(let type, let size, let index): 68 | return "/api/data/\(type.rawValue)/\(size)/\(index)" 69 | } 70 | } 71 | 72 | var method: Moya.Method { 73 | switch self { 74 | default: 75 | return .get 76 | } 77 | } 78 | 79 | var sampleData: Data { 80 | return "this is a sample data".utf8EncodedData 81 | } 82 | 83 | var parameters: [String : Any]? { 84 | return nil 85 | } 86 | 87 | var task: Task { 88 | switch self { 89 | case .data(_, _, _): 90 | return .request 91 | } 92 | } 93 | } 94 | 95 | let gankApi = RxMoyaProvider() 96 | 97 | // MARK: - Helpers 98 | 99 | private extension String { 100 | 101 | var utf8EncodedData: Data { 102 | return self.data(using: .utf8)! 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Gank/ViewModels/Home/HomeViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewModel.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/7. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import Moya 13 | import NSObject_Rx 14 | 15 | typealias GankType = GankAPI.GankCategory 16 | 17 | final class HomeViewModel: NSObject, ViewModelType { 18 | 19 | typealias Input = HomeInput 20 | typealias Output = HomeOutput 21 | 22 | // Inputs 23 | struct HomeInput { 24 | let category = Variable(0) 25 | } 26 | 27 | // Output 28 | struct HomeOutput { 29 | let section: Driver<[HomeSection]> 30 | let refreshCommand = PublishSubject() 31 | let refreshTrigger = PublishSubject() 32 | 33 | init(homeSection: Driver<[HomeSection]>) { 34 | section = homeSection 35 | } 36 | } 37 | 38 | // Public Stuff 39 | var itemURLs = Variable<[URL]>([]) 40 | // Private Stuff 41 | fileprivate let _bricks = Variable<[Brick]>([]) 42 | 43 | /// Tansform Action for DataBinding 44 | func transform(input: HomeViewModel.Input) -> HomeViewModel.Output { 45 | let section = _bricks.asObservable().map({ (bricks) -> [HomeSection] in 46 | return [HomeSection(items: bricks)] 47 | }) 48 | .asDriver(onErrorJustReturn: []) 49 | let output = Output(homeSection: section) 50 | output.refreshCommand 51 | .flatMapLatest { gankApi.request(.data(type: GankType.mapCategory(with: $0), size: 20, index: 0)) } 52 | .subscribe({ [weak self] (event) in 53 | output.refreshTrigger.onNext() 54 | switch event { 55 | case let .next(response): 56 | do { 57 | let data = try response.mapArray(Brick.self) 58 | self?._bricks.value = data 59 | } catch { 60 | self?._bricks.value = [] 61 | } 62 | break 63 | case let .error(error): 64 | output.refreshTrigger.onError(error) 65 | break 66 | default: 67 | break 68 | } 69 | }) 70 | .disposed(by: rx.disposeBag) 71 | 72 | return output 73 | } 74 | 75 | override init() { 76 | super.init() 77 | _bricks.asObservable().map { (bricks) -> [URL] in 78 | return bricks.map({ (brick) -> URL in 79 | return URL(string: brick.url) ?? URL(string: "https://www.baidu.com")! 80 | }) 81 | }.subscribe(onNext: { [weak self] (urls) in 82 | self?.itemURLs.value = urls 83 | }, onError: nil, onCompleted: nil, onDisposed: nil) 84 | .disposed(by: rx.disposeBag) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Gank/Views/home/HomeTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeTableViewCell.swift 3 | // Gank 4 | // 5 | // Created by Maru on 2016/12/2. 6 | // Copyright © 2016年 Maru. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Reusable 11 | 12 | final class HomeTableViewCell: UITableViewCell, NibReusable { 13 | 14 | @IBOutlet weak var gankTitle: UILabel! 15 | @IBOutlet weak var gankAuthor: UILabel! 16 | @IBOutlet weak var gankTime: UILabel! 17 | 18 | static let height: CGFloat = UITableViewAutomaticDimension 19 | 20 | required init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | } 23 | 24 | override init(style: UITableViewCellStyle, reuseIdentifier: String?) { 25 | super.init(style: style, reuseIdentifier: reuseIdentifier) 26 | } 27 | 28 | override func awakeFromNib() { 29 | super.awakeFromNib() 30 | // Initialization code 31 | } 32 | 33 | override func setSelected(_ selected: Bool, animated: Bool) { 34 | super.setSelected(selected, animated: animated) 35 | 36 | // Configure the view for the selected state 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Gank/Views/home/HomeTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Maru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '9.0' 3 | use_frameworks! 4 | 5 | def shared_pods 6 | pod 'Moya', '8.0.5' 7 | pod 'Moya/RxSwift' 8 | pod 'RxSwift', '3.6.1' 9 | pod 'RxDataSources', '2.0.2' 10 | pod 'Reusable', '3.0.0' 11 | pod 'Kingfisher', '~> 3.10.0' 12 | pod 'Alamofire', '4.5.1' 13 | pod 'Then', '~> 2.1' 14 | pod 'SnapKit', '~> 3.0.2' 15 | pod 'EZSwiftExtensions', '1.11' 16 | pod 'HMSegmentedControl', '1.5.4' 17 | pod 'PullToRefresher', '~> 2.0' 18 | pod 'Action', '3.2.0' 19 | pod 'RxOptional', '3.2.0' 20 | pod 'ObjectMapper', '2.2.8' 21 | pod 'NoticeBar', '0.1.5' 22 | pod 'SwiftWebVC', '0.4.1' 23 | pod 'SideMenu', '2.3.3' 24 | pod 'SwiftyBeaver', '1.4.1' 25 | pod 'NSObject+Rx', '3.0.0' 26 | end 27 | 28 | target :'Gank' do 29 | shared_pods 30 | end 31 | 32 | post_install do |installer| 33 | installer.pods_project.targets.each do |target| 34 | target.build_configurations.each do |config| 35 | config.build_settings['SWIFT_VERSION'] = '3.0' 36 | end 37 | end 38 | end 39 | 40 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Action (3.2.0): 3 | - RxCocoa (~> 3.4) 4 | - RxSwift (~> 3.4) 5 | - Alamofire (4.5.1) 6 | - Differentiator (2.0.2) 7 | - EZSwiftExtensions (1.11) 8 | - HMSegmentedControl (1.5.4) 9 | - Kingfisher (3.10.4) 10 | - Moya (8.0.5): 11 | - Moya/Core (= 8.0.5) 12 | - Moya/Core (8.0.5): 13 | - Alamofire (~> 4.1) 14 | - Result (~> 3.0) 15 | - Moya/RxSwift (8.0.5): 16 | - Moya/Core 17 | - RxSwift (~> 3.0) 18 | - NoticeBar (0.1.5) 19 | - NSObject+Rx (3.0.0): 20 | - RxSwift (~> 3.0) 21 | - ObjectMapper (2.2.8) 22 | - PullToRefresher (2.0.2) 23 | - Result (3.2.3) 24 | - Reusable (3.0.0): 25 | - Reusable/Storyboard (= 3.0.0) 26 | - Reusable/View (= 3.0.0) 27 | - Reusable/Storyboard (3.0.0) 28 | - Reusable/View (3.0.0) 29 | - RxCocoa (3.6.1): 30 | - RxSwift (~> 3.6) 31 | - RxDataSources (2.0.2): 32 | - Differentiator (~> 2.0) 33 | - RxCocoa (>= 3.6.1) 34 | - RxSwift (>= 3.6.1) 35 | - RxOptional (3.2.0): 36 | - RxCocoa 37 | - RxSwift 38 | - RxSwift (3.6.1) 39 | - SideMenu (2.3.3) 40 | - SnapKit (3.0.2) 41 | - SwiftWebVC (0.4.1) 42 | - SwiftyBeaver (1.4.1) 43 | - Then (2.1.1) 44 | 45 | DEPENDENCIES: 46 | - Action 47 | - Alamofire 48 | - EZSwiftExtensions 49 | - HMSegmentedControl 50 | - Kingfisher (~> 3.10.0) 51 | - Moya (~> 8.0.0-beta.5) 52 | - Moya/RxSwift 53 | - NoticeBar 54 | - NSObject+Rx 55 | - ObjectMapper 56 | - PullToRefresher (~> 2.0) 57 | - Reusable (= 3.0.0) 58 | - RxDataSources 59 | - RxOptional 60 | - RxSwift 61 | - SideMenu 62 | - SnapKit (~> 3.0.2) 63 | - SwiftWebVC 64 | - SwiftyBeaver 65 | - Then (~> 2.1) 66 | 67 | SPEC CHECKSUMS: 68 | Action: 991c9e175e04675028724868768aa08f22c85476 69 | Alamofire: 2d95912bf4c34f164fdfc335872e8c312acaea4a 70 | Differentiator: caa9b3957981a2ac8e6b2eac8a676af461caa604 71 | EZSwiftExtensions: 84000042202d9fd574f626cda3064143f27fa767 72 | HMSegmentedControl: b25bc4ec44766e073d62c473188fb354ee244b76 73 | Kingfisher: dc3a4983e86d2dfa89513664983a82340c11a289 74 | Moya: c37eec09a098ba9991b5a963b291fc5704bdb9ef 75 | NoticeBar: ba347fd0ec259e08480d781871648c6842ff8c57 76 | NSObject+Rx: 9c60a6e602ca647c0b2b34403383c305b1c6edc5 77 | ObjectMapper: 3d571bb5af471c779e1160828cd9ad5c4ef90958 78 | PullToRefresher: 5e101a8c3523e67ee0291375ba932ef59e0853c2 79 | Result: 128640a6347e8d2ae48b142556739a2d13f90ce6 80 | Reusable: ab77c4c51a4b9606946df70e30cb8ad9cf75c226 81 | RxCocoa: 84a08739ab186248c7f31ce4ee92d6f8a947d690 82 | RxDataSources: 7f190474a93a206c082c2d74c4cc9c8f7bea4feb 83 | RxOptional: d2d6a68064bc16b4801332c40663967f481b0234 84 | RxSwift: f9de85ea20cd2f7716ee5409fc13523dc638e4e4 85 | SideMenu: a447554ae5c982e8077666389e1c8b0c8656e065 86 | SnapKit: 2e456761aa92d4d4067a7a5594c18769d451a8ad 87 | SwiftWebVC: 17ad75e1fac143d421e0498fb40d7e37532f26d1 88 | SwiftyBeaver: f04f739cca6826c5fb0f575fed8fa3293f76b83e 89 | Then: 823a7503a851737269ba8d40e688b4db874dbd2d 90 | 91 | PODFILE CHECKSUM: 49fab69f7bf995f63e3136ade5fa95156391fd6c 92 | 93 | COCOAPODS: 1.4.0 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gank 2 | A iOS Client of gank.io written in Swift4.0 + MVVM + RxSwift. 3 | 4 | 5 | # Screenshot 6 | 7 | 首页 | 侧边栏 | 详情 8 | -------------|-------------|------------- 9 | ![Home](https://github.com/Maru-zhang/Gank/blob/master/Screenshot/01.png)|![Slider](https://github.com/Maru-zhang/Gank/blob/master/Screenshot/02.png)|![Detail](https://github.com/Maru-zhang/Gank/blob/master/Screenshot/03.png) 10 | 11 | # LICENSE 12 | 13 | Gank is under the MIT license. See LICENSE for details. 14 | 15 | -------------------------------------------------------------------------------- /Screenshot/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Screenshot/01.png -------------------------------------------------------------------------------- /Screenshot/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Screenshot/02.png -------------------------------------------------------------------------------- /Screenshot/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maru-zhang/Gank/32cedd22f1840dd2db5b8c16cead236377c8a9b2/Screenshot/03.png --------------------------------------------------------------------------------