├── 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
--------------------------------------------------------------------------------