├── CyclicCard.xcodeproj ├── project.pbxproj └── xcuserdata │ └── ylp.xcuserdatad │ └── xcschemes │ └── CyclicCard.xcscheme ├── CyclicCard ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── num_1.imageset │ │ ├── Contents.json │ │ └── num_1.png │ ├── num_2.imageset │ │ ├── Contents.json │ │ └── num_2.png │ ├── num_3.imageset │ │ ├── Contents.json │ │ └── num_3.png │ ├── num_4.imageset │ │ ├── Contents.json │ │ └── num_4.png │ └── num_5.imageset │ │ ├── Contents.json │ │ └── num_5.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── CyclicCardCell.swift ├── CyclicCardFlowLayout.swift ├── Info.plist ├── Tool │ ├── Header.swift │ └── View(Extension).swift └── ViewController.swift ├── CyclicCardTests ├── CyclicCardTests.swift └── Info.plist ├── CyclicCardUITests ├── CyclicCardUITests.swift └── Info.plist ├── README.md └── show.gif /CyclicCard.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 45503C881E305B4400C78F66 /* Header.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45503C861E305B4400C78F66 /* Header.swift */; }; 11 | 45503C891E305B4400C78F66 /* View(Extension).swift in Sources */ = {isa = PBXBuildFile; fileRef = 45503C871E305B4400C78F66 /* View(Extension).swift */; }; 12 | 45549AC21E25FDCB00F24054 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45549AC11E25FDCB00F24054 /* AppDelegate.swift */; }; 13 | 45549AC41E25FDCB00F24054 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45549AC31E25FDCB00F24054 /* ViewController.swift */; }; 14 | 45549AC71E25FDCB00F24054 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45549AC51E25FDCB00F24054 /* Main.storyboard */; }; 15 | 45549AC91E25FDCB00F24054 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 45549AC81E25FDCB00F24054 /* Assets.xcassets */; }; 16 | 45549ACC1E25FDCB00F24054 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 45549ACA1E25FDCB00F24054 /* LaunchScreen.storyboard */; }; 17 | 45549AD71E25FDCC00F24054 /* CyclicCardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45549AD61E25FDCC00F24054 /* CyclicCardTests.swift */; }; 18 | 45549AE21E25FDCC00F24054 /* CyclicCardUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45549AE11E25FDCC00F24054 /* CyclicCardUITests.swift */; }; 19 | 45549AF41E25FFBE00F24054 /* CyclicCardCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45549AF31E25FFBE00F24054 /* CyclicCardCell.swift */; }; 20 | 45549AF61E261D0900F24054 /* CyclicCardFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45549AF51E261D0900F24054 /* CyclicCardFlowLayout.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 45549AD31E25FDCC00F24054 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 45549AB61E25FDCB00F24054 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 45549ABD1E25FDCB00F24054; 29 | remoteInfo = CyclicCard; 30 | }; 31 | 45549ADE1E25FDCC00F24054 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 45549AB61E25FDCB00F24054 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 45549ABD1E25FDCB00F24054; 36 | remoteInfo = CyclicCard; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 45503C861E305B4400C78F66 /* Header.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Header.swift; sourceTree = ""; }; 42 | 45503C871E305B4400C78F66 /* View(Extension).swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View(Extension).swift"; sourceTree = ""; }; 43 | 45549ABE1E25FDCB00F24054 /* CyclicCard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CyclicCard.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 45549AC11E25FDCB00F24054 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 45 | 45549AC31E25FDCB00F24054 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 46 | 45549AC61E25FDCB00F24054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 45549AC81E25FDCB00F24054 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | 45549ACB1E25FDCB00F24054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | 45549ACD1E25FDCB00F24054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | 45549AD21E25FDCC00F24054 /* CyclicCardTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CyclicCardTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 45549AD61E25FDCC00F24054 /* CyclicCardTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CyclicCardTests.swift; sourceTree = ""; }; 52 | 45549AD81E25FDCC00F24054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | 45549ADD1E25FDCC00F24054 /* CyclicCardUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CyclicCardUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 45549AE11E25FDCC00F24054 /* CyclicCardUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CyclicCardUITests.swift; sourceTree = ""; }; 55 | 45549AE31E25FDCC00F24054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | 45549AF31E25FFBE00F24054 /* CyclicCardCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CyclicCardCell.swift; sourceTree = ""; }; 57 | 45549AF51E261D0900F24054 /* CyclicCardFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CyclicCardFlowLayout.swift; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 45549ABB1E25FDCB00F24054 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | 45549ACF1E25FDCC00F24054 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | 45549ADA1E25FDCC00F24054 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | ); 80 | runOnlyForDeploymentPostprocessing = 0; 81 | }; 82 | /* End PBXFrameworksBuildPhase section */ 83 | 84 | /* Begin PBXGroup section */ 85 | 45503C851E305B4400C78F66 /* Tool */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 45503C861E305B4400C78F66 /* Header.swift */, 89 | 45503C871E305B4400C78F66 /* View(Extension).swift */, 90 | ); 91 | path = Tool; 92 | sourceTree = ""; 93 | }; 94 | 45549AB51E25FDCB00F24054 = { 95 | isa = PBXGroup; 96 | children = ( 97 | 45549AC01E25FDCB00F24054 /* CyclicCard */, 98 | 45549AD51E25FDCC00F24054 /* CyclicCardTests */, 99 | 45549AE01E25FDCC00F24054 /* CyclicCardUITests */, 100 | 45549ABF1E25FDCB00F24054 /* Products */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 45549ABF1E25FDCB00F24054 /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 45549ABE1E25FDCB00F24054 /* CyclicCard.app */, 108 | 45549AD21E25FDCC00F24054 /* CyclicCardTests.xctest */, 109 | 45549ADD1E25FDCC00F24054 /* CyclicCardUITests.xctest */, 110 | ); 111 | name = Products; 112 | sourceTree = ""; 113 | }; 114 | 45549AC01E25FDCB00F24054 /* CyclicCard */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 45549AC11E25FDCB00F24054 /* AppDelegate.swift */, 118 | 45549AC31E25FDCB00F24054 /* ViewController.swift */, 119 | 45549AF31E25FFBE00F24054 /* CyclicCardCell.swift */, 120 | 45549AF51E261D0900F24054 /* CyclicCardFlowLayout.swift */, 121 | 45549AC51E25FDCB00F24054 /* Main.storyboard */, 122 | 45549AC81E25FDCB00F24054 /* Assets.xcassets */, 123 | 45549ACA1E25FDCB00F24054 /* LaunchScreen.storyboard */, 124 | 45549ACD1E25FDCB00F24054 /* Info.plist */, 125 | 45503C851E305B4400C78F66 /* Tool */, 126 | ); 127 | path = CyclicCard; 128 | sourceTree = ""; 129 | }; 130 | 45549AD51E25FDCC00F24054 /* CyclicCardTests */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 45549AD61E25FDCC00F24054 /* CyclicCardTests.swift */, 134 | 45549AD81E25FDCC00F24054 /* Info.plist */, 135 | ); 136 | path = CyclicCardTests; 137 | sourceTree = ""; 138 | }; 139 | 45549AE01E25FDCC00F24054 /* CyclicCardUITests */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 45549AE11E25FDCC00F24054 /* CyclicCardUITests.swift */, 143 | 45549AE31E25FDCC00F24054 /* Info.plist */, 144 | ); 145 | path = CyclicCardUITests; 146 | sourceTree = ""; 147 | }; 148 | /* End PBXGroup section */ 149 | 150 | /* Begin PBXNativeTarget section */ 151 | 45549ABD1E25FDCB00F24054 /* CyclicCard */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 45549AE61E25FDCC00F24054 /* Build configuration list for PBXNativeTarget "CyclicCard" */; 154 | buildPhases = ( 155 | 45549ABA1E25FDCB00F24054 /* Sources */, 156 | 45549ABB1E25FDCB00F24054 /* Frameworks */, 157 | 45549ABC1E25FDCB00F24054 /* Resources */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = CyclicCard; 164 | productName = CyclicCard; 165 | productReference = 45549ABE1E25FDCB00F24054 /* CyclicCard.app */; 166 | productType = "com.apple.product-type.application"; 167 | }; 168 | 45549AD11E25FDCC00F24054 /* CyclicCardTests */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 45549AE91E25FDCC00F24054 /* Build configuration list for PBXNativeTarget "CyclicCardTests" */; 171 | buildPhases = ( 172 | 45549ACE1E25FDCC00F24054 /* Sources */, 173 | 45549ACF1E25FDCC00F24054 /* Frameworks */, 174 | 45549AD01E25FDCC00F24054 /* Resources */, 175 | ); 176 | buildRules = ( 177 | ); 178 | dependencies = ( 179 | 45549AD41E25FDCC00F24054 /* PBXTargetDependency */, 180 | ); 181 | name = CyclicCardTests; 182 | productName = CyclicCardTests; 183 | productReference = 45549AD21E25FDCC00F24054 /* CyclicCardTests.xctest */; 184 | productType = "com.apple.product-type.bundle.unit-test"; 185 | }; 186 | 45549ADC1E25FDCC00F24054 /* CyclicCardUITests */ = { 187 | isa = PBXNativeTarget; 188 | buildConfigurationList = 45549AEC1E25FDCC00F24054 /* Build configuration list for PBXNativeTarget "CyclicCardUITests" */; 189 | buildPhases = ( 190 | 45549AD91E25FDCC00F24054 /* Sources */, 191 | 45549ADA1E25FDCC00F24054 /* Frameworks */, 192 | 45549ADB1E25FDCC00F24054 /* Resources */, 193 | ); 194 | buildRules = ( 195 | ); 196 | dependencies = ( 197 | 45549ADF1E25FDCC00F24054 /* PBXTargetDependency */, 198 | ); 199 | name = CyclicCardUITests; 200 | productName = CyclicCardUITests; 201 | productReference = 45549ADD1E25FDCC00F24054 /* CyclicCardUITests.xctest */; 202 | productType = "com.apple.product-type.bundle.ui-testing"; 203 | }; 204 | /* End PBXNativeTarget section */ 205 | 206 | /* Begin PBXProject section */ 207 | 45549AB61E25FDCB00F24054 /* Project object */ = { 208 | isa = PBXProject; 209 | attributes = { 210 | LastSwiftUpdateCheck = 0820; 211 | LastUpgradeCheck = 0820; 212 | ORGANIZATIONNAME = Tony; 213 | TargetAttributes = { 214 | 45549ABD1E25FDCB00F24054 = { 215 | CreatedOnToolsVersion = 8.2.1; 216 | ProvisioningStyle = Automatic; 217 | }; 218 | 45549AD11E25FDCC00F24054 = { 219 | CreatedOnToolsVersion = 8.2.1; 220 | ProvisioningStyle = Automatic; 221 | TestTargetID = 45549ABD1E25FDCB00F24054; 222 | }; 223 | 45549ADC1E25FDCC00F24054 = { 224 | CreatedOnToolsVersion = 8.2.1; 225 | ProvisioningStyle = Automatic; 226 | TestTargetID = 45549ABD1E25FDCB00F24054; 227 | }; 228 | }; 229 | }; 230 | buildConfigurationList = 45549AB91E25FDCB00F24054 /* Build configuration list for PBXProject "CyclicCard" */; 231 | compatibilityVersion = "Xcode 3.2"; 232 | developmentRegion = English; 233 | hasScannedForEncodings = 0; 234 | knownRegions = ( 235 | en, 236 | Base, 237 | ); 238 | mainGroup = 45549AB51E25FDCB00F24054; 239 | productRefGroup = 45549ABF1E25FDCB00F24054 /* Products */; 240 | projectDirPath = ""; 241 | projectRoot = ""; 242 | targets = ( 243 | 45549ABD1E25FDCB00F24054 /* CyclicCard */, 244 | 45549AD11E25FDCC00F24054 /* CyclicCardTests */, 245 | 45549ADC1E25FDCC00F24054 /* CyclicCardUITests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | 45549ABC1E25FDCB00F24054 /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | 45549ACC1E25FDCB00F24054 /* LaunchScreen.storyboard in Resources */, 256 | 45549AC91E25FDCB00F24054 /* Assets.xcassets in Resources */, 257 | 45549AC71E25FDCB00F24054 /* Main.storyboard in Resources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | 45549AD01E25FDCC00F24054 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | 45549ADB1E25FDCC00F24054 /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | /* End PBXResourcesBuildPhase section */ 276 | 277 | /* Begin PBXSourcesBuildPhase section */ 278 | 45549ABA1E25FDCB00F24054 /* Sources */ = { 279 | isa = PBXSourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 45549AF41E25FFBE00F24054 /* CyclicCardCell.swift in Sources */, 283 | 45503C891E305B4400C78F66 /* View(Extension).swift in Sources */, 284 | 45549AC41E25FDCB00F24054 /* ViewController.swift in Sources */, 285 | 45503C881E305B4400C78F66 /* Header.swift in Sources */, 286 | 45549AF61E261D0900F24054 /* CyclicCardFlowLayout.swift in Sources */, 287 | 45549AC21E25FDCB00F24054 /* AppDelegate.swift in Sources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | 45549ACE1E25FDCC00F24054 /* Sources */ = { 292 | isa = PBXSourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | 45549AD71E25FDCC00F24054 /* CyclicCardTests.swift in Sources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | 45549AD91E25FDCC00F24054 /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 45549AE21E25FDCC00F24054 /* CyclicCardUITests.swift in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXTargetDependency section */ 310 | 45549AD41E25FDCC00F24054 /* PBXTargetDependency */ = { 311 | isa = PBXTargetDependency; 312 | target = 45549ABD1E25FDCB00F24054 /* CyclicCard */; 313 | targetProxy = 45549AD31E25FDCC00F24054 /* PBXContainerItemProxy */; 314 | }; 315 | 45549ADF1E25FDCC00F24054 /* PBXTargetDependency */ = { 316 | isa = PBXTargetDependency; 317 | target = 45549ABD1E25FDCB00F24054 /* CyclicCard */; 318 | targetProxy = 45549ADE1E25FDCC00F24054 /* PBXContainerItemProxy */; 319 | }; 320 | /* End PBXTargetDependency section */ 321 | 322 | /* Begin PBXVariantGroup section */ 323 | 45549AC51E25FDCB00F24054 /* Main.storyboard */ = { 324 | isa = PBXVariantGroup; 325 | children = ( 326 | 45549AC61E25FDCB00F24054 /* Base */, 327 | ); 328 | name = Main.storyboard; 329 | sourceTree = ""; 330 | }; 331 | 45549ACA1E25FDCB00F24054 /* LaunchScreen.storyboard */ = { 332 | isa = PBXVariantGroup; 333 | children = ( 334 | 45549ACB1E25FDCB00F24054 /* Base */, 335 | ); 336 | name = LaunchScreen.storyboard; 337 | sourceTree = ""; 338 | }; 339 | /* End PBXVariantGroup section */ 340 | 341 | /* Begin XCBuildConfiguration section */ 342 | 45549AE41E25FDCC00F24054 /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_ANALYZER_NONNULL = YES; 347 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 348 | CLANG_CXX_LIBRARY = "libc++"; 349 | CLANG_ENABLE_MODULES = YES; 350 | CLANG_ENABLE_OBJC_ARC = YES; 351 | CLANG_WARN_BOOL_CONVERSION = YES; 352 | CLANG_WARN_CONSTANT_CONVERSION = YES; 353 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 354 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 355 | CLANG_WARN_EMPTY_BODY = YES; 356 | CLANG_WARN_ENUM_CONVERSION = YES; 357 | CLANG_WARN_INFINITE_RECURSION = YES; 358 | CLANG_WARN_INT_CONVERSION = YES; 359 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 360 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 361 | CLANG_WARN_UNREACHABLE_CODE = YES; 362 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 363 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 364 | COPY_PHASE_STRIP = NO; 365 | DEBUG_INFORMATION_FORMAT = dwarf; 366 | ENABLE_STRICT_OBJC_MSGSEND = YES; 367 | ENABLE_TESTABILITY = YES; 368 | GCC_C_LANGUAGE_STANDARD = gnu99; 369 | GCC_DYNAMIC_NO_PIC = NO; 370 | GCC_NO_COMMON_BLOCKS = YES; 371 | GCC_OPTIMIZATION_LEVEL = 0; 372 | GCC_PREPROCESSOR_DEFINITIONS = ( 373 | "DEBUG=1", 374 | "$(inherited)", 375 | ); 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 383 | MTL_ENABLE_DEBUG_INFO = YES; 384 | ONLY_ACTIVE_ARCH = YES; 385 | SDKROOT = iphoneos; 386 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 387 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 388 | }; 389 | name = Debug; 390 | }; 391 | 45549AE51E25FDCC00F24054 /* Release */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | CLANG_ANALYZER_NONNULL = YES; 396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 397 | CLANG_CXX_LIBRARY = "libc++"; 398 | CLANG_ENABLE_MODULES = YES; 399 | CLANG_ENABLE_OBJC_ARC = YES; 400 | CLANG_WARN_BOOL_CONVERSION = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 403 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 404 | CLANG_WARN_EMPTY_BODY = YES; 405 | CLANG_WARN_ENUM_CONVERSION = YES; 406 | CLANG_WARN_INFINITE_RECURSION = YES; 407 | CLANG_WARN_INT_CONVERSION = YES; 408 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 409 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 410 | CLANG_WARN_UNREACHABLE_CODE = YES; 411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 412 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 413 | COPY_PHASE_STRIP = NO; 414 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 415 | ENABLE_NS_ASSERTIONS = NO; 416 | ENABLE_STRICT_OBJC_MSGSEND = YES; 417 | GCC_C_LANGUAGE_STANDARD = gnu99; 418 | GCC_NO_COMMON_BLOCKS = YES; 419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 421 | GCC_WARN_UNDECLARED_SELECTOR = YES; 422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 423 | GCC_WARN_UNUSED_FUNCTION = YES; 424 | GCC_WARN_UNUSED_VARIABLE = YES; 425 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 426 | MTL_ENABLE_DEBUG_INFO = NO; 427 | SDKROOT = iphoneos; 428 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 429 | VALIDATE_PRODUCT = YES; 430 | }; 431 | name = Release; 432 | }; 433 | 45549AE71E25FDCC00F24054 /* Debug */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 437 | INFOPLIST_FILE = CyclicCard/Info.plist; 438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 439 | PRODUCT_BUNDLE_IDENTIFIER = com.Tony.CyclicCard; 440 | PRODUCT_NAME = "$(TARGET_NAME)"; 441 | SWIFT_VERSION = 3.0; 442 | }; 443 | name = Debug; 444 | }; 445 | 45549AE81E25FDCC00F24054 /* Release */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 449 | INFOPLIST_FILE = CyclicCard/Info.plist; 450 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 451 | PRODUCT_BUNDLE_IDENTIFIER = com.Tony.CyclicCard; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | SWIFT_VERSION = 3.0; 454 | }; 455 | name = Release; 456 | }; 457 | 45549AEA1E25FDCC00F24054 /* Debug */ = { 458 | isa = XCBuildConfiguration; 459 | buildSettings = { 460 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 461 | BUNDLE_LOADER = "$(TEST_HOST)"; 462 | INFOPLIST_FILE = CyclicCardTests/Info.plist; 463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 464 | PRODUCT_BUNDLE_IDENTIFIER = com.Tony.CyclicCardTests; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | SWIFT_VERSION = 3.0; 467 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CyclicCard.app/CyclicCard"; 468 | }; 469 | name = Debug; 470 | }; 471 | 45549AEB1E25FDCC00F24054 /* Release */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 475 | BUNDLE_LOADER = "$(TEST_HOST)"; 476 | INFOPLIST_FILE = CyclicCardTests/Info.plist; 477 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 478 | PRODUCT_BUNDLE_IDENTIFIER = com.Tony.CyclicCardTests; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | SWIFT_VERSION = 3.0; 481 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CyclicCard.app/CyclicCard"; 482 | }; 483 | name = Release; 484 | }; 485 | 45549AED1E25FDCC00F24054 /* Debug */ = { 486 | isa = XCBuildConfiguration; 487 | buildSettings = { 488 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 489 | INFOPLIST_FILE = CyclicCardUITests/Info.plist; 490 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 491 | PRODUCT_BUNDLE_IDENTIFIER = com.Tony.CyclicCardUITests; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | SWIFT_VERSION = 3.0; 494 | TEST_TARGET_NAME = CyclicCard; 495 | }; 496 | name = Debug; 497 | }; 498 | 45549AEE1E25FDCC00F24054 /* Release */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 502 | INFOPLIST_FILE = CyclicCardUITests/Info.plist; 503 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 504 | PRODUCT_BUNDLE_IDENTIFIER = com.Tony.CyclicCardUITests; 505 | PRODUCT_NAME = "$(TARGET_NAME)"; 506 | SWIFT_VERSION = 3.0; 507 | TEST_TARGET_NAME = CyclicCard; 508 | }; 509 | name = Release; 510 | }; 511 | /* End XCBuildConfiguration section */ 512 | 513 | /* Begin XCConfigurationList section */ 514 | 45549AB91E25FDCB00F24054 /* Build configuration list for PBXProject "CyclicCard" */ = { 515 | isa = XCConfigurationList; 516 | buildConfigurations = ( 517 | 45549AE41E25FDCC00F24054 /* Debug */, 518 | 45549AE51E25FDCC00F24054 /* Release */, 519 | ); 520 | defaultConfigurationIsVisible = 0; 521 | defaultConfigurationName = Release; 522 | }; 523 | 45549AE61E25FDCC00F24054 /* Build configuration list for PBXNativeTarget "CyclicCard" */ = { 524 | isa = XCConfigurationList; 525 | buildConfigurations = ( 526 | 45549AE71E25FDCC00F24054 /* Debug */, 527 | 45549AE81E25FDCC00F24054 /* Release */, 528 | ); 529 | defaultConfigurationIsVisible = 0; 530 | defaultConfigurationName = Release; 531 | }; 532 | 45549AE91E25FDCC00F24054 /* Build configuration list for PBXNativeTarget "CyclicCardTests" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | 45549AEA1E25FDCC00F24054 /* Debug */, 536 | 45549AEB1E25FDCC00F24054 /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | 45549AEC1E25FDCC00F24054 /* Build configuration list for PBXNativeTarget "CyclicCardUITests" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 45549AED1E25FDCC00F24054 /* Debug */, 545 | 45549AEE1E25FDCC00F24054 /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | /* End XCConfigurationList section */ 551 | }; 552 | rootObject = 45549AB61E25FDCB00F24054 /* Project object */; 553 | } 554 | -------------------------------------------------------------------------------- /CyclicCard.xcodeproj/xcuserdata/ylp.xcuserdatad/xcschemes/CyclicCard.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /CyclicCard/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CyclicCard 4 | // 5 | // Created by Tony on 17/1/11. 6 | // Copyright © 2017年 Tony. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /CyclicCard/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 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "num_1.png", 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 | } -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_1.imageset/num_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splsylp/CyclicCard/a6bbe24da577440b813169942cdd315a1543cde9/CyclicCard/Assets.xcassets/num_1.imageset/num_1.png -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "num_2.png", 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 | } -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_2.imageset/num_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splsylp/CyclicCard/a6bbe24da577440b813169942cdd315a1543cde9/CyclicCard/Assets.xcassets/num_2.imageset/num_2.png -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "num_3.png", 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 | } -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_3.imageset/num_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splsylp/CyclicCard/a6bbe24da577440b813169942cdd315a1543cde9/CyclicCard/Assets.xcassets/num_3.imageset/num_3.png -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "num_4.png", 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 | } -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_4.imageset/num_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splsylp/CyclicCard/a6bbe24da577440b813169942cdd315a1543cde9/CyclicCard/Assets.xcassets/num_4.imageset/num_4.png -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "num_5.png", 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 | } -------------------------------------------------------------------------------- /CyclicCard/Assets.xcassets/num_5.imageset/num_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splsylp/CyclicCard/a6bbe24da577440b813169942cdd315a1543cde9/CyclicCard/Assets.xcassets/num_5.imageset/num_5.png -------------------------------------------------------------------------------- /CyclicCard/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 | -------------------------------------------------------------------------------- /CyclicCard/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 | -------------------------------------------------------------------------------- /CyclicCard/CyclicCardCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclicCardCell.swift 3 | // CyclicCard 4 | // 5 | // Created by Tony on 17/1/11. 6 | // Copyright © 2017年 Tony. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CyclicCardCell: UICollectionViewCell { 12 | 13 | var index = Int() // 下标 14 | 15 | let cardImgView = UIImageView() 16 | let cardNameLabel = UILabel() 17 | 18 | override init(frame: CGRect) { 19 | 20 | super.init(frame: frame) 21 | 22 | backgroundColor = GRAY_COLOR() 23 | layer.masksToBounds = true 24 | layer.cornerRadius = 5.0 25 | 26 | self.addSubview(cardImgView) 27 | cardImgView.frame = CGRect(x: 0, y: 0, width: self.width, height: self.width) 28 | 29 | self.addSubview(cardNameLabel) 30 | cardNameLabel.frame = CGRect(x: 0, y: cardImgView.bottom, width: cardImgView.width, height: self.height - cardImgView.height) 31 | cardNameLabel.textAlignment = NSTextAlignment.center 32 | cardNameLabel.textColor = Font_Color_First() 33 | cardNameLabel.font = AUTO_FONT(15) 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | fatalError("init(coder:) has not been implemented") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /CyclicCard/CyclicCardFlowLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclicCardFlowLayout.swift 3 | // CyclicCard 4 | // 5 | // Created by Tony on 17/1/11. 6 | // Copyright © 2017年 Tony. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CyclicCardFlowLayout: UICollectionViewFlowLayout { 12 | 13 | override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { 14 | 15 | let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height) 16 | // 目标区域中包含的cell 17 | let attriArray = super.layoutAttributesForElements(in: targetRect)! as [UICollectionViewLayoutAttributes] 18 | // collectionView落在屏幕中点的x坐标 19 | let horizontalCenterX = proposedContentOffset.x + (self.collectionView!.bounds.width / 2.0) 20 | var offsetAdjustment = CGFloat(MAXFLOAT) 21 | for layoutAttributes in attriArray { 22 | let itemHorizontalCenterX = layoutAttributes.center.x 23 | // 找出离中心点最近的 24 | if(abs(itemHorizontalCenterX-horizontalCenterX) < abs(offsetAdjustment)) { 25 | offsetAdjustment = itemHorizontalCenterX-horizontalCenterX 26 | } 27 | } 28 | 29 | //返回collectionView最终停留的位置 30 | return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) 31 | } 32 | 33 | let ActiveDistance : CGFloat = 400 //垂直缩放除以系数 34 | let ScaleFactor : CGFloat = 0.25 //缩放系数 越大缩放越大 35 | 36 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 37 | 38 | let array = super.layoutAttributesForElements(in: rect) 39 | var visibleRect = CGRect() 40 | visibleRect.origin = self.collectionView!.contentOffset 41 | visibleRect.size = self.collectionView!.bounds.size 42 | 43 | for attributes in array! { 44 | let distance = visibleRect.midX - attributes.center.x 45 | let normalizedDistance = abs(distance / ActiveDistance) 46 | let zoom = 1 - ScaleFactor * normalizedDistance 47 | attributes.transform3D = CATransform3DMakeScale(1.0, zoom, 1.0) 48 | // let alpha = 1 - normalizedDistance 49 | // attributes.alpha = alpha 50 | } 51 | return array 52 | } 53 | 54 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { 55 | //滑动放大缩小 需要实时刷新layout 56 | return true 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CyclicCard/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 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CyclicCard/Tool/Header.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Header.swift 3 | // HuiKaoBa 4 | // 5 | // Created by Tony on 16/11/17. 6 | // Copyright © 2016年 Tony. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /** 屏幕尺寸 */ 12 | 13 | // 屏幕宽度 14 | let SCREEN_HEIGHT = UIScreen.main.bounds.size.height 15 | // 屏幕高度 16 | let SCREEN_WIDTH = UIScreen.main.bounds.size.width 17 | // 自适应屏幕宽度 18 | func FIT_SCREEN_WIDTH(_ size: CGFloat) -> CGFloat { 19 | return size * SCREEN_WIDTH / 375.0 20 | } 21 | // 自适应屏幕高度 22 | func FIT_SCREEN_HEIGHT(_ size: CGFloat) -> CGFloat { 23 | return size * SCREEN_HEIGHT / 667.0 24 | } 25 | // 自适应屏幕字体大小 26 | func AUTO_FONT(_ size: CGFloat) -> UIFont { 27 | let autoSize = size * SCREEN_WIDTH / 375.0 28 | return UIFont.systemFont(ofSize: autoSize) 29 | } 30 | 31 | 32 | /** 颜色值 */ 33 | // RGB颜色 34 | func RGB_COLOR(_ r:CGFloat, g:CGFloat, b:CGFloat, alpha:CGFloat) -> UIColor { 35 | return UIColor(red: r / 255.0, green: g / 255.0, blue: b / 255.0, alpha: alpha) 36 | } 37 | // 项目主颜色 38 | func MAIN_COLOR() -> UIColor { 39 | return UIColor(red: 74/255.0, green: 163/255.0, blue: 243/255.0, alpha: 1.0) 40 | } 41 | 42 | /** 一级文字颜色 */ 43 | func Font_Color_First() -> UIColor { 44 | return UIColor(red: 51.0/255.0, green: 51.0/255.0, blue: 51.0/255.0, alpha: 1.0) 45 | } 46 | 47 | /** 二级文字颜色 */ 48 | func Font_Color_Second() -> UIColor { 49 | return UIColor(red: 102.0/255.0, green: 102.0/255.0, blue: 102.0/255.0, alpha: 1.0) 50 | } 51 | /** 三级文字颜色 */ 52 | func Font_Color_Third() -> UIColor { 53 | return UIColor(red: 153.0/255.0, green: 153.0/255.0, blue: 153.0/255.0, alpha: 1.0) 54 | } 55 | 56 | /** 分割线颜色 */ 57 | func Line_COLOR() -> UIColor { 58 | return UIColor(red: 221.0/255.0, green: 221.0/255.0, blue: 221.0/255.0, alpha: 1.0) 59 | } 60 | 61 | // 灰色背景 62 | func GRAY_COLOR() -> UIColor { 63 | return UIColor(red: 242/255.0, green: 243/255.0, blue: 248/255.0, alpha: 1.0) 64 | } 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /CyclicCard/Tool/View(Extension).swift: -------------------------------------------------------------------------------- 1 | // 2 | // View(Extension).swift 3 | // HuiKaoBa 4 | // 5 | // Created by Tony on 16/11/17. 6 | // Copyright © 2016年 Tony. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIView { 12 | 13 | public var x: CGFloat { 14 | get { 15 | return self.frame.origin.x 16 | } 17 | set { 18 | var frame = self.frame 19 | frame.origin.x = newValue 20 | self.frame = frame 21 | } 22 | } 23 | 24 | public var y: CGFloat{ 25 | get { 26 | return self.frame.origin.y 27 | } 28 | set { 29 | var frame = self.frame 30 | frame.origin.y = newValue 31 | self.frame = frame 32 | } 33 | } 34 | 35 | /** 宽 */ 36 | public var width: CGFloat{ 37 | get { 38 | return self.frame.size.width 39 | } 40 | set { 41 | var frame = self.frame 42 | frame.size.width = newValue 43 | self.frame = frame 44 | } 45 | } 46 | 47 | /** 高 */ 48 | public var height: CGFloat{ 49 | get { 50 | return self.frame.size.height 51 | } 52 | set { 53 | var frame = self.frame 54 | frame.size.height = newValue 55 | self.frame = frame 56 | } 57 | } 58 | 59 | /** 下 */ 60 | public var bottom: CGFloat{ 61 | get { 62 | return self.frame.origin.y + self.frame.size.height 63 | } 64 | 65 | set { 66 | var frame = self.frame 67 | frame.origin.y = newValue - self.frame.size.height 68 | self.frame = frame 69 | } 70 | } 71 | 72 | /** 右 */ 73 | public var right: CGFloat{ 74 | get { 75 | return self.frame.origin.x + self.frame.size.width 76 | } 77 | 78 | set { 79 | var frame = self.frame 80 | frame.origin.x = newValue - self.frame.size.width 81 | self.frame = frame 82 | } 83 | } 84 | 85 | /** 尺寸 */ 86 | public var size: CGSize{ 87 | get { 88 | return self.frame.size 89 | } 90 | 91 | set { 92 | var frame = self.frame 93 | frame.size = newValue 94 | self.frame = frame 95 | } 96 | } 97 | 98 | /** 竖直中心对齐 */ 99 | public var centerX: CGFloat{ 100 | get { 101 | return self.center.x 102 | } 103 | 104 | set { 105 | var center = self.center 106 | center.x = newValue 107 | self.center = center 108 | } 109 | } 110 | 111 | /** 水平中心对齐 */ 112 | public var centerY: CGFloat{ 113 | get { 114 | return self.center.y 115 | } 116 | 117 | set { 118 | var center = self.center 119 | center.y = newValue 120 | self.center = center 121 | } 122 | } 123 | 124 | func roundCorners(corners:UIRectCorner, radius: CGFloat) { 125 | 126 | let path:UIBezierPath = UIBezierPath.init(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) 127 | let shapeLayer:CAShapeLayer = CAShapeLayer() 128 | shapeLayer.path = path.cgPath 129 | self.layer.mask = shapeLayer 130 | } 131 | } 132 | 133 | -------------------------------------------------------------------------------- /CyclicCard/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CyclicCard 4 | // 5 | // Created by Tony on 17/1/11. 6 | // Copyright © 2017年 Tony. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { 12 | 13 | let bgview = UIView() 14 | var collectionView : UICollectionView! 15 | let showLabel = UILabel() 16 | let currentLabel = UILabel() 17 | 18 | let groupCount = 100 // 制造100组数据,给无限滚动提供足够多的数据,嫌少可以200,1000。。。 19 | var imageArr = [String]() // 图片数组 20 | var indexArr = [Int]() // 存储图片下标,解决制造100组图片数据占用过大内存问题 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | makeUI() 26 | 27 | // 设置数据源 28 | imageArr = ["num_1", "num_2", "num_3", "num_4", "num_5"] 29 | for _ in 0 ..< groupCount { 30 | for j in 0 ..< imageArr.count { 31 | indexArr.append(j) 32 | } 33 | } 34 | 35 | // 定位到 第50组(中间那组) 36 | collectionView.scrollToItem(at: NSIndexPath.init(item: groupCount / 2 * imageArr.count, section: 0) as IndexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: false) 37 | } 38 | 39 | // MARK:- UI 40 | func makeUI() { 41 | 42 | automaticallyAdjustsScrollViewInsets = false 43 | 44 | bgview.frame = CGRect(x: 0, y: 64, width: SCREEN_WIDTH, height: FIT_SCREEN_HEIGHT(240)) 45 | bgview.backgroundColor = MAIN_COLOR() 46 | self.view.addSubview(bgview) 47 | 48 | setupCollection() 49 | 50 | showLabel.frame = CGRect(x: 0, y: bgview.bottom + FIT_SCREEN_HEIGHT(20), width: SCREEN_WIDTH, height: FIT_SCREEN_HEIGHT(20)) 51 | showLabel.textAlignment = NSTextAlignment.center 52 | showLabel.text = "滚动至第0张" 53 | view.addSubview(showLabel) 54 | 55 | currentLabel.frame = CGRect(x: 0, y: showLabel.bottom + FIT_SCREEN_HEIGHT(20), width: SCREEN_WIDTH, height: FIT_SCREEN_HEIGHT(20)) 56 | currentLabel.textAlignment = NSTextAlignment.center 57 | currentLabel.text = "点击第 张图片" 58 | view.addSubview(currentLabel) 59 | } 60 | 61 | func setupCollection() { 62 | 63 | let padding = FIT_SCREEN_WIDTH(20) 64 | 65 | let layout = CyclicCardFlowLayout() 66 | layout.scrollDirection = .horizontal 67 | layout.minimumLineSpacing = padding 68 | layout.minimumInteritemSpacing = padding 69 | layout.sectionInset = UIEdgeInsetsMake(padding, 0, padding, 0) 70 | let itemW = (SCREEN_WIDTH - padding * 2) * 0.5 71 | layout.itemSize = CGSize(width: itemW, height: bgview.height - padding * 2) 72 | collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: bgview.height), collectionViewLayout: layout) 73 | collectionView.backgroundColor = UIColor.clear 74 | collectionView.collectionViewLayout = layout 75 | collectionView.showsHorizontalScrollIndicator = false 76 | collectionView.delegate = self 77 | collectionView.dataSource = self 78 | collectionView.register(CyclicCardCell.self, forCellWithReuseIdentifier: NSStringFromClass(CyclicCardCell.self)) 79 | bgview.addSubview(self.collectionView) 80 | } 81 | 82 | 83 | // MARK:- CollectionView 84 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 85 | return indexArr.count 86 | } 87 | 88 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 89 | 90 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(CyclicCardCell.self), for: indexPath) as! CyclicCardCell 91 | 92 | let index = indexArr[indexPath.row] 93 | cell.index = index 94 | cell.cardImgView.image = UIImage(named: imageArr[index]) 95 | cell.cardNameLabel.text = "奔跑吧,小蜗牛~" 96 | 97 | return cell 98 | } 99 | 100 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 101 | let cell = collectionView.cellForItem(at: indexPath) as! CyclicCardCell 102 | print("点击第\(cell.index + 1)张图片") 103 | currentLabel.text = "点击第\(cell.index + 1)张图片" 104 | } 105 | 106 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 107 | 108 | let pointInView = view.convert(collectionView.center, to: collectionView) 109 | let indexPathNow = collectionView.indexPathForItem(at: pointInView) 110 | let index = (indexPathNow?.row ?? 0) % imageArr.count 111 | let curIndexStr = String(format: "滚动至第%d张", index + 1) 112 | print(curIndexStr) 113 | showLabel.text = curIndexStr 114 | 115 | // 动画停止, 重新定位到 第50组(中间那组) 模型 116 | collectionView.scrollToItem(at: NSIndexPath.init(item: groupCount / 2 * imageArr.count + index, section: 0) as IndexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: false) 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /CyclicCardTests/CyclicCardTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclicCardTests.swift 3 | // CyclicCardTests 4 | // 5 | // Created by Tony on 17/1/11. 6 | // Copyright © 2017年 Tony. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import CyclicCard 11 | 12 | class CyclicCardTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /CyclicCardTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CyclicCardUITests/CyclicCardUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CyclicCardUITests.swift 3 | // CyclicCardUITests 4 | // 5 | // Created by Tony on 17/1/11. 6 | // Copyright © 2017年 Tony. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class CyclicCardUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | 18 | // In UI tests it is usually best to stop immediately when a failure occurs. 19 | continueAfterFailure = false 20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 21 | XCUIApplication().launch() 22 | 23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 24 | } 25 | 26 | override func tearDown() { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | super.tearDown() 29 | } 30 | 31 | func testExample() { 32 | // Use recording to get started writing UI tests. 33 | // Use XCTAssert and related functions to verify your tests produce the correct results. 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /CyclicCardUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CyclicCard 2 | 利用collectionView实现的卡片滑动效果,可以无限循环滚动,十分流畅。 3 | 4 | ### [简书:实现原理解析](http://www.jianshu.com/p/6091c5e37289) 5 | ![Image text](https://github.com/splsylp/CyclicCard/blob/master/show.gif) 6 | 7 | 8 | 还有一个类似的**重叠卡片滚动**的动画,感兴趣的可以看下: 9 | #### [ScrollCard](https://github.com/splsylp/ScrollCard) 10 | #### [简书:重叠卡片滚动动画](http://www.jianshu.com/p/086309689041) 11 | 12 | 13 | --- 14 | ### 您的star,是对我最大的鼓励与支持~ 15 | 16 | -------------------------------------------------------------------------------- /show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splsylp/CyclicCard/a6bbe24da577440b813169942cdd315a1543cde9/show.gif --------------------------------------------------------------------------------