├── README.md ├── XYCoverCardViewDemo ├── XYCoverCardViewDemo.xcodeproj │ └── project.pbxproj └── XYCoverCardViewDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── ViewController.h │ ├── ViewController.m │ ├── XYCoverCardView │ ├── Layout │ │ ├── XYCoverCardViewLayout.h │ │ └── XYCoverCardViewLayout.m │ ├── XYCoverCardCollectionView.h │ ├── XYCoverCardCollectionView.m │ ├── XYCoverCardView.h │ ├── XYCoverCardView.m │ ├── XYCoverCell.h │ ├── XYCoverCell.m │ ├── XYCoverModel.h │ └── XYCoverModel.m │ └── main.m ├── preview.gif └── preview1.gif /README.md: -------------------------------------------------------------------------------- 1 | # XYCoverCardView 2 | ### 如果有任何疑问或新的需求可发邮件:ioshxy@163.com 如果有需要可以开发Swift版本。 3 | 4 | CoverCardView, 叠加的轮播view,类似知乎的回答问题。及like or pass。可定时轮播。 5 | 可以用于Banner,或者广告推广展示。 6 | 7 | 8 | 2022.9.6-Notice: 9 | - 如果自定义cell,子控件用frame添加时,应避免将子控件的frame设为cell.frame。 10 | - 需用cell.bounds来替代。 11 | ``` 12 | view.frame = self.bounds; 13 | ``` 14 | - 主要原因是一开始cell.frame.origin的x,y都不是0,0。 15 | 16 | 17 | 2022.07.11-新增加: 18 | - 可以手动返回上一张。 19 | - 在分支dev-reverse里。下次有空的时候会合并分支。 20 | ```swift 21 | self.cardView.isCanReverse = YES; 22 | ``` 23 | 24 | 25 | 26 | 2022.07.06-新增加: 27 | - 右边重叠效果 28 | - 自动轮播的方向可以自行设置 29 | ```swift 30 | // 重叠的方向 31 | self.cardView.coverDirectionType = XYCoverDirectionRight; 32 | // 移动的方向 33 | self.cardView.movedDirectionType = XYMovedDirectionLeft; 34 | ``` 35 | 36 | 37 | ```swift 38 | // 重叠的方向 39 | self.cardView.coverDirectionType = XYCoverDirectionBottom; 40 | // 移动的方向 41 | self.cardView.movedDirectionType = XYMovedDirectionRight; 42 | ``` 43 | 44 | 45 | 46 | #### 在早期项目中用到的,所以抽取出来,封装的比较粗糙,如果有实际需求欢迎提出,会继续改进。 47 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 64B3EDF7286D5362004A9A71 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EDF6286D5362004A9A71 /* AppDelegate.m */; }; 11 | 64B3EDFA286D5362004A9A71 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EDF9286D5362004A9A71 /* SceneDelegate.m */; }; 12 | 64B3EDFD286D5362004A9A71 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EDFC286D5362004A9A71 /* ViewController.m */; }; 13 | 64B3EE00286D5362004A9A71 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64B3EDFE286D5362004A9A71 /* Main.storyboard */; }; 14 | 64B3EE02286D5364004A9A71 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 64B3EE01286D5364004A9A71 /* Assets.xcassets */; }; 15 | 64B3EE05286D5364004A9A71 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 64B3EE03286D5364004A9A71 /* LaunchScreen.storyboard */; }; 16 | 64B3EE08286D5364004A9A71 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EE07286D5364004A9A71 /* main.m */; }; 17 | 64B3EE19286D5378004A9A71 /* XYCoverCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EE0F286D5378004A9A71 /* XYCoverCell.m */; }; 18 | 64B3EE1A286D5378004A9A71 /* XYCoverModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EE12286D5378004A9A71 /* XYCoverModel.m */; }; 19 | 64B3EE1B286D5378004A9A71 /* XYCoverCardView.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EE15286D5378004A9A71 /* XYCoverCardView.m */; }; 20 | 64B3EE1D286D5378004A9A71 /* XYCoverCardViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 64B3EE17286D5378004A9A71 /* XYCoverCardViewLayout.m */; }; 21 | 64BE156D28752AC000869C4A /* XYCoverCardCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 64BE156B28752AC000869C4A /* XYCoverCardCollectionView.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 64B3EDF2286D5362004A9A71 /* XYCoverCardViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XYCoverCardViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 64B3EDF5286D5362004A9A71 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 27 | 64B3EDF6286D5362004A9A71 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 28 | 64B3EDF8286D5362004A9A71 /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; 29 | 64B3EDF9286D5362004A9A71 /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; 30 | 64B3EDFB286D5362004A9A71 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 31 | 64B3EDFC286D5362004A9A71 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 32 | 64B3EDFF286D5362004A9A71 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 33 | 64B3EE01286D5364004A9A71 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 34 | 64B3EE04286D5364004A9A71 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 35 | 64B3EE06286D5364004A9A71 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 64B3EE07286D5364004A9A71 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 37 | 64B3EE0F286D5378004A9A71 /* XYCoverCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYCoverCell.m; sourceTree = ""; }; 38 | 64B3EE10286D5378004A9A71 /* XYCoverCardView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYCoverCardView.h; sourceTree = ""; }; 39 | 64B3EE12286D5378004A9A71 /* XYCoverModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYCoverModel.m; sourceTree = ""; }; 40 | 64B3EE13286D5378004A9A71 /* XYCoverCardViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYCoverCardViewLayout.h; sourceTree = ""; }; 41 | 64B3EE14286D5378004A9A71 /* XYCoverCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYCoverCell.h; sourceTree = ""; }; 42 | 64B3EE15286D5378004A9A71 /* XYCoverCardView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYCoverCardView.m; sourceTree = ""; }; 43 | 64B3EE17286D5378004A9A71 /* XYCoverCardViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYCoverCardViewLayout.m; sourceTree = ""; }; 44 | 64B3EE18286D5378004A9A71 /* XYCoverModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYCoverModel.h; sourceTree = ""; }; 45 | 64BE156B28752AC000869C4A /* XYCoverCardCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XYCoverCardCollectionView.m; sourceTree = ""; }; 46 | 64BE156C28752AC000869C4A /* XYCoverCardCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XYCoverCardCollectionView.h; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 64B3EDEF286D5362004A9A71 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | 64B3EDE9286D5362004A9A71 = { 61 | isa = PBXGroup; 62 | children = ( 63 | 64B3EDF4286D5362004A9A71 /* XYCoverCardViewDemo */, 64 | 64B3EDF3286D5362004A9A71 /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | 64B3EDF3286D5362004A9A71 /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 64B3EDF2286D5362004A9A71 /* XYCoverCardViewDemo.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | 64B3EDF4286D5362004A9A71 /* XYCoverCardViewDemo */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 64B3EE0E286D5378004A9A71 /* XYCoverCardView */, 80 | 64B3EDF5286D5362004A9A71 /* AppDelegate.h */, 81 | 64B3EDF6286D5362004A9A71 /* AppDelegate.m */, 82 | 64B3EDF8286D5362004A9A71 /* SceneDelegate.h */, 83 | 64B3EDF9286D5362004A9A71 /* SceneDelegate.m */, 84 | 64B3EDFB286D5362004A9A71 /* ViewController.h */, 85 | 64B3EDFC286D5362004A9A71 /* ViewController.m */, 86 | 64B3EDFE286D5362004A9A71 /* Main.storyboard */, 87 | 64B3EE01286D5364004A9A71 /* Assets.xcassets */, 88 | 64B3EE03286D5364004A9A71 /* LaunchScreen.storyboard */, 89 | 64B3EE06286D5364004A9A71 /* Info.plist */, 90 | 64B3EE07286D5364004A9A71 /* main.m */, 91 | ); 92 | path = XYCoverCardViewDemo; 93 | sourceTree = ""; 94 | }; 95 | 64B3EE0E286D5378004A9A71 /* XYCoverCardView */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 64BE156728751FB200869C4A /* Layout */, 99 | 64B3EE10286D5378004A9A71 /* XYCoverCardView.h */, 100 | 64B3EE15286D5378004A9A71 /* XYCoverCardView.m */, 101 | 64BE156C28752AC000869C4A /* XYCoverCardCollectionView.h */, 102 | 64BE156B28752AC000869C4A /* XYCoverCardCollectionView.m */, 103 | 64B3EE14286D5378004A9A71 /* XYCoverCell.h */, 104 | 64B3EE0F286D5378004A9A71 /* XYCoverCell.m */, 105 | 64B3EE18286D5378004A9A71 /* XYCoverModel.h */, 106 | 64B3EE12286D5378004A9A71 /* XYCoverModel.m */, 107 | ); 108 | path = XYCoverCardView; 109 | sourceTree = ""; 110 | }; 111 | 64BE156728751FB200869C4A /* Layout */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 64B3EE13286D5378004A9A71 /* XYCoverCardViewLayout.h */, 115 | 64B3EE17286D5378004A9A71 /* XYCoverCardViewLayout.m */, 116 | ); 117 | path = Layout; 118 | sourceTree = ""; 119 | }; 120 | /* End PBXGroup section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | 64B3EDF1286D5362004A9A71 /* XYCoverCardViewDemo */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = 64B3EE0B286D5364004A9A71 /* Build configuration list for PBXNativeTarget "XYCoverCardViewDemo" */; 126 | buildPhases = ( 127 | 64B3EDEE286D5362004A9A71 /* Sources */, 128 | 64B3EDEF286D5362004A9A71 /* Frameworks */, 129 | 64B3EDF0286D5362004A9A71 /* Resources */, 130 | ); 131 | buildRules = ( 132 | ); 133 | dependencies = ( 134 | ); 135 | name = XYCoverCardViewDemo; 136 | productName = XYCoverCardViewDemo; 137 | productReference = 64B3EDF2286D5362004A9A71 /* XYCoverCardViewDemo.app */; 138 | productType = "com.apple.product-type.application"; 139 | }; 140 | /* End PBXNativeTarget section */ 141 | 142 | /* Begin PBXProject section */ 143 | 64B3EDEA286D5362004A9A71 /* Project object */ = { 144 | isa = PBXProject; 145 | attributes = { 146 | BuildIndependentTargetsInParallel = 1; 147 | LastUpgradeCheck = 1340; 148 | TargetAttributes = { 149 | 64B3EDF1286D5362004A9A71 = { 150 | CreatedOnToolsVersion = 13.4.1; 151 | }; 152 | }; 153 | }; 154 | buildConfigurationList = 64B3EDED286D5362004A9A71 /* Build configuration list for PBXProject "XYCoverCardViewDemo" */; 155 | compatibilityVersion = "Xcode 13.0"; 156 | developmentRegion = en; 157 | hasScannedForEncodings = 0; 158 | knownRegions = ( 159 | en, 160 | Base, 161 | ); 162 | mainGroup = 64B3EDE9286D5362004A9A71; 163 | productRefGroup = 64B3EDF3286D5362004A9A71 /* Products */; 164 | projectDirPath = ""; 165 | projectRoot = ""; 166 | targets = ( 167 | 64B3EDF1286D5362004A9A71 /* XYCoverCardViewDemo */, 168 | ); 169 | }; 170 | /* End PBXProject section */ 171 | 172 | /* Begin PBXResourcesBuildPhase section */ 173 | 64B3EDF0286D5362004A9A71 /* Resources */ = { 174 | isa = PBXResourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | 64B3EE05286D5364004A9A71 /* LaunchScreen.storyboard in Resources */, 178 | 64B3EE02286D5364004A9A71 /* Assets.xcassets in Resources */, 179 | 64B3EE00286D5362004A9A71 /* Main.storyboard in Resources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXResourcesBuildPhase section */ 184 | 185 | /* Begin PBXSourcesBuildPhase section */ 186 | 64B3EDEE286D5362004A9A71 /* Sources */ = { 187 | isa = PBXSourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 64B3EDFD286D5362004A9A71 /* ViewController.m in Sources */, 191 | 64B3EDF7286D5362004A9A71 /* AppDelegate.m in Sources */, 192 | 64B3EE1A286D5378004A9A71 /* XYCoverModel.m in Sources */, 193 | 64B3EE08286D5364004A9A71 /* main.m in Sources */, 194 | 64B3EE1B286D5378004A9A71 /* XYCoverCardView.m in Sources */, 195 | 64B3EE19286D5378004A9A71 /* XYCoverCell.m in Sources */, 196 | 64B3EE1D286D5378004A9A71 /* XYCoverCardViewLayout.m in Sources */, 197 | 64BE156D28752AC000869C4A /* XYCoverCardCollectionView.m in Sources */, 198 | 64B3EDFA286D5362004A9A71 /* SceneDelegate.m in Sources */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | /* End PBXSourcesBuildPhase section */ 203 | 204 | /* Begin PBXVariantGroup section */ 205 | 64B3EDFE286D5362004A9A71 /* Main.storyboard */ = { 206 | isa = PBXVariantGroup; 207 | children = ( 208 | 64B3EDFF286D5362004A9A71 /* Base */, 209 | ); 210 | name = Main.storyboard; 211 | sourceTree = ""; 212 | }; 213 | 64B3EE03286D5364004A9A71 /* LaunchScreen.storyboard */ = { 214 | isa = PBXVariantGroup; 215 | children = ( 216 | 64B3EE04286D5364004A9A71 /* Base */, 217 | ); 218 | name = LaunchScreen.storyboard; 219 | sourceTree = ""; 220 | }; 221 | /* End PBXVariantGroup section */ 222 | 223 | /* Begin XCBuildConfiguration section */ 224 | 64B3EE09286D5364004A9A71 /* Debug */ = { 225 | isa = XCBuildConfiguration; 226 | buildSettings = { 227 | ALWAYS_SEARCH_USER_PATHS = NO; 228 | CLANG_ANALYZER_NONNULL = YES; 229 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = YES; 233 | CLANG_ENABLE_OBJC_WEAK = YES; 234 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 235 | CLANG_WARN_BOOL_CONVERSION = YES; 236 | CLANG_WARN_COMMA = YES; 237 | CLANG_WARN_CONSTANT_CONVERSION = YES; 238 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 239 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 240 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 247 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 250 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 251 | CLANG_WARN_STRICT_PROTOTYPES = YES; 252 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 253 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 254 | CLANG_WARN_UNREACHABLE_CODE = YES; 255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 256 | COPY_PHASE_STRIP = NO; 257 | DEBUG_INFORMATION_FORMAT = dwarf; 258 | ENABLE_STRICT_OBJC_MSGSEND = YES; 259 | ENABLE_TESTABILITY = YES; 260 | GCC_C_LANGUAGE_STANDARD = gnu11; 261 | GCC_DYNAMIC_NO_PIC = NO; 262 | GCC_NO_COMMON_BLOCKS = YES; 263 | GCC_OPTIMIZATION_LEVEL = 0; 264 | GCC_PREPROCESSOR_DEFINITIONS = ( 265 | "DEBUG=1", 266 | "$(inherited)", 267 | ); 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | IPHONEOS_DEPLOYMENT_TARGET = 15.5; 275 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 276 | MTL_FAST_MATH = YES; 277 | ONLY_ACTIVE_ARCH = YES; 278 | SDKROOT = iphoneos; 279 | }; 280 | name = Debug; 281 | }; 282 | 64B3EE0A286D5364004A9A71 /* Release */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | CLANG_ANALYZER_NONNULL = YES; 287 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 288 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 289 | CLANG_ENABLE_MODULES = YES; 290 | CLANG_ENABLE_OBJC_ARC = YES; 291 | CLANG_ENABLE_OBJC_WEAK = YES; 292 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 293 | CLANG_WARN_BOOL_CONVERSION = YES; 294 | CLANG_WARN_COMMA = YES; 295 | CLANG_WARN_CONSTANT_CONVERSION = YES; 296 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 297 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 298 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 299 | CLANG_WARN_EMPTY_BODY = YES; 300 | CLANG_WARN_ENUM_CONVERSION = YES; 301 | CLANG_WARN_INFINITE_RECURSION = YES; 302 | CLANG_WARN_INT_CONVERSION = YES; 303 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 304 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 305 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 306 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 307 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 308 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 309 | CLANG_WARN_STRICT_PROTOTYPES = YES; 310 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 311 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 312 | CLANG_WARN_UNREACHABLE_CODE = YES; 313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 314 | COPY_PHASE_STRIP = NO; 315 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 316 | ENABLE_NS_ASSERTIONS = NO; 317 | ENABLE_STRICT_OBJC_MSGSEND = YES; 318 | GCC_C_LANGUAGE_STANDARD = gnu11; 319 | GCC_NO_COMMON_BLOCKS = YES; 320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 322 | GCC_WARN_UNDECLARED_SELECTOR = YES; 323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 324 | GCC_WARN_UNUSED_FUNCTION = YES; 325 | GCC_WARN_UNUSED_VARIABLE = YES; 326 | IPHONEOS_DEPLOYMENT_TARGET = 15.5; 327 | MTL_ENABLE_DEBUG_INFO = NO; 328 | MTL_FAST_MATH = YES; 329 | SDKROOT = iphoneos; 330 | VALIDATE_PRODUCT = YES; 331 | }; 332 | name = Release; 333 | }; 334 | 64B3EE0C286D5364004A9A71 /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 338 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 339 | CODE_SIGN_STYLE = Automatic; 340 | CURRENT_PROJECT_VERSION = 1; 341 | GENERATE_INFOPLIST_FILE = YES; 342 | INFOPLIST_FILE = XYCoverCardViewDemo/Info.plist; 343 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 344 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 345 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 346 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 347 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 348 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 349 | LD_RUNPATH_SEARCH_PATHS = ( 350 | "$(inherited)", 351 | "@executable_path/Frameworks", 352 | ); 353 | MARKETING_VERSION = 1.0; 354 | PRODUCT_BUNDLE_IDENTIFIER = iOSyan.XYCoverCardViewDemo; 355 | PRODUCT_NAME = "$(TARGET_NAME)"; 356 | SWIFT_EMIT_LOC_STRINGS = YES; 357 | TARGETED_DEVICE_FAMILY = "1,2"; 358 | }; 359 | name = Debug; 360 | }; 361 | 64B3EE0D286D5364004A9A71 /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 365 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 366 | CODE_SIGN_STYLE = Automatic; 367 | CURRENT_PROJECT_VERSION = 1; 368 | GENERATE_INFOPLIST_FILE = YES; 369 | INFOPLIST_FILE = XYCoverCardViewDemo/Info.plist; 370 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 371 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 372 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 373 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 374 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 375 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 376 | LD_RUNPATH_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "@executable_path/Frameworks", 379 | ); 380 | MARKETING_VERSION = 1.0; 381 | PRODUCT_BUNDLE_IDENTIFIER = iOSyan.XYCoverCardViewDemo; 382 | PRODUCT_NAME = "$(TARGET_NAME)"; 383 | SWIFT_EMIT_LOC_STRINGS = YES; 384 | TARGETED_DEVICE_FAMILY = "1,2"; 385 | }; 386 | name = Release; 387 | }; 388 | /* End XCBuildConfiguration section */ 389 | 390 | /* Begin XCConfigurationList section */ 391 | 64B3EDED286D5362004A9A71 /* Build configuration list for PBXProject "XYCoverCardViewDemo" */ = { 392 | isa = XCConfigurationList; 393 | buildConfigurations = ( 394 | 64B3EE09286D5364004A9A71 /* Debug */, 395 | 64B3EE0A286D5364004A9A71 /* Release */, 396 | ); 397 | defaultConfigurationIsVisible = 0; 398 | defaultConfigurationName = Release; 399 | }; 400 | 64B3EE0B286D5364004A9A71 /* Build configuration list for PBXNativeTarget "XYCoverCardViewDemo" */ = { 401 | isa = XCConfigurationList; 402 | buildConfigurations = ( 403 | 64B3EE0C286D5364004A9A71 /* Debug */, 404 | 64B3EE0D286D5364004A9A71 /* Release */, 405 | ); 406 | defaultConfigurationIsVisible = 0; 407 | defaultConfigurationName = Release; 408 | }; 409 | /* End XCConfigurationList section */ 410 | }; 411 | rootObject = 64B3EDEA286D5362004A9A71 /* Project object */; 412 | } 413 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // XYCoverCardViewDemo 4 | // 5 | // Created by ecsage on 2022/6/30. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // XYCoverCardViewDemo 4 | // 5 | // Created by ecsage on 2022/6/30. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | 12 | @end 13 | 14 | @implementation AppDelegate 15 | 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 18 | // Override point for customization after application launch. 19 | return YES; 20 | } 21 | 22 | 23 | #pragma mark - UISceneSession lifecycle 24 | 25 | 26 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options API_AVAILABLE(ios(13.0)){ 27 | // Called when a new scene session is being created. 28 | // Use this method to select a configuration to create the new scene with. 29 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 30 | } 31 | 32 | 33 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions API_AVAILABLE(ios(13.0)){ 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "2x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "83.5x83.5" 82 | }, 83 | { 84 | "idiom" : "ios-marketing", 85 | "scale" : "1x", 86 | "size" : "1024x1024" 87 | } 88 | ], 89 | "info" : { 90 | "author" : "xcode", 91 | "version" : 1 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/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 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/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 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // XYCoverCardViewDemo 4 | // 5 | // Created by ecsage on 2022/6/30. 6 | // 7 | 8 | #import 9 | 10 | @interface SceneDelegate : UIResponder 11 | 12 | @property (strong, nonatomic) UIWindow * window; 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.m 3 | // XYCoverCardViewDemo 4 | // 5 | // Created by ecsage on 2022/6/30. 6 | // 7 | 8 | #import "SceneDelegate.h" 9 | 10 | @interface SceneDelegate () 11 | 12 | @end 13 | 14 | @implementation SceneDelegate 15 | 16 | 17 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions API_AVAILABLE(ios(13.0)){ 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | } 22 | 23 | 24 | - (void)sceneDidDisconnect:(UIScene *)scene API_AVAILABLE(ios(13.0)){ 25 | // Called as the scene is being released by the system. 26 | // This occurs shortly after the scene enters the background, or when its session is discarded. 27 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 28 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 29 | } 30 | 31 | 32 | - (void)sceneDidBecomeActive:(UIScene *)scene API_AVAILABLE(ios(13.0)){ 33 | // Called when the scene has moved from an inactive state to an active state. 34 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 35 | } 36 | 37 | 38 | - (void)sceneWillResignActive:(UIScene *)scene API_AVAILABLE(ios(13.0)){ 39 | // Called when the scene will move from an active state to an inactive state. 40 | // This may occur due to temporary interruptions (ex. an incoming phone call). 41 | } 42 | 43 | 44 | - (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0)){ 45 | // Called as the scene transitions from the background to the foreground. 46 | // Use this method to undo the changes made on entering the background. 47 | } 48 | 49 | 50 | - (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0)){ 51 | // Called as the scene transitions from the foreground to the background. 52 | // Use this method to save data, release shared resources, and store enough scene-specific state information 53 | // to restore the scene back to its current state. 54 | } 55 | 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // XYCoverCardViewDemo 4 | // 5 | // Created by ecsage on 2022/6/30. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // XYCoverCardViewDemo 4 | // 5 | // Created by ecsage on 2022/6/30. 6 | // 7 | 8 | #import "ViewController.h" 9 | #import "XYCoverCardView.h" 10 | #import "XYCoverModel.h" 11 | #import "XYCoverCell.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (nonatomic, strong) XYCoverCardView *cardView; 16 | @property (nonatomic, strong) NSMutableArray *dataArray; 17 | 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | 25 | [self setupCoverView]; 26 | } 27 | 28 | - (void)setupCoverView { 29 | self.cardView = [[XYCoverCardView alloc] initWithFrame:CGRectMake(0, 0, 280, 180)]; 30 | self.cardView.dataArray = self.dataArray; 31 | // 重叠的方向 32 | self.cardView.coverDirectionType = XYCoverDirectionRight; 33 | // 移动的方向 34 | self.cardView.movedDirectionType = XYMovedDirectionLeft; 35 | self.cardView.timerDuration = 2.0; 36 | 37 | self.cardView.center = self.view.center; 38 | [self.cardView registerCellClass:[XYCoverCell class] forCellWithReuseIdentifier:@"cellID"]; 39 | self.cardView.cardViewDataSource = self; 40 | [self.view addSubview:self.cardView]; 41 | } 42 | 43 | #pragma mark - XYCoverCardViewDataSource 44 | - (UICollectionViewCell *)coverCardView:(XYCoverCardView *)coverCardView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath { 45 | XYCoverCell *cell = (XYCoverCell *)[coverCardView dequeueReusableCellWithReuseIdentifier:@"cellID" forIndexPath:indexPath]; 46 | XYCoverModel *m = self.dataArray[indexPath.item]; 47 | cell.label.text = m.name; 48 | cell.backgroundColor = [self randomColor]; 49 | return cell; 50 | } 51 | 52 | - (void)coverCardView:(XYCoverCardView *)coverCardView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 53 | XYCoverModel *m = self.dataArray[indexPath.item]; 54 | NSLog(@"%@", m.name); 55 | } 56 | 57 | #pragma mark - getter 58 | - (NSMutableArray *)dataArray { 59 | if (_dataArray == nil) { 60 | _dataArray = [NSMutableArray array]; 61 | for (int i = 0; i < 3; i++) { 62 | XYCoverModel *m = [[XYCoverModel alloc] init]; 63 | m.name = [NSString stringWithFormat:@"%d", i]; 64 | [_dataArray addObject:m]; 65 | } 66 | } 67 | return _dataArray; 68 | } 69 | 70 | - (UIColor *)randomColor { 71 | CGFloat hue = ( arc4random() % 256 / 256.0 ); //0.0 to 1.0 72 | CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0,away from white 73 | CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; //0.5 to 1.0,away from black 74 | return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1]; 75 | } 76 | 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/Layout/XYCoverCardViewLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCardViewLayout.h 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /// 覆盖的方向 14 | typedef NS_ENUM(NSInteger, XYCoverDirectionType) 15 | { 16 | XYCoverDirectionRight, 17 | XYCoverDirectionBottom, 18 | }; 19 | 20 | @interface XYCoverCardViewLayout : UICollectionViewLayout 21 | 22 | /// 覆盖的方向 23 | @property (nonatomic, assign) XYCoverDirectionType coverDirectionType; 24 | 25 | /// 卡片左右之间的距离 26 | @property (nonatomic, assign) CGFloat lineSpacing; 27 | /// 卡片上下之间的距离 28 | @property (nonatomic, assign) CGFloat itemSpacing; 29 | 30 | @end 31 | 32 | NS_ASSUME_NONNULL_END 33 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/Layout/XYCoverCardViewLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCardViewLayout.m 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import "XYCoverCardViewLayout.h" 10 | 11 | @interface XYCoverCardViewLayout () 12 | @property (nonatomic, strong) NSMutableArray *attributesArray; 13 | @property (nonatomic, assign) CGRect contentBounds; 14 | @end 15 | 16 | @implementation XYCoverCardViewLayout 17 | 18 | - (instancetype)init 19 | { 20 | self = [super init]; 21 | if (self) { 22 | [self setup]; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)setup { 28 | self.coverDirectionType = XYCoverDirectionBottom; 29 | self.lineSpacing = 15; 30 | self.itemSpacing = 15; 31 | } 32 | 33 | - (void)prepareLayout { 34 | [super prepareLayout]; 35 | 36 | [self.attributesArray removeAllObjects]; 37 | self.contentBounds = CGRectMake(0, 0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height); 38 | NSInteger count = [self.collectionView numberOfItemsInSection:0]; 39 | NSInteger currentIndex = 0; 40 | 41 | while (currentIndex < count) { 42 | UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:currentIndex inSection:0]]; 43 | attributes.frame = self.collectionView.bounds; 44 | 45 | // 计算每个 cell 的宽度和高度 46 | CGFloat width = self.collectionView.bounds.size.width - currentIndex * self.lineSpacing * 2.0; 47 | CGFloat height = self.collectionView.bounds.size.height - currentIndex * self.itemSpacing * 2.0; 48 | // 计算出缩放的比例 49 | CGFloat scaleX = width / attributes.bounds.size.width; 50 | CGFloat scaleY = height / attributes.bounds.size.height; 51 | 52 | CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scaleX, scaleY); 53 | 54 | CGAffineTransform transform; 55 | if (self.coverDirectionType == XYCoverDirectionRight) { 56 | transform = CGAffineTransformTranslate(scaleTransform, currentIndex * self.itemSpacing * 2.0, 0); 57 | } else { 58 | transform = CGAffineTransformTranslate(scaleTransform, 0, currentIndex * self.itemSpacing * 2.0); 59 | } 60 | 61 | attributes.transform = transform; 62 | [self.attributesArray insertObject:attributes atIndex:0]; 63 | 64 | if (currentIndex == 0) { 65 | attributes.transform = CGAffineTransformIdentity; 66 | } 67 | 68 | currentIndex += 1; 69 | } 70 | } 71 | 72 | - (CGSize)collectionViewContentSize { 73 | return self.contentBounds.size; 74 | } 75 | 76 | - (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { 77 | return self.attributesArray; 78 | } 79 | 80 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 81 | return self.attributesArray[indexPath.item]; 82 | } 83 | 84 | #pragma mark - getter 85 | - (NSMutableArray *)attributesArray { 86 | if (_attributesArray == nil) { 87 | _attributesArray = [NSMutableArray array]; 88 | } 89 | return _attributesArray; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverCardCollectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCardCollectionView.h 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface XYCoverCardCollectionView : UICollectionView 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverCardCollectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCardCollectionView.m 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import "XYCoverCardCollectionView.h" 10 | 11 | @implementation XYCoverCardCollectionView 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { 14 | self = [super initWithFrame:frame collectionViewLayout:layout]; 15 | return self; 16 | } 17 | 18 | - (void)didAddSubview:(UIView *)subview { 19 | [super didAddSubview:subview]; 20 | 21 | if ([subview isKindOfClass:[UICollectionViewCell class]]) { 22 | [self sendSubviewToBack:subview]; 23 | } 24 | } 25 | 26 | /* 27 | // Only override drawRect: if you perform custom drawing. 28 | // An empty implementation adversely affects performance during animation. 29 | - (void)drawRect:(CGRect)rect { 30 | // Drawing code 31 | } 32 | */ 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverCardView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCardView.h 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "XYCoverCardViewLayout.h" 11 | @class XYCoverCardView; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /// 划走的方向 16 | typedef NS_ENUM(NSInteger, XYMovedDirectionType) 17 | { 18 | XYMovedDirectionLeft, 19 | XYMovedDirectionRight, 20 | }; 21 | 22 | typedef void (^UpdatesBlock)(void); 23 | typedef void (^Completion)(BOOL); 24 | 25 | @protocol XYCoverCardViewDataSource 26 | 27 | - (UICollectionViewCell *)coverCardView:(XYCoverCardView *)coverCardView cellForItemAtIndexPath:(NSIndexPath *)indexPath; 28 | 29 | @optional 30 | - (void)coverCardView:(XYCoverCardView *)coverCardView didSelectItemAtIndexPath:(NSIndexPath *)indexPath; 31 | 32 | @end 33 | 34 | @interface XYCoverCardView : UIView 35 | 36 | /// 覆盖的方向 37 | @property (nonatomic, assign) XYCoverDirectionType coverDirectionType; 38 | 39 | /// 划走的方向 40 | @property (nonatomic, assign) XYMovedDirectionType movedDirectionType; 41 | 42 | /// 自动轮播时间间隔 默认2s 43 | @property (nonatomic, assign) CGFloat timerDuration; 44 | 45 | /// 数据数组 46 | @property (nonatomic, strong) NSMutableArray *dataArray; 47 | 48 | @property (nonatomic, strong) UICollectionView *collectionView; 49 | @property (nonatomic, assign) CGPoint movingPoint; 50 | @property (nonatomic, weak) id cardViewDataSource; 51 | 52 | - (void)registerCellClass:(Class)anyClass forCellWithReuseIdentifier:(NSString *)identifier; 53 | - (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(nonnull NSIndexPath *)indexPath; 54 | - (void)reloadData; 55 | - (void)insertCellsAtIndexPath:(NSArray *)indexPaths; 56 | - (void)performBatchUpdates:(UpdatesBlock)updates completion:(Completion)completion; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END 61 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverCardView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCardView.m 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import "XYCoverCardView.h" 10 | #import "XYCoverCardCollectionView.h" 11 | 12 | @interface XYCoverCardView () 13 | 14 | @property (nonatomic,strong) UIPageControl *pageControl; 15 | 16 | @property (nonatomic,strong) NSTimer *timer; 17 | 18 | @property (nonatomic, assign) NSInteger currentPage; 19 | 20 | @end 21 | 22 | @implementation XYCoverCardView 23 | 24 | - (instancetype)initWithFrame:(CGRect)frame { 25 | self = [super initWithFrame:frame]; 26 | if (self) { 27 | [self setup]; 28 | [self setupUI]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)setup { 34 | self.dataArray = [NSMutableArray array]; 35 | 36 | self.timerDuration = 2.0; 37 | self.movedDirectionType = XYMovedDirectionRight; 38 | } 39 | 40 | - (void)setupUI { 41 | 42 | XYCoverCardViewLayout *layout = [[XYCoverCardViewLayout alloc] init]; 43 | layout.coverDirectionType = self.coverDirectionType; 44 | self.collectionView = [[XYCoverCardCollectionView alloc] initWithFrame:self.bounds collectionViewLayout:layout]; 45 | self.collectionView.clipsToBounds = NO; 46 | self.collectionView.dataSource = self; 47 | self.collectionView.delegate = self; 48 | 49 | UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)]; 50 | [self.collectionView addGestureRecognizer:panGesture]; 51 | [self addSubview:self.collectionView]; 52 | } 53 | 54 | - (void)layoutSubviews { 55 | [super layoutSubviews]; 56 | self.collectionView.frame = self.bounds; 57 | } 58 | 59 | - (void)removeFromSuperview { 60 | [super removeFromSuperview]; 61 | 62 | [self removeTimer]; 63 | } 64 | 65 | - (void)dealloc { 66 | NSLog(@"dealloc"); 67 | } 68 | 69 | - (void)registerCellClass:(Class)anyClass forCellWithReuseIdentifier:(NSString *)identifier { 70 | [self.collectionView registerClass:anyClass forCellWithReuseIdentifier:identifier]; 71 | } 72 | 73 | - (UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(nonnull NSIndexPath *)indexPath { 74 | return [self.collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath]; 75 | } 76 | 77 | - (void)reloadData { 78 | [self.collectionView reloadData]; 79 | 80 | self.pageControl.numberOfPages = self.dataArray.count; 81 | self.pageControl.currentPage = 0; 82 | [self refreshPageControlFrame]; 83 | } 84 | 85 | - (void)refreshPageControlFrame { 86 | CGSize size = [self.pageControl sizeForNumberOfPages:self.pageControl.numberOfPages]; 87 | self.pageControl.frame = CGRectMake((self.frame.size.width - size.width)/2, self.frame.size.height - size.height - 10, size.width, size.height); 88 | [self bringSubviewToFront:self.pageControl]; 89 | } 90 | 91 | #pragma mark - setter 92 | - (void)setCardViewDataSource:(id)cardViewDataSource { 93 | _cardViewDataSource = cardViewDataSource; 94 | 95 | [self addTimer]; 96 | } 97 | 98 | - (void)setDataArray:(NSMutableArray *)dataArray { 99 | _dataArray = dataArray; 100 | 101 | self.pageControl.hidden = dataArray.count < 2; 102 | if (self.pageControl.hidden) return; 103 | 104 | self.pageControl.numberOfPages = dataArray.count; 105 | self.pageControl.currentPage = 0; 106 | [self refreshPageControlFrame]; 107 | } 108 | 109 | #pragma mark - NSTimer 110 | // 添加定时器 111 | - (void)addTimer { 112 | 113 | NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:self.timerDuration target:self selector:@selector(aotuNextPage) userInfo:nil repeats:YES]; 114 | // 添加到runloop中 115 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 116 | self.timer = timer; 117 | } 118 | 119 | // 删除定时器 120 | - (void)removeTimer { 121 | 122 | [self.timer invalidate]; 123 | self.timer = nil; 124 | } 125 | 126 | // 自动滚动 127 | - (void)aotuNextPage { 128 | [self nextPage: self.movedDirectionType]; 129 | } 130 | 131 | #pragma mark - 卡片的处理 132 | - (void)insertCellsAtIndexPath:(NSArray *)indexPaths { 133 | [UIView performWithoutAnimation:^{ 134 | [self.collectionView insertItemsAtIndexPaths:indexPaths]; 135 | }]; 136 | } 137 | 138 | - (void)performBatchUpdates:(UpdatesBlock)updates completion:(Completion)completion { 139 | [self.collectionView performBatchUpdates:updates completion:completion]; 140 | } 141 | 142 | // 滑到下一张 143 | - (void)moveCell:(UICollectionViewCell *)cell toNextWithGesture:(UIPanGestureRecognizer *)panGesture point:(CGPoint)movePoint { 144 | self.movingPoint = CGPointMake(self.movingPoint.x + movePoint.x, self.movingPoint.y); 145 | cell.transform = CGAffineTransformMakeTranslation(self.movingPoint.x, self.movingPoint.y); 146 | [panGesture setTranslation:CGPointZero inView:panGesture.view]; 147 | } 148 | 149 | // 滚动到下一页 150 | - (void)nextPage:(XYMovedDirectionType)movedDirection { 151 | if (self.dataArray.count < 2) return; 152 | 153 | self.currentPage++; 154 | if (self.currentPage == self.dataArray.count) { 155 | self.currentPage = 0; 156 | } 157 | 158 | CGFloat x = movedDirection == XYMovedDirectionLeft ? -200 : 200; 159 | 160 | UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; 161 | 162 | [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ 163 | cell.transform = CGAffineTransformTranslate(cell.transform, x, 0); 164 | } completion:^(BOOL finished) { 165 | 166 | cell.hidden = YES; 167 | 168 | /// 移除cell之后, 调用此方法, 需要从数据源移除model 169 | id model = self.dataArray[0]; 170 | [self.dataArray removeObjectAtIndex:0]; 171 | 172 | [self performBatchUpdates:^{ 173 | // 如果只有两个 是两个Indicator 没有items 174 | if (self.collectionView.subviews.count == 2) return; 175 | [self.collectionView deleteItemsAtIndexPaths:@[[NSIndexPath indexPathForItem:0 inSection:0]]]; 176 | 177 | } completion:^(BOOL finished) { 178 | NSArray *indexes = @[[NSIndexPath indexPathForItem:self.dataArray.count inSection:0]]; 179 | [self.dataArray addObject:model]; 180 | 181 | [self insertCellsAtIndexPath:indexes]; 182 | cell.hidden = NO; 183 | }]; 184 | }]; 185 | 186 | self.pageControl.currentPage = self.currentPage; 187 | } 188 | 189 | #pragma mark - 手势 190 | - (void)panGestureAction:(UIPanGestureRecognizer *)panGesture { 191 | 192 | if (self.dataArray.count < 2) return; 193 | 194 | // 获取到当前cell 195 | UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; 196 | 197 | CGPoint movePoint = [panGesture translationInView:panGesture.view]; 198 | switch (panGesture.state) { 199 | case UIGestureRecognizerStateBegan: 200 | { 201 | self.movingPoint = CGPointZero; 202 | // 暂停 203 | [self removeTimer]; 204 | } 205 | break; 206 | case UIGestureRecognizerStateChanged: 207 | { 208 | // 只允许左右滑动 209 | if (movePoint.y > 20 || movePoint.y < -20) return; 210 | 211 | // 卡片划走的方向 -> 右 下一张 212 | [self moveCell:cell toNextWithGesture:panGesture point:movePoint]; 213 | } 214 | break; 215 | case UIGestureRecognizerStateEnded: 216 | { 217 | if (self.movingPoint.x > 100) { // 右滑 218 | 219 | // 卡片划走的方向 -> 左 下一张 220 | [self nextPage: XYMovedDirectionRight]; 221 | 222 | } else if (self.movingPoint.x < -100) { // 左滑 223 | // 卡片划走的方向 -> 左 下一张 224 | [self nextPage: XYMovedDirectionLeft]; 225 | } 226 | else 227 | { 228 | [UIView animateWithDuration:0.25 animations:^{ 229 | // 还原到最初的状态 230 | cell.transform = CGAffineTransformIdentity; 231 | }]; 232 | } 233 | 234 | // 继续 235 | [self addTimer]; 236 | } 237 | break; 238 | 239 | default: 240 | break; 241 | } 242 | } 243 | 244 | #pragma mark - UICollectionViewDataSource, UICollectionViewDelegate 245 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 246 | return self.dataArray.count; 247 | } 248 | 249 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 250 | return [self.cardViewDataSource coverCardView:self cellForItemAtIndexPath:indexPath]; 251 | } 252 | 253 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 254 | NSLog(@"indexPath.item ==== %zd", indexPath.item); 255 | if ([self.cardViewDataSource respondsToSelector:@selector(coverCardView:didSelectItemAtIndexPath:)]) { 256 | [self.cardViewDataSource coverCardView:self didSelectItemAtIndexPath:indexPath]; 257 | } 258 | } 259 | 260 | #pragma mark - getter 261 | //懒加载pageControl 262 | - (UIPageControl *)pageControl { 263 | 264 | if (_pageControl == nil) { 265 | //分页控件,本质上和cardView没有任何关系,是2个独立的控件 266 | _pageControl = [[UIPageControl alloc]init]; 267 | _pageControl.pageIndicatorTintColor = [UIColor yellowColor]; 268 | _pageControl.currentPageIndicatorTintColor = [UIColor redColor]; 269 | _pageControl.currentPage = 0; 270 | [self addSubview:_pageControl]; 271 | } 272 | return _pageControl; 273 | } 274 | 275 | /* 276 | // Only override drawRect: if you perform custom drawing. 277 | // An empty implementation adversely affects performance during animation. 278 | - (void)drawRect:(CGRect)rect { 279 | // Drawing code 280 | } 281 | */ 282 | 283 | @end 284 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCell.h 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface XYCoverCell : UICollectionViewCell 14 | 15 | @property (nonatomic, strong) UILabel *label; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverCell.m 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import "XYCoverCell.h" 10 | 11 | @implementation XYCoverCell 12 | 13 | - (instancetype)initWithFrame:(CGRect)frame { 14 | self = [super initWithFrame:frame]; 15 | if (self) { 16 | 17 | self.layer.shadowColor = [[UIColor blueColor] CGColor]; 18 | self.layer.shadowOffset = CGSizeMake(0, 0); 19 | self.layer.shadowRadius = 10.0; 20 | self.layer.shadowOpacity = 0.3; 21 | self.layer.cornerRadius = 10.0; 22 | 23 | CGFloat width = 100; 24 | UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake((self.frame.size.width - width)/2, 50, width, width)]; 25 | label.textColor = [UIColor whiteColor]; 26 | label.textAlignment = NSTextAlignmentCenter; 27 | [self addSubview:label]; 28 | self.label = label; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)awakeFromNib { 34 | [super awakeFromNib]; 35 | } 36 | 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverModel.h 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface XYCoverModel : NSObject 14 | 15 | @property (nonatomic, copy) NSString *name; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/XYCoverCardView/XYCoverModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // XYCoverModel.m 3 | // LNHealthWatch 4 | // 5 | // Created by xiaoyan on 2021/11/17. 6 | // Copyright © 2021 ecsage. All rights reserved. 7 | // 8 | 9 | #import "XYCoverModel.h" 10 | 11 | @implementation XYCoverModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /XYCoverCardViewDemo/XYCoverCardViewDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // XYCoverCardViewDemo 4 | // 5 | // Created by ecsage on 2022/6/30. 6 | // 7 | 8 | #import 9 | #import "AppDelegate.h" 10 | 11 | int main(int argc, char * argv[]) { 12 | NSString * appDelegateClassName; 13 | @autoreleasepool { 14 | // Setup code that might create autoreleased objects goes here. 15 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 16 | } 17 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 18 | } 19 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iOSyan/XYCoverCardView/c99b10b09d75f7bf889c562dca4f1b7fd2f0b2a4/preview.gif -------------------------------------------------------------------------------- /preview1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iOSyan/XYCoverCardView/c99b10b09d75f7bf889c562dca4f1b7fd2f0b2a4/preview1.gif --------------------------------------------------------------------------------