├── .gitignore ├── .swift-version ├── Chidori.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── Navi.xcscheme ├── Chidori ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-29.png │ │ ├── Icon-29@2x-1.png │ │ ├── Icon-29@2x.png │ │ ├── Icon-29@3x.png │ │ ├── Icon-40.png │ │ ├── Icon-40@2x-1.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76.png │ │ └── Icon-76@2x.png │ ├── Contents.json │ ├── round_avatar_placeholder.imageset │ │ ├── Contents.json │ │ └── round_avatar_placeholder.pdf │ └── square_avatar_placeholder.imageset │ │ ├── Contents.json │ │ └── square_avatar_placeholder.pdf ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Extensions │ └── UIFont+Chidori.swift ├── Helpers │ ├── Config.swift │ └── Filter.swift ├── Info.plist ├── ViewControllers │ └── AvatarsViewController.swift └── Views │ └── Cells │ └── Avatar │ ├── AvatarCell.swift │ └── AvatarCell.xib ├── LICENSE ├── Navi.podspec ├── Navi ├── Avatar.swift ├── AvatarPod.swift ├── Info.plist ├── Navi.h ├── UIImage+Navi.swift └── UIImageView+Navi.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | ## Build generated 5 | build/ 6 | DerivedData 7 | 8 | ## Various settings 9 | *.pbxuser 10 | !default.pbxuser 11 | *.mode1v3 12 | !default.mode1v3 13 | *.mode2v3 14 | !default.mode2v3 15 | *.perspectivev3 16 | !default.perspectivev3 17 | xcuserdata 18 | 19 | ## Other 20 | *.xccheckout 21 | *.moved-aside 22 | *.xcuserstate 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | 29 | # CocoaPods 30 | Pods/ 31 | 32 | # Carthage 33 | Carthage/Checkouts 34 | Carthage/Build 35 | 36 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Chidori.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 502A2D4B1BB7A19300B3A6B1 /* UIImage+Navi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502A2D4A1BB7A19300B3A6B1 /* UIImage+Navi.swift */; }; 11 | 5057A2921BBFE5C700D3A876 /* UIFont+Chidori.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5057A2911BBFE5C700D3A876 /* UIFont+Chidori.swift */; }; 12 | 5057A29B1BC13D8100D3A876 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5057A29A1BC13D8100D3A876 /* Config.swift */; }; 13 | 505A55DF1BB63CFD00159CD0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505A55DE1BB63CFD00159CD0 /* AppDelegate.swift */; }; 14 | 505A55E41BB63CFD00159CD0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 505A55E21BB63CFD00159CD0 /* Main.storyboard */; }; 15 | 505A55E61BB63CFD00159CD0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 505A55E51BB63CFD00159CD0 /* Assets.xcassets */; }; 16 | 505A55E91BB63CFD00159CD0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 505A55E71BB63CFD00159CD0 /* LaunchScreen.storyboard */; }; 17 | 505A55F81BB63F2E00159CD0 /* Navi.h in Headers */ = {isa = PBXBuildFile; fileRef = 505A55F71BB63F2E00159CD0 /* Navi.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18 | 505A560A1BB63F2E00159CD0 /* Navi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 505A55F51BB63F2E00159CD0 /* Navi.framework */; }; 19 | 505A560B1BB63F2E00159CD0 /* Navi.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 505A55F51BB63F2E00159CD0 /* Navi.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 20 | 505A56141BB6404800159CD0 /* AvatarPod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505A56131BB6404800159CD0 /* AvatarPod.swift */; }; 21 | 505A56181BB640E600159CD0 /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505A56171BB640E500159CD0 /* Avatar.swift */; }; 22 | 505A561A1BB6559800159CD0 /* AvatarsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505A56191BB6559800159CD0 /* AvatarsViewController.swift */; }; 23 | 505A561E1BB6566F00159CD0 /* AvatarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505A561C1BB6566F00159CD0 /* AvatarCell.swift */; }; 24 | 505A561F1BB6566F00159CD0 /* AvatarCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 505A561D1BB6566F00159CD0 /* AvatarCell.xib */; }; 25 | 50970C9C1BBD252D001BFB19 /* UIImageView+Navi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50970C9B1BBD252D001BFB19 /* UIImageView+Navi.swift */; }; 26 | 50970CB11BBE9ECA001BFB19 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50970CB01BBE9ECA001BFB19 /* Filter.swift */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | 505A56081BB63F2E00159CD0 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 505A55D31BB63CFD00159CD0 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 505A55F41BB63F2E00159CD0; 35 | remoteInfo = Navi; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXCopyFilesBuildPhase section */ 40 | 505A56111BB63F2E00159CD0 /* Embed Frameworks */ = { 41 | isa = PBXCopyFilesBuildPhase; 42 | buildActionMask = 2147483647; 43 | dstPath = ""; 44 | dstSubfolderSpec = 10; 45 | files = ( 46 | 505A560B1BB63F2E00159CD0 /* Navi.framework in Embed Frameworks */, 47 | ); 48 | name = "Embed Frameworks"; 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXCopyFilesBuildPhase section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | 502A2D4A1BB7A19300B3A6B1 /* UIImage+Navi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Navi.swift"; sourceTree = ""; }; 55 | 5057A2911BBFE5C700D3A876 /* UIFont+Chidori.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "UIFont+Chidori.swift"; path = "Extensions/UIFont+Chidori.swift"; sourceTree = ""; }; 56 | 5057A29A1BC13D8100D3A876 /* Config.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Config.swift; path = Helpers/Config.swift; sourceTree = ""; }; 57 | 505A55DB1BB63CFD00159CD0 /* Chidori.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Chidori.app; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 505A55DE1BB63CFD00159CD0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 59 | 505A55E31BB63CFD00159CD0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 60 | 505A55E51BB63CFD00159CD0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 61 | 505A55E81BB63CFD00159CD0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 505A55EA1BB63CFD00159CD0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 505A55F51BB63F2E00159CD0 /* Navi.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Navi.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 505A55F71BB63F2E00159CD0 /* Navi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Navi.h; sourceTree = ""; }; 65 | 505A55F91BB63F2E00159CD0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | 505A56131BB6404800159CD0 /* AvatarPod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarPod.swift; sourceTree = ""; }; 67 | 505A56171BB640E500159CD0 /* Avatar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Avatar.swift; sourceTree = ""; }; 68 | 505A56191BB6559800159CD0 /* AvatarsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AvatarsViewController.swift; path = ViewControllers/AvatarsViewController.swift; sourceTree = ""; }; 69 | 505A561C1BB6566F00159CD0 /* AvatarCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AvatarCell.swift; path = Views/Cells/Avatar/AvatarCell.swift; sourceTree = ""; }; 70 | 505A561D1BB6566F00159CD0 /* AvatarCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = AvatarCell.xib; path = Views/Cells/Avatar/AvatarCell.xib; sourceTree = ""; }; 71 | 50970C9B1BBD252D001BFB19 /* UIImageView+Navi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Navi.swift"; sourceTree = ""; }; 72 | 50970CB01BBE9ECA001BFB19 /* Filter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Filter.swift; path = Helpers/Filter.swift; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | 505A55D81BB63CFD00159CD0 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | 505A560A1BB63F2E00159CD0 /* Navi.framework in Frameworks */, 81 | ); 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | 505A55F11BB63F2E00159CD0 /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 5057A2931BBFE5CC00D3A876 /* Extensions */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 5057A2911BBFE5C700D3A876 /* UIFont+Chidori.swift */, 98 | ); 99 | name = Extensions; 100 | sourceTree = ""; 101 | }; 102 | 505A55D21BB63CFC00159CD0 = { 103 | isa = PBXGroup; 104 | children = ( 105 | 505A55DD1BB63CFD00159CD0 /* Chidori */, 106 | 505A55F61BB63F2E00159CD0 /* Navi */, 107 | 505A55DC1BB63CFD00159CD0 /* Products */, 108 | ); 109 | sourceTree = ""; 110 | }; 111 | 505A55DC1BB63CFD00159CD0 /* Products */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 505A55DB1BB63CFD00159CD0 /* Chidori.app */, 115 | 505A55F51BB63F2E00159CD0 /* Navi.framework */, 116 | ); 117 | name = Products; 118 | sourceTree = ""; 119 | }; 120 | 505A55DD1BB63CFD00159CD0 /* Chidori */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 505A55DE1BB63CFD00159CD0 /* AppDelegate.swift */, 124 | 50970CB21BBE9ECE001BFB19 /* Helpers */, 125 | 5057A2931BBFE5CC00D3A876 /* Extensions */, 126 | 505A56201BB6567200159CD0 /* Views */, 127 | 505A561B1BB6559D00159CD0 /* ViewControllers */, 128 | 505A55E21BB63CFD00159CD0 /* Main.storyboard */, 129 | 505A55E51BB63CFD00159CD0 /* Assets.xcassets */, 130 | 505A55E71BB63CFD00159CD0 /* LaunchScreen.storyboard */, 131 | 505A55EA1BB63CFD00159CD0 /* Info.plist */, 132 | ); 133 | path = Chidori; 134 | sourceTree = ""; 135 | }; 136 | 505A55F61BB63F2E00159CD0 /* Navi */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 505A55F71BB63F2E00159CD0 /* Navi.h */, 140 | 505A56171BB640E500159CD0 /* Avatar.swift */, 141 | 505A56131BB6404800159CD0 /* AvatarPod.swift */, 142 | 502A2D4A1BB7A19300B3A6B1 /* UIImage+Navi.swift */, 143 | 50970C9B1BBD252D001BFB19 /* UIImageView+Navi.swift */, 144 | 505A55F91BB63F2E00159CD0 /* Info.plist */, 145 | ); 146 | path = Navi; 147 | sourceTree = ""; 148 | }; 149 | 505A561B1BB6559D00159CD0 /* ViewControllers */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | 505A56191BB6559800159CD0 /* AvatarsViewController.swift */, 153 | ); 154 | name = ViewControllers; 155 | sourceTree = ""; 156 | }; 157 | 505A56201BB6567200159CD0 /* Views */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 505A56211BB6567B00159CD0 /* Cells */, 161 | ); 162 | name = Views; 163 | sourceTree = ""; 164 | }; 165 | 505A56211BB6567B00159CD0 /* Cells */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 505A56221BB6568700159CD0 /* Avatar */, 169 | ); 170 | name = Cells; 171 | sourceTree = ""; 172 | }; 173 | 505A56221BB6568700159CD0 /* Avatar */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 505A561C1BB6566F00159CD0 /* AvatarCell.swift */, 177 | 505A561D1BB6566F00159CD0 /* AvatarCell.xib */, 178 | ); 179 | name = Avatar; 180 | sourceTree = ""; 181 | }; 182 | 50970CB21BBE9ECE001BFB19 /* Helpers */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | 50970CB01BBE9ECA001BFB19 /* Filter.swift */, 186 | 5057A29A1BC13D8100D3A876 /* Config.swift */, 187 | ); 188 | name = Helpers; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXGroup section */ 192 | 193 | /* Begin PBXHeadersBuildPhase section */ 194 | 505A55F21BB63F2E00159CD0 /* Headers */ = { 195 | isa = PBXHeadersBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 505A55F81BB63F2E00159CD0 /* Navi.h in Headers */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXHeadersBuildPhase section */ 203 | 204 | /* Begin PBXNativeTarget section */ 205 | 505A55DA1BB63CFD00159CD0 /* Chidori */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = 505A55ED1BB63CFD00159CD0 /* Build configuration list for PBXNativeTarget "Chidori" */; 208 | buildPhases = ( 209 | 505A55D71BB63CFD00159CD0 /* Sources */, 210 | 505A55D81BB63CFD00159CD0 /* Frameworks */, 211 | 505A55D91BB63CFD00159CD0 /* Resources */, 212 | 505A56111BB63F2E00159CD0 /* Embed Frameworks */, 213 | ); 214 | buildRules = ( 215 | ); 216 | dependencies = ( 217 | 505A56091BB63F2E00159CD0 /* PBXTargetDependency */, 218 | ); 219 | name = Chidori; 220 | productName = Chidori; 221 | productReference = 505A55DB1BB63CFD00159CD0 /* Chidori.app */; 222 | productType = "com.apple.product-type.application"; 223 | }; 224 | 505A55F41BB63F2E00159CD0 /* Navi */ = { 225 | isa = PBXNativeTarget; 226 | buildConfigurationList = 505A56101BB63F2E00159CD0 /* Build configuration list for PBXNativeTarget "Navi" */; 227 | buildPhases = ( 228 | 505A55F01BB63F2E00159CD0 /* Sources */, 229 | 505A55F11BB63F2E00159CD0 /* Frameworks */, 230 | 505A55F21BB63F2E00159CD0 /* Headers */, 231 | 505A55F31BB63F2E00159CD0 /* Resources */, 232 | ); 233 | buildRules = ( 234 | ); 235 | dependencies = ( 236 | ); 237 | name = Navi; 238 | productName = Navi; 239 | productReference = 505A55F51BB63F2E00159CD0 /* Navi.framework */; 240 | productType = "com.apple.product-type.framework"; 241 | }; 242 | /* End PBXNativeTarget section */ 243 | 244 | /* Begin PBXProject section */ 245 | 505A55D31BB63CFD00159CD0 /* Project object */ = { 246 | isa = PBXProject; 247 | attributes = { 248 | LastSwiftUpdateCheck = 0700; 249 | LastUpgradeCheck = 0900; 250 | ORGANIZATIONNAME = nixWork; 251 | TargetAttributes = { 252 | 505A55DA1BB63CFD00159CD0 = { 253 | CreatedOnToolsVersion = 7.0; 254 | DevelopmentTeam = 8D957V42M6; 255 | LastSwiftMigration = 0800; 256 | }; 257 | 505A55F41BB63F2E00159CD0 = { 258 | CreatedOnToolsVersion = 7.0; 259 | LastSwiftMigration = 0800; 260 | }; 261 | }; 262 | }; 263 | buildConfigurationList = 505A55D61BB63CFD00159CD0 /* Build configuration list for PBXProject "Chidori" */; 264 | compatibilityVersion = "Xcode 3.2"; 265 | developmentRegion = English; 266 | hasScannedForEncodings = 0; 267 | knownRegions = ( 268 | en, 269 | Base, 270 | ); 271 | mainGroup = 505A55D21BB63CFC00159CD0; 272 | productRefGroup = 505A55DC1BB63CFD00159CD0 /* Products */; 273 | projectDirPath = ""; 274 | projectRoot = ""; 275 | targets = ( 276 | 505A55DA1BB63CFD00159CD0 /* Chidori */, 277 | 505A55F41BB63F2E00159CD0 /* Navi */, 278 | ); 279 | }; 280 | /* End PBXProject section */ 281 | 282 | /* Begin PBXResourcesBuildPhase section */ 283 | 505A55D91BB63CFD00159CD0 /* Resources */ = { 284 | isa = PBXResourcesBuildPhase; 285 | buildActionMask = 2147483647; 286 | files = ( 287 | 505A55E91BB63CFD00159CD0 /* LaunchScreen.storyboard in Resources */, 288 | 505A55E61BB63CFD00159CD0 /* Assets.xcassets in Resources */, 289 | 505A561F1BB6566F00159CD0 /* AvatarCell.xib in Resources */, 290 | 505A55E41BB63CFD00159CD0 /* Main.storyboard in Resources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | 505A55F31BB63F2E00159CD0 /* Resources */ = { 295 | isa = PBXResourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | /* End PBXResourcesBuildPhase section */ 302 | 303 | /* Begin PBXSourcesBuildPhase section */ 304 | 505A55D71BB63CFD00159CD0 /* Sources */ = { 305 | isa = PBXSourcesBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | 5057A29B1BC13D8100D3A876 /* Config.swift in Sources */, 309 | 505A55DF1BB63CFD00159CD0 /* AppDelegate.swift in Sources */, 310 | 5057A2921BBFE5C700D3A876 /* UIFont+Chidori.swift in Sources */, 311 | 505A561E1BB6566F00159CD0 /* AvatarCell.swift in Sources */, 312 | 50970CB11BBE9ECA001BFB19 /* Filter.swift in Sources */, 313 | 505A561A1BB6559800159CD0 /* AvatarsViewController.swift in Sources */, 314 | ); 315 | runOnlyForDeploymentPostprocessing = 0; 316 | }; 317 | 505A55F01BB63F2E00159CD0 /* Sources */ = { 318 | isa = PBXSourcesBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | 505A56181BB640E600159CD0 /* Avatar.swift in Sources */, 322 | 505A56141BB6404800159CD0 /* AvatarPod.swift in Sources */, 323 | 50970C9C1BBD252D001BFB19 /* UIImageView+Navi.swift in Sources */, 324 | 502A2D4B1BB7A19300B3A6B1 /* UIImage+Navi.swift in Sources */, 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | }; 328 | /* End PBXSourcesBuildPhase section */ 329 | 330 | /* Begin PBXTargetDependency section */ 331 | 505A56091BB63F2E00159CD0 /* PBXTargetDependency */ = { 332 | isa = PBXTargetDependency; 333 | target = 505A55F41BB63F2E00159CD0 /* Navi */; 334 | targetProxy = 505A56081BB63F2E00159CD0 /* PBXContainerItemProxy */; 335 | }; 336 | /* End PBXTargetDependency section */ 337 | 338 | /* Begin PBXVariantGroup section */ 339 | 505A55E21BB63CFD00159CD0 /* Main.storyboard */ = { 340 | isa = PBXVariantGroup; 341 | children = ( 342 | 505A55E31BB63CFD00159CD0 /* Base */, 343 | ); 344 | name = Main.storyboard; 345 | sourceTree = ""; 346 | }; 347 | 505A55E71BB63CFD00159CD0 /* LaunchScreen.storyboard */ = { 348 | isa = PBXVariantGroup; 349 | children = ( 350 | 505A55E81BB63CFD00159CD0 /* Base */, 351 | ); 352 | name = LaunchScreen.storyboard; 353 | sourceTree = ""; 354 | }; 355 | /* End PBXVariantGroup section */ 356 | 357 | /* Begin XCBuildConfiguration section */ 358 | 505A55EB1BB63CFD00159CD0 /* Debug */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ALWAYS_SEARCH_USER_PATHS = NO; 362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 363 | CLANG_CXX_LIBRARY = "libc++"; 364 | CLANG_ENABLE_MODULES = YES; 365 | CLANG_ENABLE_OBJC_ARC = YES; 366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 367 | CLANG_WARN_BOOL_CONVERSION = YES; 368 | CLANG_WARN_COMMA = YES; 369 | CLANG_WARN_CONSTANT_CONVERSION = YES; 370 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INFINITE_RECURSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 377 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 378 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 379 | CLANG_WARN_STRICT_PROTOTYPES = YES; 380 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 381 | CLANG_WARN_UNREACHABLE_CODE = YES; 382 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 383 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 384 | COPY_PHASE_STRIP = NO; 385 | DEBUG_INFORMATION_FORMAT = dwarf; 386 | ENABLE_STRICT_OBJC_MSGSEND = YES; 387 | ENABLE_TESTABILITY = YES; 388 | GCC_C_LANGUAGE_STANDARD = gnu99; 389 | GCC_DYNAMIC_NO_PIC = NO; 390 | GCC_NO_COMMON_BLOCKS = YES; 391 | GCC_OPTIMIZATION_LEVEL = 0; 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "DEBUG=1", 394 | "$(inherited)", 395 | ); 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 403 | MTL_ENABLE_DEBUG_INFO = YES; 404 | ONLY_ACTIVE_ARCH = YES; 405 | SDKROOT = iphoneos; 406 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 407 | SWIFT_VERSION = 4.0; 408 | TARGETED_DEVICE_FAMILY = "1,2"; 409 | }; 410 | name = Debug; 411 | }; 412 | 505A55EC1BB63CFD00159CD0 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ALWAYS_SEARCH_USER_PATHS = NO; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 417 | CLANG_CXX_LIBRARY = "libc++"; 418 | CLANG_ENABLE_MODULES = YES; 419 | CLANG_ENABLE_OBJC_ARC = YES; 420 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 421 | CLANG_WARN_BOOL_CONVERSION = YES; 422 | CLANG_WARN_COMMA = YES; 423 | CLANG_WARN_CONSTANT_CONVERSION = YES; 424 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 425 | CLANG_WARN_EMPTY_BODY = YES; 426 | CLANG_WARN_ENUM_CONVERSION = YES; 427 | CLANG_WARN_INFINITE_RECURSION = YES; 428 | CLANG_WARN_INT_CONVERSION = YES; 429 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 430 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 431 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 432 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 433 | CLANG_WARN_STRICT_PROTOTYPES = YES; 434 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 435 | CLANG_WARN_UNREACHABLE_CODE = YES; 436 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 437 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 438 | COPY_PHASE_STRIP = NO; 439 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 440 | ENABLE_NS_ASSERTIONS = NO; 441 | ENABLE_STRICT_OBJC_MSGSEND = YES; 442 | GCC_C_LANGUAGE_STANDARD = gnu99; 443 | GCC_NO_COMMON_BLOCKS = YES; 444 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 445 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 446 | GCC_WARN_UNDECLARED_SELECTOR = YES; 447 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 448 | GCC_WARN_UNUSED_FUNCTION = YES; 449 | GCC_WARN_UNUSED_VARIABLE = YES; 450 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 451 | MTL_ENABLE_DEBUG_INFO = NO; 452 | SDKROOT = iphoneos; 453 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 454 | SWIFT_VERSION = 4.0; 455 | TARGETED_DEVICE_FAMILY = "1,2"; 456 | VALIDATE_PRODUCT = YES; 457 | }; 458 | name = Release; 459 | }; 460 | 505A55EE1BB63CFD00159CD0 /* Debug */ = { 461 | isa = XCBuildConfiguration; 462 | buildSettings = { 463 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 465 | DEVELOPMENT_TEAM = 8D957V42M6; 466 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 467 | INFOPLIST_FILE = Chidori/Info.plist; 468 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 469 | PRODUCT_BUNDLE_IDENTIFIER = com.nixWork.Chidori; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | SWIFT_VERSION = 4.0; 472 | TARGETED_DEVICE_FAMILY = 1; 473 | }; 474 | name = Debug; 475 | }; 476 | 505A55EF1BB63CFD00159CD0 /* Release */ = { 477 | isa = XCBuildConfiguration; 478 | buildSettings = { 479 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 480 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 481 | DEVELOPMENT_TEAM = 8D957V42M6; 482 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 483 | INFOPLIST_FILE = Chidori/Info.plist; 484 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 485 | PRODUCT_BUNDLE_IDENTIFIER = com.nixWork.Chidori; 486 | PRODUCT_NAME = "$(TARGET_NAME)"; 487 | SWIFT_VERSION = 4.0; 488 | TARGETED_DEVICE_FAMILY = 1; 489 | }; 490 | name = Release; 491 | }; 492 | 505A560C1BB63F2E00159CD0 /* Debug */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | APPLICATION_EXTENSION_API_ONLY = YES; 496 | CLANG_ENABLE_MODULES = YES; 497 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 498 | CURRENT_PROJECT_VERSION = 1; 499 | DEFINES_MODULE = YES; 500 | DYLIB_COMPATIBILITY_VERSION = 1; 501 | DYLIB_CURRENT_VERSION = 1; 502 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 503 | INFOPLIST_FILE = Navi/Info.plist; 504 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 505 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 506 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 507 | PRODUCT_BUNDLE_IDENTIFIER = com.nixWork.Navi; 508 | PRODUCT_NAME = "$(TARGET_NAME)"; 509 | SKIP_INSTALL = YES; 510 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 511 | SWIFT_VERSION = 4.0; 512 | VERSIONING_SYSTEM = "apple-generic"; 513 | VERSION_INFO_PREFIX = ""; 514 | }; 515 | name = Debug; 516 | }; 517 | 505A560D1BB63F2E00159CD0 /* Release */ = { 518 | isa = XCBuildConfiguration; 519 | buildSettings = { 520 | APPLICATION_EXTENSION_API_ONLY = YES; 521 | CLANG_ENABLE_MODULES = YES; 522 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 523 | CURRENT_PROJECT_VERSION = 1; 524 | DEFINES_MODULE = YES; 525 | DYLIB_COMPATIBILITY_VERSION = 1; 526 | DYLIB_CURRENT_VERSION = 1; 527 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 528 | INFOPLIST_FILE = Navi/Info.plist; 529 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 530 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 531 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 532 | PRODUCT_BUNDLE_IDENTIFIER = com.nixWork.Navi; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SKIP_INSTALL = YES; 535 | SWIFT_VERSION = 4.0; 536 | VERSIONING_SYSTEM = "apple-generic"; 537 | VERSION_INFO_PREFIX = ""; 538 | }; 539 | name = Release; 540 | }; 541 | /* End XCBuildConfiguration section */ 542 | 543 | /* Begin XCConfigurationList section */ 544 | 505A55D61BB63CFD00159CD0 /* Build configuration list for PBXProject "Chidori" */ = { 545 | isa = XCConfigurationList; 546 | buildConfigurations = ( 547 | 505A55EB1BB63CFD00159CD0 /* Debug */, 548 | 505A55EC1BB63CFD00159CD0 /* Release */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | 505A55ED1BB63CFD00159CD0 /* Build configuration list for PBXNativeTarget "Chidori" */ = { 554 | isa = XCConfigurationList; 555 | buildConfigurations = ( 556 | 505A55EE1BB63CFD00159CD0 /* Debug */, 557 | 505A55EF1BB63CFD00159CD0 /* Release */, 558 | ); 559 | defaultConfigurationIsVisible = 0; 560 | defaultConfigurationName = Release; 561 | }; 562 | 505A56101BB63F2E00159CD0 /* Build configuration list for PBXNativeTarget "Navi" */ = { 563 | isa = XCConfigurationList; 564 | buildConfigurations = ( 565 | 505A560C1BB63F2E00159CD0 /* Debug */, 566 | 505A560D1BB63F2E00159CD0 /* Release */, 567 | ); 568 | defaultConfigurationIsVisible = 0; 569 | defaultConfigurationName = Release; 570 | }; 571 | /* End XCConfigurationList section */ 572 | }; 573 | rootObject = 505A55D31BB63CFD00159CD0 /* Project object */; 574 | } 575 | -------------------------------------------------------------------------------- /Chidori.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chidori.xcodeproj/xcshareddata/xcschemes/Navi.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 47 | 48 | 54 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 72 | 73 | 74 | 75 | 77 | 78 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /Chidori/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Chidori 4 | // 5 | // Created by NIX on 15/9/26. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 17 | return true 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-29@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-29@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-40@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-40@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-60@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "Icon-29.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "Icon-29@2x-1.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "Icon-40.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-40@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "76x76", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-76.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-76@2x.png", 73 | "scale" : "2x" 74 | } 75 | ], 76 | "info" : { 77 | "version" : 1, 78 | "author" : "xcode" 79 | } 80 | } -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29@2x-1.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40@2x-1.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/round_avatar_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "round_avatar_placeholder.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/round_avatar_placeholder.imageset/round_avatar_placeholder.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/round_avatar_placeholder.imageset/round_avatar_placeholder.pdf -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/square_avatar_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "square_avatar_placeholder.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Chidori/Assets.xcassets/square_avatar_placeholder.imageset/square_avatar_placeholder.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nixzhu/Navi/657f95a709f2b5853eeb73be4da5db8e7a8c0ed3/Chidori/Assets.xcassets/square_avatar_placeholder.imageset/square_avatar_placeholder.pdf -------------------------------------------------------------------------------- /Chidori/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Chidori/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 | -------------------------------------------------------------------------------- /Chidori/Extensions/UIFont+Chidori.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIFont+Chidori.swift 3 | // Chidori 4 | // 5 | // Created by NIX on 15/10/3. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIFont { 12 | 13 | class func tweetMessageFont() -> UIFont { 14 | return UIFont.systemFont(ofSize: 15) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chidori/Helpers/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // Chidori 4 | // 5 | // Created by NIX on 15/10/4. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | struct Config { 10 | 11 | struct Notification { 12 | 13 | static let newUsers = "newUsers" 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Chidori/Helpers/Filter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Filter.swift 3 | // Chidori 4 | // 5 | // Created by NIX on 15/10/2. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreImage 11 | 12 | typealias Filter = (CIImage) -> CIImage 13 | 14 | func blurWithRadius(_ radius: CGFloat) -> Filter { 15 | 16 | return { image in 17 | 18 | let parameters = [ 19 | kCIInputRadiusKey: radius, 20 | kCIInputImageKey: image, 21 | ] as [String : Any] 22 | 23 | let filter = CIFilter(name: "CIGaussianBlur", withInputParameters: parameters) 24 | 25 | return filter!.outputImage! 26 | } 27 | } 28 | 29 | func colorGenerator(_ color: UIColor) -> Filter { 30 | 31 | return { _ in 32 | 33 | let parameters = [ 34 | kCIInputColorKey: CIColor(color: color), 35 | ] 36 | 37 | let filter = CIFilter(name: "CIConstantColorGenerator", withInputParameters: parameters) 38 | 39 | return filter!.outputImage! 40 | } 41 | } 42 | 43 | func compositeSourceOver(_ overlay: CIImage) -> Filter { 44 | return { image in 45 | let parameters = [ 46 | kCIInputBackgroundImageKey: image, 47 | kCIInputImageKey: overlay, 48 | ] 49 | 50 | let filter = CIFilter(name: "CISourceOverCompositing", withInputParameters: parameters) 51 | 52 | let cropRect = image.extent 53 | 54 | return filter!.outputImage!.cropped(to: cropRect) 55 | } 56 | } 57 | 58 | func overlayWithColor(_ color: UIColor) -> Filter { 59 | return { image in 60 | let overlay = colorGenerator(color)(image) 61 | return compositeSourceOver(overlay)(image) 62 | } 63 | } 64 | 65 | precedencegroup FilterPrecedence { 66 | associativity: left 67 | } 68 | 69 | infix operator +++: FilterPrecedence 70 | 71 | func +++(filterA: @escaping Filter, filterB: @escaping Filter) -> Filter { 72 | return { image in 73 | return filterB(filterA(image)) 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /Chidori/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.1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 31 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 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Chidori/ViewControllers/AvatarsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AvatarsViewController.swift 3 | // Chidori 4 | // 5 | // Created by NIX on 15/9/26. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Navi 11 | 12 | struct YepAvatar { 13 | 14 | let avatarURL: URL 15 | } 16 | 17 | extension YepAvatar: Navi.Avatar { 18 | 19 | var url: URL? { 20 | return avatarURL 21 | } 22 | var style: AvatarStyle { 23 | return .roundedRectangle( 24 | size: CGSize(width: 60, height: 60), 25 | cornerRadius: 30, 26 | borderWidth: 0 27 | ) 28 | } 29 | var placeholderImage: UIImage? { 30 | return UIImage(named: "round_avatar_placeholder") 31 | } 32 | var localOriginalImage: UIImage? { 33 | return nil 34 | } 35 | var localStyledImage: UIImage? { 36 | return nil 37 | } 38 | 39 | func save(originalImage: UIImage, styledImage: UIImage) { 40 | // TODO: save images 41 | } 42 | } 43 | 44 | class AvatarsViewController: UICollectionViewController { 45 | 46 | let yepAvatarURLStrings = [ 47 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/84c9a0a9-c6eb-4495-9b50-0d551749956a", 48 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/7cf09c38-355b-4daa-b733-5fede0181e5f", 49 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/76daf547-a38f-410f-88fb-c7aece4fb1c8", 50 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/0e47196b-1656-4b79-8953-457afaca6f7b", 51 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/e2b84ebe-533d-4845-a842-774de98c8504", 52 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/e24117db-d360-4c0b-8159-c908bf216e38", 53 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/0738b75f-b223-4e34-a61c-add693f99f74", 54 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/d88b7c3d-7252-41d5-a7bd-068980257eff", 55 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/134f80a5-d273-4e7c-b490-f0de862c4ac4", 56 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/d0c29846-e064-4b4c-b4aa-bd0bd2d8d435", 57 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/d124dcfe-07ec-4ac6-aaf3-5ba6afd131ad", 58 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/70f6f156-7707-471d-8c98-fcb7d2a6edb1", 59 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/24795538-fc57-428b-843e-211e6b89a00c", 60 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/70a3d702-7769-4616-8410-0a7f5d39d883", 61 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/14902752-2e43-45a1-901b-3a534b1d32b4", 62 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/db49a8c6-dd2f-464d-8d06-03e7268c7fb4", 63 | "https://yep-avatars.s3.cn-north-1.amazonaws.com.cn/4c59a970-2e3d-452a-aa2b-00f46ac0512f", 64 | ] 65 | 66 | let alphaAvatarURLStrings = [ 67 | "http://7xkdk4.com2.z0.glb.qiniucdn.com/pics/avatars/u5561381442825024.jpg?imageView2/1/w/128/h/128", 68 | "http://7xkszy.com2.z0.glb.qiniucdn.com/pics/avatars/u8516711441533445.jpg?imageView2/1/w/128/h/128", 69 | "http://q.qlogo.cn/qqapp/1103866037/C31B48C64369A93E57E6B971F41246DE/100", 70 | ] 71 | 72 | fileprivate let avatarCellID = "AvatarCell" 73 | 74 | deinit { 75 | NotificationCenter.default.removeObserver(self) 76 | } 77 | 78 | override func viewDidLoad() { 79 | super.viewDidLoad() 80 | 81 | title = "Avatars" 82 | 83 | collectionView!.backgroundColor = UIColor.white 84 | collectionView!.register(UINib(nibName: avatarCellID, bundle: nil), forCellWithReuseIdentifier: avatarCellID) 85 | collectionView!.dataSource = self 86 | collectionView!.alwaysBounceVertical = true 87 | 88 | NotificationCenter.default.addObserver(self, selector: #selector(AvatarsViewController.updateCollectionView(_:)), name: NSNotification.Name(rawValue: Config.Notification.newUsers), object: nil) 89 | } 90 | 91 | // MARK: Actions 92 | 93 | @objc func updateCollectionView(_ notification: Notification) { 94 | collectionView?.reloadData() 95 | } 96 | 97 | // MARK: - UICollectionView 98 | 99 | enum Section: Int { 100 | case yep 101 | case alpha 102 | } 103 | 104 | override func numberOfSections(in collectionView: UICollectionView) -> Int { 105 | 106 | return 2 107 | } 108 | 109 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 110 | 111 | switch section { 112 | 113 | case Section.yep.rawValue: 114 | return yepAvatarURLStrings.count 115 | 116 | case Section.alpha.rawValue: 117 | return alphaAvatarURLStrings.count 118 | 119 | default: 120 | return 0 121 | } 122 | } 123 | 124 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 125 | 126 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: avatarCellID, for: indexPath) as! AvatarCell 127 | 128 | return cell 129 | } 130 | 131 | override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 132 | 133 | configureCell(cell as! AvatarCell, atIndexPath: indexPath) 134 | } 135 | 136 | fileprivate func configureCell(_ cell: AvatarCell, atIndexPath indexPath: IndexPath) { 137 | 138 | switch (indexPath as NSIndexPath).section { 139 | 140 | case Section.yep.rawValue: 141 | let avatarURLString = yepAvatarURLStrings[(indexPath as NSIndexPath).item] 142 | let yepAvatar = YepAvatar(avatarURL: URL(string: avatarURLString)!) 143 | cell.configureWithAvatar(yepAvatar) 144 | 145 | case Section.alpha.rawValue: 146 | let avatarURLString = alphaAvatarURLStrings[(indexPath as NSIndexPath).item] 147 | let yepAvatar = YepAvatar(avatarURL: URL(string: avatarURLString)!) 148 | cell.configureWithAvatar(yepAvatar) 149 | 150 | default: 151 | break 152 | } 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /Chidori/Views/Cells/Avatar/AvatarCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AvatarCell.swift 3 | // Chidori 4 | // 5 | // Created by NIX on 15/9/26. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Navi 11 | 12 | class AvatarCell: UICollectionViewCell { 13 | 14 | @IBOutlet weak var imageView: UIImageView! 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | 19 | imageView.contentMode = .center 20 | } 21 | 22 | func configureWithAvatar(_ avatar: Navi.Avatar) { 23 | 24 | imageView.navi_setAvatar(avatar, withFadeTransitionDuration: 0.25) 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Chidori/Views/Cells/Avatar/AvatarCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2016 nixzhu 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 | -------------------------------------------------------------------------------- /Navi.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "Navi" 4 | s.version = "1.1.1" 5 | s.summary = "Focus on avatar caching." 6 | 7 | s.description = <<-DESC 8 | Navi is designed for avatar caching, with style. 9 | DESC 10 | 11 | s.homepage = "https://github.com/nixzhu/Navi" 12 | 13 | s.license = { :type => "MIT", :file => "LICENSE" } 14 | 15 | s.authors = { "nixzhu" => "zhuhongxu@gmail.com" } 16 | s.social_media_url = "https://twitter.com/nixzhu" 17 | 18 | s.ios.deployment_target = "8.0" 19 | 20 | s.source = { :git => "https://github.com/nixzhu/Navi.git", :tag => s.version } 21 | s.source_files = "Navi/*.swift" 22 | s.requires_arc = true 23 | 24 | end 25 | -------------------------------------------------------------------------------- /Navi/Avatar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Avatar.swift 3 | // Navi 4 | // 5 | // Created by NIX on 15/9/26. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum AvatarStyle { 12 | 13 | case original 14 | case rectangle(size: CGSize) 15 | case roundedRectangle(size: CGSize, cornerRadius: CGFloat, borderWidth: CGFloat) 16 | 17 | public typealias Transform = (UIImage) -> UIImage? 18 | case freeform(name: String, transform: Transform) 19 | } 20 | 21 | extension AvatarStyle { 22 | 23 | var hashString: String { 24 | 25 | switch self { 26 | 27 | case .original: 28 | return "Original-" 29 | 30 | case .rectangle(let size): 31 | return "Rectangle-\(size)-" 32 | 33 | case .roundedRectangle(let size, let cornerRadius, let borderWidth): 34 | return "RoundedRectangle-\(size)-\(cornerRadius)-\(borderWidth)-" 35 | 36 | case .freeform(let name, _): 37 | return "Freeform-\(name)-" 38 | } 39 | } 40 | } 41 | 42 | extension AvatarStyle: Equatable { 43 | 44 | public static func ==(lhs: AvatarStyle, rhs: AvatarStyle) -> Bool { 45 | 46 | switch (lhs, rhs) { 47 | 48 | case (.original, .original): 49 | return true 50 | 51 | case (.rectangle(let sizeA), .rectangle(let sizeB)) where sizeA == sizeB: 52 | return true 53 | 54 | case (.roundedRectangle(let sizeA, let cornerRadiusA, let borderWidthA), .roundedRectangle(let sizeB, let cornerRadiusB, let borderWidthB)) where (sizeA == sizeB && cornerRadiusA == cornerRadiusB && borderWidthA == borderWidthB): 55 | return true 56 | 57 | case (.freeform(let nameA, _), .freeform(let nameB, _)) where nameA == nameB: 58 | return true 59 | 60 | default: 61 | return false 62 | } 63 | } 64 | } 65 | 66 | public protocol Avatar { 67 | 68 | var url: URL? { get } 69 | var style: AvatarStyle { get } 70 | var placeholderImage: UIImage? { get } 71 | var localOriginalImage: UIImage? { get } 72 | var localStyledImage: UIImage? { get } 73 | 74 | func save(originalImage: UIImage, styledImage: UIImage) 75 | } 76 | 77 | public extension Avatar { 78 | 79 | public var key: String { 80 | return style.hashString + (url?.absoluteString ?? "") 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /Navi/AvatarPod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AvatarPod.swift 3 | // Navi 4 | // 5 | // Created by NIX on 15/9/26. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final public class AvatarPod { 12 | 13 | private static let sharedInstance = AvatarPod() 14 | 15 | private let cache = NSCache() 16 | 17 | private lazy var session = URLSession(configuration: URLSessionConfiguration.default) 18 | 19 | public enum CacheType { 20 | case memory 21 | case disk 22 | case cloud 23 | } 24 | 25 | public typealias Completion = (_ finished: Bool, _ image: UIImage, _ cacheType: CacheType) -> Void 26 | 27 | private struct Request { 28 | 29 | let avatar: Avatar 30 | let completion: Completion 31 | 32 | var url: URL? { 33 | return avatar.url 34 | } 35 | 36 | var key: String { 37 | return avatar.key 38 | } 39 | } 40 | 41 | private class RequestTank { 42 | 43 | let url: URL 44 | var requests: [Request] = [] 45 | 46 | init(url: URL) { 47 | self.url = url 48 | } 49 | } 50 | 51 | private class RequestPool { 52 | 53 | fileprivate var requestTanks = [URL: RequestTank]() 54 | 55 | func addRequest(_ request: Request) { 56 | 57 | guard let url = request.url else { 58 | return 59 | } 60 | 61 | if let requestTank = requestTanks[url] { 62 | requestTank.requests.append(request) 63 | 64 | } else { 65 | let requestTank = RequestTank(url: url) 66 | requestTanks[url] = requestTank 67 | requestTank.requests.append(request) 68 | } 69 | } 70 | 71 | func requestsWithURL(_ URL: Foundation.URL) -> [Request] { 72 | 73 | guard let requestTank = requestTanks[URL] else { 74 | return [] 75 | } 76 | 77 | return requestTank.requests 78 | } 79 | 80 | func removeRequestsWithURL(_ URL: Foundation.URL) { 81 | 82 | requestTanks.removeValue(forKey: URL) 83 | } 84 | 85 | func removeAllRequests() { 86 | requestTanks.removeAll() 87 | } 88 | } 89 | 90 | private var requestPool = RequestPool() 91 | 92 | private let requestQueue = DispatchQueue(label: "com.nixWork.Navi.requestQueue", attributes: []) 93 | private let cacheQueue = DispatchQueue.global(qos: .background) 94 | 95 | private func completeRequest(_ request: Request, withStyledImage styledImage: UIImage, cacheType: CacheType) { 96 | 97 | DispatchQueue.main.async { 98 | request.completion(true, styledImage, cacheType) 99 | } 100 | 101 | cache.setObject(styledImage, forKey: request.key as NSString) 102 | } 103 | 104 | private func completeRequestsWithURL(_ URL: Foundation.URL, image: UIImage, cacheType: CacheType) { 105 | 106 | requestQueue.async { 107 | 108 | let requests = self.requestPool.requestsWithURL(URL) 109 | 110 | self.cacheQueue.async { 111 | 112 | requests.forEach({ request in 113 | 114 | // if can find styledImage in cache, no need to generate it again or save 115 | 116 | if let styledImage = self.cache.object(forKey: request.key as NSString) { 117 | self.completeRequest(request, withStyledImage: styledImage, cacheType: cacheType) 118 | 119 | } else { 120 | let styledImage = image.navi_avatarImageWithStyle(request.avatar.style) 121 | 122 | self.completeRequest(request, withStyledImage: styledImage, cacheType: cacheType) 123 | 124 | // save images to local 125 | 126 | request.avatar.save(originalImage: image, styledImage: styledImage) 127 | } 128 | }) 129 | } 130 | 131 | self.requestPool.removeRequestsWithURL(URL) 132 | } 133 | } 134 | 135 | // MARK: - API 136 | 137 | public class func wakeAvatar(_ avatar: Avatar, completion: @escaping Completion) { 138 | 139 | guard let url = avatar.url else { 140 | completion(false, avatar.placeholderImage ?? UIImage(), .memory) 141 | return 142 | } 143 | 144 | let request = Request(avatar: avatar, completion: completion) 145 | 146 | if let image = sharedInstance.cache.object(forKey: request.key as NSString) { 147 | completion(true, image, .memory) 148 | 149 | } else { 150 | if let placeholderImage = avatar.placeholderImage { 151 | completion(false, placeholderImage, .memory) 152 | } 153 | 154 | sharedInstance.cacheQueue.async { 155 | 156 | if let styledImage = avatar.localStyledImage { 157 | sharedInstance.completeRequest(request, withStyledImage: styledImage, cacheType: .disk) 158 | 159 | } else { 160 | sharedInstance.requestQueue.async { 161 | 162 | sharedInstance.requestPool.addRequest(request) 163 | 164 | if sharedInstance.requestPool.requestsWithURL(url).count > 1 { 165 | // do nothing 166 | 167 | } else { 168 | sharedInstance.cacheQueue.async { 169 | 170 | if let image = avatar.localOriginalImage { 171 | sharedInstance.completeRequestsWithURL(url, image: image, cacheType: .disk) 172 | 173 | } else { 174 | let task = sharedInstance.session.dataTask(with: url, completionHandler: { data, response, error in 175 | 176 | guard error == nil, let data = data, let image = UIImage(data: data) else { 177 | sharedInstance.requestQueue.async { 178 | sharedInstance.requestPool.removeRequestsWithURL(url) 179 | } 180 | 181 | return 182 | } 183 | 184 | sharedInstance.completeRequestsWithURL(url, image: image, cacheType: .cloud) 185 | }) 186 | 187 | task.resume() 188 | } 189 | } 190 | } 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | public class func clear() { 198 | 199 | sharedInstance.requestPool.removeAllRequests() 200 | 201 | sharedInstance.cache.removeAllObjects() 202 | } 203 | } 204 | 205 | -------------------------------------------------------------------------------- /Navi/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Navi/Navi.h: -------------------------------------------------------------------------------- 1 | // 2 | // Navi.h 3 | // Navi 4 | // 5 | // Created by NIX on 15/9/26. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Navi. 12 | FOUNDATION_EXPORT double NaviVersionNumber; 13 | 14 | //! Project version string for Navi. 15 | FOUNDATION_EXPORT const unsigned char NaviVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Navi/UIImage+Navi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Navi.swift 3 | // Navi 4 | // 5 | // Created by NIX on 15/9/27. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // ref http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/ 12 | // but with better scale logic 13 | 14 | private let screenScale = UIScreen.main.scale 15 | 16 | // MARK: - API 17 | 18 | public extension UIImage { 19 | 20 | public func navi_avatarImageWithStyle(_ avatarStyle: AvatarStyle) -> UIImage { 21 | 22 | var avatarImage: UIImage? 23 | 24 | switch avatarStyle { 25 | 26 | case .original: 27 | return self 28 | 29 | case .rectangle(let size): 30 | avatarImage = navi_centerCropWithSize(size) 31 | 32 | case .roundedRectangle(let size, let cornerRadius, let borderWidth): 33 | avatarImage = navi_centerCropWithSize(size)?.navi_roundWithCornerRadius(cornerRadius, borderWidth: borderWidth) 34 | 35 | case .freeform(_, let transform): 36 | avatarImage = transform(self) 37 | } 38 | 39 | return avatarImage ?? self 40 | } 41 | } 42 | 43 | // MARK: - Resize 44 | 45 | public extension UIImage { 46 | 47 | public func navi_resizeToSize(_ size: CGSize, withTransform transform: CGAffineTransform, drawTransposed: Bool, interpolationQuality: CGInterpolationQuality) -> UIImage? { 48 | 49 | let pixelSize = CGSize(width: size.width * screenScale, height: size.height * screenScale) 50 | 51 | let newRect = CGRect(origin: CGPoint.zero, size: pixelSize).integral 52 | let transposedRect = CGRect(origin: CGPoint.zero, size: CGSize(width: pixelSize.height, height: pixelSize.width)) 53 | 54 | guard let cgImage = cgImage else { 55 | return nil 56 | } 57 | guard let colorSpace = cgImage.colorSpace else { 58 | return nil 59 | } 60 | guard let bitmapContext = CGContext(data: nil, width: Int(newRect.width), height: Int(newRect.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: cgImage.bitmapInfo.rawValue) else { 61 | return nil 62 | } 63 | 64 | bitmapContext.concatenate(transform) 65 | 66 | bitmapContext.interpolationQuality = interpolationQuality 67 | 68 | bitmapContext.draw(cgImage, in: drawTransposed ? transposedRect : newRect) 69 | 70 | guard let newCGImage = bitmapContext.makeImage() else { 71 | return nil 72 | } 73 | 74 | let image = UIImage(cgImage: newCGImage, scale: screenScale, orientation: imageOrientation) 75 | return image 76 | } 77 | 78 | public func navi_transformForOrientationWithSize(_ size: CGSize) -> CGAffineTransform { 79 | 80 | var transform = CGAffineTransform.identity 81 | 82 | switch imageOrientation { 83 | 84 | case .down, .downMirrored: 85 | transform = transform.translatedBy(x: size.width, y: size.height) 86 | transform = transform.rotated(by: .pi) 87 | 88 | case .left, .leftMirrored: 89 | transform = transform.translatedBy(x: size.width, y: 0) 90 | transform = transform.rotated(by: .pi / 2) 91 | 92 | case .right, .rightMirrored: 93 | transform = transform.translatedBy(x: 0, y: size.height) 94 | transform = transform.rotated(by: .pi / -2) 95 | 96 | default: 97 | break 98 | } 99 | 100 | switch imageOrientation { 101 | 102 | case .upMirrored, .downMirrored: 103 | transform = transform.translatedBy(x: size.width, y: 0) 104 | transform = transform.scaledBy(x: -1, y: 1) 105 | 106 | case .leftMirrored, .rightMirrored: 107 | transform = transform.translatedBy(x: size.height, y: 0) 108 | transform = transform.scaledBy(x: -1, y: 1) 109 | 110 | default: 111 | break 112 | } 113 | 114 | return transform 115 | } 116 | 117 | public func navi_resizeToSize(_ size: CGSize, withInterpolationQuality interpolationQuality: CGInterpolationQuality) -> UIImage? { 118 | 119 | let drawTransposed: Bool 120 | 121 | switch imageOrientation { 122 | case .left, .leftMirrored, .right, .rightMirrored: 123 | drawTransposed = true 124 | default: 125 | drawTransposed = false 126 | } 127 | 128 | let image = navi_resizeToSize(size, withTransform: navi_transformForOrientationWithSize(size), drawTransposed: drawTransposed, interpolationQuality: interpolationQuality) 129 | return image 130 | } 131 | 132 | public func navi_cropWithBounds(_ bounds: CGRect) -> UIImage? { 133 | 134 | guard let cgImage = cgImage else { 135 | return nil 136 | } 137 | guard let newCGImage = cgImage.cropping(to: bounds) else { 138 | return nil 139 | 140 | } 141 | 142 | let image = UIImage(cgImage: newCGImage, scale: screenScale, orientation: imageOrientation) 143 | return image 144 | } 145 | 146 | public func navi_centerCropWithSize(_ size: CGSize) -> UIImage? { 147 | 148 | let pixelSize = CGSize(width: size.width * screenScale, height: size.height * screenScale) 149 | 150 | let horizontalRatio = pixelSize.width / self.size.width 151 | let verticalRatio = pixelSize.height / self.size.height 152 | 153 | let ratio: CGFloat 154 | 155 | let originalX: CGFloat 156 | let originalY: CGFloat 157 | 158 | if horizontalRatio > verticalRatio { 159 | ratio = horizontalRatio 160 | 161 | originalX = 0 162 | originalY = (self.size.height - pixelSize.height / ratio) / 2 163 | 164 | } else { 165 | ratio = verticalRatio 166 | 167 | originalX = (self.size.width - pixelSize.width / ratio) / 2 168 | originalY = 0 169 | } 170 | 171 | let bounds = CGRect(x: originalX, y: originalY, width: pixelSize.width / ratio, height: pixelSize.height / ratio) 172 | 173 | let image = navi_cropWithBounds(bounds)?.navi_resizeToSize(size, withInterpolationQuality: .default) 174 | return image 175 | } 176 | } 177 | 178 | // MARK: - Round 179 | 180 | public extension UIImage { 181 | 182 | fileprivate func navi_cgContextAddRoundedRect(_ context: CGContext, rect: CGRect, ovalWidth: CGFloat, ovalHeight: CGFloat) { 183 | 184 | if ovalWidth <= 0 || ovalHeight <= 0 { 185 | context.addRect(rect) 186 | 187 | } else { 188 | context.saveGState() 189 | 190 | context.translateBy(x: rect.minX, y: rect.minY) 191 | 192 | context.scaleBy(x: ovalWidth, y: ovalHeight) 193 | 194 | let fw = rect.width / ovalWidth 195 | let fh = rect.height / ovalHeight 196 | 197 | context.move(to: CGPoint(x: fw, y: fh/2)) 198 | context.addArc(tangent1End: CGPoint(x: fw, y: fh), tangent2End: CGPoint(x: fw/2, y: fh), radius: 1) 199 | context.addArc(tangent1End: CGPoint(x: 0, y: fh), tangent2End: CGPoint(x: 0, y: fh/2), radius: 1) 200 | context.addArc(tangent1End: CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: fw/2, y: 0), radius: 1) 201 | context.addArc(tangent1End: CGPoint(x: fw, y: 0), tangent2End: CGPoint(x: fw, y: fh/2), radius: 1) 202 | context.closePath() 203 | 204 | context.restoreGState() 205 | } 206 | } 207 | 208 | public func navi_roundWithCornerRadius(_ cornerRadius: CGFloat, borderWidth: CGFloat) -> UIImage? { 209 | 210 | let image = navi_imageWithAlpha() 211 | 212 | let cornerRadius = cornerRadius * screenScale 213 | let borderWidth = borderWidth * screenScale 214 | 215 | let pixelSize = CGSize(width: image.size.width * screenScale, height: image.size.height * screenScale) 216 | 217 | guard let cgImage = image.cgImage else { 218 | return nil 219 | } 220 | guard let colorSpace = cgImage.colorSpace else { 221 | return nil 222 | } 223 | guard let bitmapContext = CGContext(data: nil, width: Int(pixelSize.width), height: Int(pixelSize.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: cgImage.bitmapInfo.rawValue) else { 224 | return nil 225 | } 226 | 227 | bitmapContext.beginPath() 228 | 229 | let rect = CGRect(x: borderWidth, y: borderWidth, width: pixelSize.width - borderWidth * 2, height: pixelSize.height - borderWidth * 2) 230 | navi_cgContextAddRoundedRect(bitmapContext, rect: rect, ovalWidth: cornerRadius, ovalHeight: cornerRadius) 231 | 232 | bitmapContext.closePath() 233 | 234 | bitmapContext.clip() 235 | 236 | let imageRect = CGRect(origin: CGPoint.zero, size: pixelSize) 237 | bitmapContext.draw(cgImage, in: imageRect) 238 | 239 | if let newCGImage = bitmapContext.makeImage() { 240 | let image = UIImage(cgImage: newCGImage, scale: screenScale, orientation: imageOrientation) 241 | return image 242 | } 243 | 244 | return nil 245 | } 246 | } 247 | 248 | // MARK: - Alpha 249 | 250 | public extension UIImage { 251 | 252 | public func navi_hasAlpha() -> Bool { 253 | 254 | guard let cgImage = cgImage else { 255 | return false 256 | } 257 | 258 | let alpha = cgImage.alphaInfo 259 | 260 | switch alpha { 261 | 262 | case .first, .last, .premultipliedFirst, .premultipliedLast: 263 | return true 264 | 265 | default: 266 | return false 267 | } 268 | } 269 | 270 | public func navi_imageWithAlpha() -> UIImage { 271 | 272 | if navi_hasAlpha() { 273 | return self 274 | } 275 | 276 | guard let cgImage = cgImage else { 277 | return self 278 | } 279 | 280 | let pixelSize = CGSize(width: self.size.width * screenScale, height: self.size.height * screenScale) 281 | 282 | let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo().rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) 283 | 284 | guard let offscreenContext = CGContext(data: nil, width: Int(pixelSize.width), height: Int(pixelSize.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo.rawValue) else { 285 | return self 286 | } 287 | 288 | offscreenContext.draw(cgImage, in: CGRect(origin: CGPoint.zero, size: pixelSize)) 289 | 290 | if let alphaCGImage = offscreenContext.makeImage() { 291 | let image = UIImage(cgImage: alphaCGImage, scale: screenScale, orientation: imageOrientation) 292 | return image 293 | 294 | } else { 295 | return self 296 | } 297 | } 298 | } 299 | 300 | -------------------------------------------------------------------------------- /Navi/UIImageView+Navi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+Navi.swift 3 | // Navi 4 | // 5 | // Created by NIX on 15/10/1. 6 | // Copyright © 2015年 nixWork. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private var avatarKeyAssociatedObject: Void? 12 | 13 | public extension UIImageView { 14 | 15 | fileprivate var navi_avatarKey: String? { 16 | return objc_getAssociatedObject(self, &avatarKeyAssociatedObject) as? String 17 | } 18 | 19 | fileprivate func navi_setAvatarKey(_ avatarKey: String) { 20 | objc_setAssociatedObject(self, &avatarKeyAssociatedObject, avatarKey, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) 21 | } 22 | 23 | public func navi_setAvatar(_ avatar: Avatar, withFadeTransitionDuration fadeTransitionDuration: TimeInterval = 0) { 24 | 25 | navi_setAvatarKey(avatar.key) 26 | 27 | AvatarPod.wakeAvatar(avatar) { [weak self] finished, image, cacheType in 28 | 29 | guard let strongSelf = self, let avatarKey = strongSelf.navi_avatarKey , avatarKey == avatar.key else { 30 | return 31 | } 32 | 33 | if finished && cacheType != .memory { 34 | UIView.transition(with: strongSelf, duration: fadeTransitionDuration, options: .transitionCrossDissolve, animations: { 35 | self?.image = image 36 | }, completion: nil) 37 | 38 | } else { 39 | self?.image = image 40 | } 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 | 6 | # Navi 7 | 8 | Navi is designed for avatar caching, with style. 9 | 10 | The name of **Navi** from movie [Avatar](https://en.wikipedia.org/wiki/Avatar_(2009_film)). 11 | 12 | ## Requirements 13 | 14 | Swift 3.1, iOS 8.0 15 | 16 | - Swift 2.3, use version 0.5.0 17 | - Swift 3.0, use version 1.1.0 18 | 19 | ## Usage 20 | 21 | 1. Make your User conform Avatar protocol. 22 | 23 | ``` swift 24 | protocol Avatar { 25 | 26 | var url: URL? { get } 27 | var style: AvatarStyle { get } 28 | var placeholderImage: UIImage? { get } 29 | var localOriginalImage: UIImage? { get } 30 | var localStyledImage: UIImage? { get } 31 | 32 | func save(originalImage: UIImage, styledImage: UIImage) 33 | } 34 | ``` 35 | 36 | 2. And, set avatar for your avatarImageView 37 | 38 | ``` swift 39 | avatarImageView.navi_setAvatar(userAvatar) 40 | ``` 41 | 42 | Check the demo for more information. 43 | 44 | 另有[中文介绍](https://github.com/nixzhu/dev-blog/blob/master/2015-10-08-navi.md)。 45 | 46 | ## Installation 47 | 48 | ### Carthage 49 | 50 | ```ogdl 51 | github "nixzhu/Navi" 52 | ``` 53 | 54 | ### CocoaPods 55 | 56 | ```ruby 57 | pod 'Navi' 58 | ``` 59 | 60 | ## Contact 61 | 62 | NIX [@nixzhu](https://twitter.com/nixzhu) 63 | 64 | ## License 65 | 66 | Navi is available under the MIT license. See the LICENSE file for more info. 67 | --------------------------------------------------------------------------------