├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── JJCarouselView-Demo
├── JJCarouselView-Demo.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ │ └── zhengguijie.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcuserdata
│ │ └── zhengguijie.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── JJCarouselView-Demo.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── JJCarouselView-Demo
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Images
│ │ ├── a-0.jpeg
│ │ ├── a-1.jpeg
│ │ ├── a-2.jpeg
│ │ ├── a-3.jpeg
│ │ ├── a-4.jpeg
│ │ └── a-5.jpeg
│ ├── Info.plist
│ ├── TabBarViewController.swift
│ ├── ViewController.swift
│ ├── WebCarouselModel.swift
│ └── WebCarouselView.swift
└── Podfile
├── JJCarouselView.podspec
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── CellContainer.swift
├── Config.swift
├── Direction.swift
├── Event.swift
├── JJCarousel+namespace.swift
├── JJCarouselContainerView.swift
├── JJCarouselView.swift
├── JJCarouselViewPageable.swift
├── Providers
│ ├── FullCarouselContainerView.swift
│ ├── JJCarouselDotPageView.swift
│ ├── JJCarouselHiddenPageView.swift
│ └── JJCarouselNumberPageView.swift
├── Style.swift
└── UIColor+Extension.swift
└── Tests
└── JJCarouselViewTests
└── JJCarouselViewTests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 | .DS_Store
20 |
21 | ## Other
22 | *.moved-aside
23 | *.xccheckout
24 | *.xcscmblueprint
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 | *.dSYM.zip
30 | *.dSYM
31 |
32 | ## Playgrounds
33 | timeline.xctimeline
34 | playground.xcworkspace
35 |
36 | # Swift Package Manager
37 | #
38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
39 | # Packages/
40 | # Package.pins
41 | # Package.resolved
42 | .build/
43 |
44 | # CocoaPods
45 | Pods/
46 | Pods
47 | Podfile.lock
48 | /Podfile.lock
49 |
50 | #
51 | # Add this line if you want to avoid checking in source code from the Xcode workspace
52 | # *.xcworkspace
53 |
54 | # Carthage
55 | #
56 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
57 | # Carthage/Checkouts
58 |
59 | Carthage/Build
60 |
61 | # fastlane
62 | #
63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
64 | # screenshots whenever they are needed.
65 | # For more information about the recommended setup visit:
66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
67 |
68 | fastlane/report.xml
69 | fastlane/Preview.html
70 | fastlane/screenshots/**/*.png
71 | fastlane/test_output
72 |
73 | # Code Injection
74 | #
75 | # After new code Injection tools there's a generated folder /iOSInjectionProject
76 | # https://github.com/johnno1962/injectionforxcode
77 |
78 | /.swiftpm
79 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 5E03445D2800037900CB9C82 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E03445C2800037900CB9C82 /* AppDelegate.swift */; };
11 | 5E0344612800037900CB9C82 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0344602800037900CB9C82 /* ViewController.swift */; };
12 | 5E0344662800037A00CB9C82 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E0344652800037A00CB9C82 /* Assets.xcassets */; };
13 | 5E0344692800037A00CB9C82 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5E0344672800037A00CB9C82 /* LaunchScreen.storyboard */; };
14 | 5E0344722800042E00CB9C82 /* JJCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0344712800042E00CB9C82 /* JJCarouselView.swift */; };
15 | 5E0344762800047B00CB9C82 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0344752800047B00CB9C82 /* Config.swift */; };
16 | 5E0344782800049D00CB9C82 /* CellContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E0344772800049D00CB9C82 /* CellContainer.swift */; };
17 | 5E03447A280004BC00CB9C82 /* JJCarouselViewPageable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E034479280004BB00CB9C82 /* JJCarouselViewPageable.swift */; };
18 | 5E03448428000EEA00CB9C82 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 5E03448128000EEA00CB9C82 /* LICENSE */; };
19 | 5E03448628000EEA00CB9C82 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 5E03448328000EEA00CB9C82 /* README.md */; };
20 | 5E03448E2800109700CB9C82 /* a-5.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5E0344882800109700CB9C82 /* a-5.jpeg */; };
21 | 5E03448F2800109700CB9C82 /* a-4.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5E0344892800109700CB9C82 /* a-4.jpeg */; };
22 | 5E0344902800109700CB9C82 /* a-3.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5E03448A2800109700CB9C82 /* a-3.jpeg */; };
23 | 5E0344912800109700CB9C82 /* a-2.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5E03448B2800109700CB9C82 /* a-2.jpeg */; };
24 | 5E0344922800109700CB9C82 /* a-1.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5E03448C2800109700CB9C82 /* a-1.jpeg */; };
25 | 5E0344932800109700CB9C82 /* a-0.jpeg in Resources */ = {isa = PBXBuildFile; fileRef = 5E03448D2800109700CB9C82 /* a-0.jpeg */; };
26 | 5E03449528001C3800CB9C82 /* WebCarouselModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E03449428001C3800CB9C82 /* WebCarouselModel.swift */; };
27 | 5E03449728001C6800CB9C82 /* WebCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E03449628001C6800CB9C82 /* WebCarouselView.swift */; };
28 | 5E2F818528041939008A511C /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E2F818428041939008A511C /* Event.swift */; };
29 | 5E34ACC628114BE3003EB1E4 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E34ACC528114BE3003EB1E4 /* UIColor+Extension.swift */; };
30 | 5E3B8972287D190700048C25 /* JJCarousel+namespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B8971287D190700048C25 /* JJCarousel+namespace.swift */; };
31 | 5E3B8974287D198700048C25 /* Direction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B8973287D198700048C25 /* Direction.swift */; };
32 | 5E3B897A287D50BF00048C25 /* JJCarouselHiddenPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B8976287D50BF00048C25 /* JJCarouselHiddenPageView.swift */; };
33 | 5E3B897B287D50BF00048C25 /* JJCarouselDotPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B8977287D50BF00048C25 /* JJCarouselDotPageView.swift */; };
34 | 5E3B897C287D50BF00048C25 /* FullCarouselContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B8978287D50BF00048C25 /* FullCarouselContainerView.swift */; };
35 | 5E3B897D287D50BF00048C25 /* JJCarouselNumberPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E3B8979287D50BF00048C25 /* JJCarouselNumberPageView.swift */; };
36 | 5E874BE12806A6710046F4DC /* JJCarouselContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E874BE02806A6710046F4DC /* JJCarouselContainerView.swift */; };
37 | 5E874BE32806AEEB0046F4DC /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E874BE22806AEEB0046F4DC /* Style.swift */; };
38 | 5EF487CC280E40180093B98E /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EF487CB280E40180093B98E /* TabBarViewController.swift */; };
39 | C233C6BCA5368CE57955AA1C /* Pods_JJCarouselView_Demo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1ACA3563D2902F00B45369A /* Pods_JJCarouselView_Demo.framework */; };
40 | /* End PBXBuildFile section */
41 |
42 | /* Begin PBXFileReference section */
43 | 359CE58ED333B2505B5CDAA4 /* Pods-JJCarouselView-Demo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JJCarouselView-Demo.release.xcconfig"; path = "Target Support Files/Pods-JJCarouselView-Demo/Pods-JJCarouselView-Demo.release.xcconfig"; sourceTree = ""; };
44 | 5E0344592800037900CB9C82 /* JJCarouselView-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JJCarouselView-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 5E03445C2800037900CB9C82 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
46 | 5E0344602800037900CB9C82 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
47 | 5E0344652800037A00CB9C82 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
48 | 5E0344682800037A00CB9C82 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
49 | 5E03446A2800037A00CB9C82 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
50 | 5E0344712800042E00CB9C82 /* JJCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JJCarouselView.swift; sourceTree = ""; };
51 | 5E0344752800047B00CB9C82 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; };
52 | 5E0344772800049D00CB9C82 /* CellContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellContainer.swift; sourceTree = ""; };
53 | 5E034479280004BB00CB9C82 /* JJCarouselViewPageable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JJCarouselViewPageable.swift; sourceTree = ""; };
54 | 5E03448128000EEA00CB9C82 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
55 | 5E03448328000EEA00CB9C82 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
56 | 5E0344882800109700CB9C82 /* a-5.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "a-5.jpeg"; sourceTree = ""; };
57 | 5E0344892800109700CB9C82 /* a-4.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "a-4.jpeg"; sourceTree = ""; };
58 | 5E03448A2800109700CB9C82 /* a-3.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "a-3.jpeg"; sourceTree = ""; };
59 | 5E03448B2800109700CB9C82 /* a-2.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "a-2.jpeg"; sourceTree = ""; };
60 | 5E03448C2800109700CB9C82 /* a-1.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "a-1.jpeg"; sourceTree = ""; };
61 | 5E03448D2800109700CB9C82 /* a-0.jpeg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "a-0.jpeg"; sourceTree = ""; };
62 | 5E03449428001C3800CB9C82 /* WebCarouselModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCarouselModel.swift; sourceTree = ""; };
63 | 5E03449628001C6800CB9C82 /* WebCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCarouselView.swift; sourceTree = ""; };
64 | 5E2F818428041939008A511C /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; };
65 | 5E34ACC528114BE3003EB1E4 /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; };
66 | 5E3B8971287D190700048C25 /* JJCarousel+namespace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JJCarousel+namespace.swift"; sourceTree = ""; };
67 | 5E3B8973287D198700048C25 /* Direction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Direction.swift; sourceTree = ""; };
68 | 5E3B8976287D50BF00048C25 /* JJCarouselHiddenPageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JJCarouselHiddenPageView.swift; sourceTree = ""; };
69 | 5E3B8977287D50BF00048C25 /* JJCarouselDotPageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JJCarouselDotPageView.swift; sourceTree = ""; };
70 | 5E3B8978287D50BF00048C25 /* FullCarouselContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FullCarouselContainerView.swift; sourceTree = ""; };
71 | 5E3B8979287D50BF00048C25 /* JJCarouselNumberPageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JJCarouselNumberPageView.swift; sourceTree = ""; };
72 | 5E874BE02806A6710046F4DC /* JJCarouselContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JJCarouselContainerView.swift; sourceTree = ""; };
73 | 5E874BE22806AEEB0046F4DC /* Style.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Style.swift; sourceTree = ""; };
74 | 5EF487CB280E40180093B98E /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = ""; };
75 | C1ACA3563D2902F00B45369A /* Pods_JJCarouselView_Demo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JJCarouselView_Demo.framework; sourceTree = BUILT_PRODUCTS_DIR; };
76 | E700E886F9A495C3B7BB4D33 /* Pods-JJCarouselView-Demo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JJCarouselView-Demo.debug.xcconfig"; path = "Target Support Files/Pods-JJCarouselView-Demo/Pods-JJCarouselView-Demo.debug.xcconfig"; sourceTree = ""; };
77 | /* End PBXFileReference section */
78 |
79 | /* Begin PBXFrameworksBuildPhase section */
80 | 5E0344562800037900CB9C82 /* Frameworks */ = {
81 | isa = PBXFrameworksBuildPhase;
82 | buildActionMask = 2147483647;
83 | files = (
84 | C233C6BCA5368CE57955AA1C /* Pods_JJCarouselView_Demo.framework in Frameworks */,
85 | );
86 | runOnlyForDeploymentPostprocessing = 0;
87 | };
88 | /* End PBXFrameworksBuildPhase section */
89 |
90 | /* Begin PBXGroup section */
91 | 5E0344502800037900CB9C82 = {
92 | isa = PBXGroup;
93 | children = (
94 | 5E03448128000EEA00CB9C82 /* LICENSE */,
95 | 5E03448328000EEA00CB9C82 /* README.md */,
96 | 5E0344702800040800CB9C82 /* Sources */,
97 | 5E03445B2800037900CB9C82 /* JJCarouselView-Demo */,
98 | 5E03445A2800037900CB9C82 /* Products */,
99 | C281EAAFB48684A8CF0DBB15 /* Pods */,
100 | E6432283997209AA9D0C2CBB /* Frameworks */,
101 | );
102 | sourceTree = "";
103 | };
104 | 5E03445A2800037900CB9C82 /* Products */ = {
105 | isa = PBXGroup;
106 | children = (
107 | 5E0344592800037900CB9C82 /* JJCarouselView-Demo.app */,
108 | );
109 | name = Products;
110 | sourceTree = "";
111 | };
112 | 5E03445B2800037900CB9C82 /* JJCarouselView-Demo */ = {
113 | isa = PBXGroup;
114 | children = (
115 | 5E0344872800109700CB9C82 /* Images */,
116 | 5E03445C2800037900CB9C82 /* AppDelegate.swift */,
117 | 5EF487CB280E40180093B98E /* TabBarViewController.swift */,
118 | 5E0344602800037900CB9C82 /* ViewController.swift */,
119 | 5E03449628001C6800CB9C82 /* WebCarouselView.swift */,
120 | 5E03449428001C3800CB9C82 /* WebCarouselModel.swift */,
121 | 5E0344652800037A00CB9C82 /* Assets.xcassets */,
122 | 5E0344672800037A00CB9C82 /* LaunchScreen.storyboard */,
123 | 5E03446A2800037A00CB9C82 /* Info.plist */,
124 | );
125 | path = "JJCarouselView-Demo";
126 | sourceTree = "";
127 | };
128 | 5E0344702800040800CB9C82 /* Sources */ = {
129 | isa = PBXGroup;
130 | children = (
131 | 5E3B8971287D190700048C25 /* JJCarousel+namespace.swift */,
132 | 5E0344712800042E00CB9C82 /* JJCarouselView.swift */,
133 | 5E874BE22806AEEB0046F4DC /* Style.swift */,
134 | 5E3B8973287D198700048C25 /* Direction.swift */,
135 | 5E0344752800047B00CB9C82 /* Config.swift */,
136 | 5E2F818428041939008A511C /* Event.swift */,
137 | 5E874BE02806A6710046F4DC /* JJCarouselContainerView.swift */,
138 | 5E034479280004BB00CB9C82 /* JJCarouselViewPageable.swift */,
139 | 5E0344772800049D00CB9C82 /* CellContainer.swift */,
140 | 5E34ACC528114BE3003EB1E4 /* UIColor+Extension.swift */,
141 | 5E3B8975287D50BF00048C25 /* Providers */,
142 | );
143 | name = Sources;
144 | path = ../Sources;
145 | sourceTree = "";
146 | };
147 | 5E0344872800109700CB9C82 /* Images */ = {
148 | isa = PBXGroup;
149 | children = (
150 | 5E0344882800109700CB9C82 /* a-5.jpeg */,
151 | 5E0344892800109700CB9C82 /* a-4.jpeg */,
152 | 5E03448A2800109700CB9C82 /* a-3.jpeg */,
153 | 5E03448B2800109700CB9C82 /* a-2.jpeg */,
154 | 5E03448C2800109700CB9C82 /* a-1.jpeg */,
155 | 5E03448D2800109700CB9C82 /* a-0.jpeg */,
156 | );
157 | path = Images;
158 | sourceTree = "";
159 | };
160 | 5E3B8975287D50BF00048C25 /* Providers */ = {
161 | isa = PBXGroup;
162 | children = (
163 | 5E3B8978287D50BF00048C25 /* FullCarouselContainerView.swift */,
164 | 5E3B8976287D50BF00048C25 /* JJCarouselHiddenPageView.swift */,
165 | 5E3B8977287D50BF00048C25 /* JJCarouselDotPageView.swift */,
166 | 5E3B8979287D50BF00048C25 /* JJCarouselNumberPageView.swift */,
167 | );
168 | path = Providers;
169 | sourceTree = "";
170 | };
171 | C281EAAFB48684A8CF0DBB15 /* Pods */ = {
172 | isa = PBXGroup;
173 | children = (
174 | E700E886F9A495C3B7BB4D33 /* Pods-JJCarouselView-Demo.debug.xcconfig */,
175 | 359CE58ED333B2505B5CDAA4 /* Pods-JJCarouselView-Demo.release.xcconfig */,
176 | );
177 | path = Pods;
178 | sourceTree = "";
179 | };
180 | E6432283997209AA9D0C2CBB /* Frameworks */ = {
181 | isa = PBXGroup;
182 | children = (
183 | C1ACA3563D2902F00B45369A /* Pods_JJCarouselView_Demo.framework */,
184 | );
185 | name = Frameworks;
186 | sourceTree = "";
187 | };
188 | /* End PBXGroup section */
189 |
190 | /* Begin PBXNativeTarget section */
191 | 5E0344582800037900CB9C82 /* JJCarouselView-Demo */ = {
192 | isa = PBXNativeTarget;
193 | buildConfigurationList = 5E03446D2800037A00CB9C82 /* Build configuration list for PBXNativeTarget "JJCarouselView-Demo" */;
194 | buildPhases = (
195 | 722B92B046888C91BAA37381 /* [CP] Check Pods Manifest.lock */,
196 | 5E0344552800037900CB9C82 /* Sources */,
197 | 5E0344562800037900CB9C82 /* Frameworks */,
198 | 5E0344572800037900CB9C82 /* Resources */,
199 | 0921E51A191B0F557C99B6E7 /* [CP] Embed Pods Frameworks */,
200 | );
201 | buildRules = (
202 | );
203 | dependencies = (
204 | );
205 | name = "JJCarouselView-Demo";
206 | productName = "JJCarouselView-Demo";
207 | productReference = 5E0344592800037900CB9C82 /* JJCarouselView-Demo.app */;
208 | productType = "com.apple.product-type.application";
209 | };
210 | /* End PBXNativeTarget section */
211 |
212 | /* Begin PBXProject section */
213 | 5E0344512800037900CB9C82 /* Project object */ = {
214 | isa = PBXProject;
215 | attributes = {
216 | BuildIndependentTargetsInParallel = 1;
217 | LastSwiftUpdateCheck = 1330;
218 | LastUpgradeCheck = 1330;
219 | TargetAttributes = {
220 | 5E0344582800037900CB9C82 = {
221 | CreatedOnToolsVersion = 13.3;
222 | };
223 | };
224 | };
225 | buildConfigurationList = 5E0344542800037900CB9C82 /* Build configuration list for PBXProject "JJCarouselView-Demo" */;
226 | compatibilityVersion = "Xcode 13.0";
227 | developmentRegion = en;
228 | hasScannedForEncodings = 0;
229 | knownRegions = (
230 | en,
231 | Base,
232 | );
233 | mainGroup = 5E0344502800037900CB9C82;
234 | productRefGroup = 5E03445A2800037900CB9C82 /* Products */;
235 | projectDirPath = "";
236 | projectRoot = "";
237 | targets = (
238 | 5E0344582800037900CB9C82 /* JJCarouselView-Demo */,
239 | );
240 | };
241 | /* End PBXProject section */
242 |
243 | /* Begin PBXResourcesBuildPhase section */
244 | 5E0344572800037900CB9C82 /* Resources */ = {
245 | isa = PBXResourcesBuildPhase;
246 | buildActionMask = 2147483647;
247 | files = (
248 | 5E0344932800109700CB9C82 /* a-0.jpeg in Resources */,
249 | 5E03448E2800109700CB9C82 /* a-5.jpeg in Resources */,
250 | 5E03448428000EEA00CB9C82 /* LICENSE in Resources */,
251 | 5E0344902800109700CB9C82 /* a-3.jpeg in Resources */,
252 | 5E03448F2800109700CB9C82 /* a-4.jpeg in Resources */,
253 | 5E0344912800109700CB9C82 /* a-2.jpeg in Resources */,
254 | 5E0344692800037A00CB9C82 /* LaunchScreen.storyboard in Resources */,
255 | 5E03448628000EEA00CB9C82 /* README.md in Resources */,
256 | 5E0344662800037A00CB9C82 /* Assets.xcassets in Resources */,
257 | 5E0344922800109700CB9C82 /* a-1.jpeg in Resources */,
258 | );
259 | runOnlyForDeploymentPostprocessing = 0;
260 | };
261 | /* End PBXResourcesBuildPhase section */
262 |
263 | /* Begin PBXShellScriptBuildPhase section */
264 | 0921E51A191B0F557C99B6E7 /* [CP] Embed Pods Frameworks */ = {
265 | isa = PBXShellScriptBuildPhase;
266 | buildActionMask = 2147483647;
267 | files = (
268 | );
269 | inputFileListPaths = (
270 | "${PODS_ROOT}/Target Support Files/Pods-JJCarouselView-Demo/Pods-JJCarouselView-Demo-frameworks-${CONFIGURATION}-input-files.xcfilelist",
271 | );
272 | name = "[CP] Embed Pods Frameworks";
273 | outputFileListPaths = (
274 | "${PODS_ROOT}/Target Support Files/Pods-JJCarouselView-Demo/Pods-JJCarouselView-Demo-frameworks-${CONFIGURATION}-output-files.xcfilelist",
275 | );
276 | runOnlyForDeploymentPostprocessing = 0;
277 | shellPath = /bin/sh;
278 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JJCarouselView-Demo/Pods-JJCarouselView-Demo-frameworks.sh\"\n";
279 | showEnvVarsInLog = 0;
280 | };
281 | 722B92B046888C91BAA37381 /* [CP] Check Pods Manifest.lock */ = {
282 | isa = PBXShellScriptBuildPhase;
283 | buildActionMask = 2147483647;
284 | files = (
285 | );
286 | inputFileListPaths = (
287 | );
288 | inputPaths = (
289 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
290 | "${PODS_ROOT}/Manifest.lock",
291 | );
292 | name = "[CP] Check Pods Manifest.lock";
293 | outputFileListPaths = (
294 | );
295 | outputPaths = (
296 | "$(DERIVED_FILE_DIR)/Pods-JJCarouselView-Demo-checkManifestLockResult.txt",
297 | );
298 | runOnlyForDeploymentPostprocessing = 0;
299 | shellPath = /bin/sh;
300 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
301 | showEnvVarsInLog = 0;
302 | };
303 | /* End PBXShellScriptBuildPhase section */
304 |
305 | /* Begin PBXSourcesBuildPhase section */
306 | 5E0344552800037900CB9C82 /* Sources */ = {
307 | isa = PBXSourcesBuildPhase;
308 | buildActionMask = 2147483647;
309 | files = (
310 | 5E0344722800042E00CB9C82 /* JJCarouselView.swift in Sources */,
311 | 5E34ACC628114BE3003EB1E4 /* UIColor+Extension.swift in Sources */,
312 | 5E0344612800037900CB9C82 /* ViewController.swift in Sources */,
313 | 5E3B897B287D50BF00048C25 /* JJCarouselDotPageView.swift in Sources */,
314 | 5E3B8974287D198700048C25 /* Direction.swift in Sources */,
315 | 5E03445D2800037900CB9C82 /* AppDelegate.swift in Sources */,
316 | 5EF487CC280E40180093B98E /* TabBarViewController.swift in Sources */,
317 | 5E2F818528041939008A511C /* Event.swift in Sources */,
318 | 5E874BE12806A6710046F4DC /* JJCarouselContainerView.swift in Sources */,
319 | 5E0344782800049D00CB9C82 /* CellContainer.swift in Sources */,
320 | 5E03447A280004BC00CB9C82 /* JJCarouselViewPageable.swift in Sources */,
321 | 5E3B897D287D50BF00048C25 /* JJCarouselNumberPageView.swift in Sources */,
322 | 5E03449528001C3800CB9C82 /* WebCarouselModel.swift in Sources */,
323 | 5E3B897A287D50BF00048C25 /* JJCarouselHiddenPageView.swift in Sources */,
324 | 5E3B897C287D50BF00048C25 /* FullCarouselContainerView.swift in Sources */,
325 | 5E874BE32806AEEB0046F4DC /* Style.swift in Sources */,
326 | 5E0344762800047B00CB9C82 /* Config.swift in Sources */,
327 | 5E03449728001C6800CB9C82 /* WebCarouselView.swift in Sources */,
328 | 5E3B8972287D190700048C25 /* JJCarousel+namespace.swift in Sources */,
329 | );
330 | runOnlyForDeploymentPostprocessing = 0;
331 | };
332 | /* End PBXSourcesBuildPhase section */
333 |
334 | /* Begin PBXVariantGroup section */
335 | 5E0344672800037A00CB9C82 /* LaunchScreen.storyboard */ = {
336 | isa = PBXVariantGroup;
337 | children = (
338 | 5E0344682800037A00CB9C82 /* Base */,
339 | );
340 | name = LaunchScreen.storyboard;
341 | sourceTree = "";
342 | };
343 | /* End PBXVariantGroup section */
344 |
345 | /* Begin XCBuildConfiguration section */
346 | 5E03446B2800037A00CB9C82 /* Debug */ = {
347 | isa = XCBuildConfiguration;
348 | buildSettings = {
349 | ALWAYS_SEARCH_USER_PATHS = NO;
350 | CLANG_ANALYZER_NONNULL = YES;
351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
353 | CLANG_ENABLE_MODULES = YES;
354 | CLANG_ENABLE_OBJC_ARC = YES;
355 | CLANG_ENABLE_OBJC_WEAK = YES;
356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
357 | CLANG_WARN_BOOL_CONVERSION = YES;
358 | CLANG_WARN_COMMA = YES;
359 | CLANG_WARN_CONSTANT_CONVERSION = YES;
360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INFINITE_RECURSION = YES;
366 | CLANG_WARN_INT_CONVERSION = YES;
367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
371 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
373 | CLANG_WARN_STRICT_PROTOTYPES = YES;
374 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
375 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
376 | CLANG_WARN_UNREACHABLE_CODE = YES;
377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
378 | COPY_PHASE_STRIP = NO;
379 | DEBUG_INFORMATION_FORMAT = dwarf;
380 | ENABLE_STRICT_OBJC_MSGSEND = YES;
381 | ENABLE_TESTABILITY = YES;
382 | GCC_C_LANGUAGE_STANDARD = gnu11;
383 | GCC_DYNAMIC_NO_PIC = NO;
384 | GCC_NO_COMMON_BLOCKS = YES;
385 | GCC_OPTIMIZATION_LEVEL = 0;
386 | GCC_PREPROCESSOR_DEFINITIONS = (
387 | "DEBUG=1",
388 | "$(inherited)",
389 | );
390 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
391 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
392 | GCC_WARN_UNDECLARED_SELECTOR = YES;
393 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
394 | GCC_WARN_UNUSED_FUNCTION = YES;
395 | GCC_WARN_UNUSED_VARIABLE = YES;
396 | IPHONEOS_DEPLOYMENT_TARGET = 15.4;
397 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
398 | MTL_FAST_MATH = YES;
399 | ONLY_ACTIVE_ARCH = YES;
400 | SDKROOT = iphoneos;
401 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
402 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
403 | };
404 | name = Debug;
405 | };
406 | 5E03446C2800037A00CB9C82 /* Release */ = {
407 | isa = XCBuildConfiguration;
408 | buildSettings = {
409 | ALWAYS_SEARCH_USER_PATHS = NO;
410 | CLANG_ANALYZER_NONNULL = YES;
411 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
412 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
413 | CLANG_ENABLE_MODULES = YES;
414 | CLANG_ENABLE_OBJC_ARC = YES;
415 | CLANG_ENABLE_OBJC_WEAK = YES;
416 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
417 | CLANG_WARN_BOOL_CONVERSION = YES;
418 | CLANG_WARN_COMMA = YES;
419 | CLANG_WARN_CONSTANT_CONVERSION = YES;
420 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
421 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
422 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
423 | CLANG_WARN_EMPTY_BODY = YES;
424 | CLANG_WARN_ENUM_CONVERSION = YES;
425 | CLANG_WARN_INFINITE_RECURSION = YES;
426 | CLANG_WARN_INT_CONVERSION = YES;
427 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
428 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
429 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
430 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
431 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
432 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
433 | CLANG_WARN_STRICT_PROTOTYPES = YES;
434 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
435 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
436 | CLANG_WARN_UNREACHABLE_CODE = YES;
437 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
438 | COPY_PHASE_STRIP = NO;
439 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
440 | ENABLE_NS_ASSERTIONS = NO;
441 | ENABLE_STRICT_OBJC_MSGSEND = YES;
442 | GCC_C_LANGUAGE_STANDARD = gnu11;
443 | GCC_NO_COMMON_BLOCKS = YES;
444 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
445 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
446 | GCC_WARN_UNDECLARED_SELECTOR = YES;
447 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
448 | GCC_WARN_UNUSED_FUNCTION = YES;
449 | GCC_WARN_UNUSED_VARIABLE = YES;
450 | IPHONEOS_DEPLOYMENT_TARGET = 15.4;
451 | MTL_ENABLE_DEBUG_INFO = NO;
452 | MTL_FAST_MATH = YES;
453 | SDKROOT = iphoneos;
454 | SWIFT_COMPILATION_MODE = wholemodule;
455 | SWIFT_OPTIMIZATION_LEVEL = "-O";
456 | VALIDATE_PRODUCT = YES;
457 | };
458 | name = Release;
459 | };
460 | 5E03446E2800037A00CB9C82 /* Debug */ = {
461 | isa = XCBuildConfiguration;
462 | baseConfigurationReference = E700E886F9A495C3B7BB4D33 /* Pods-JJCarouselView-Demo.debug.xcconfig */;
463 | buildSettings = {
464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
465 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
466 | CODE_SIGN_STYLE = Automatic;
467 | CURRENT_PROJECT_VERSION = 1;
468 | DEVELOPMENT_TEAM = 4288334GUU;
469 | GENERATE_INFOPLIST_FILE = YES;
470 | INFOPLIST_FILE = "JJCarouselView-Demo/Info.plist";
471 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
472 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
473 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
474 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
475 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
476 | LD_RUNPATH_SEARCH_PATHS = (
477 | "$(inherited)",
478 | "@executable_path/Frameworks",
479 | );
480 | MARKETING_VERSION = 1.0;
481 | PRODUCT_BUNDLE_IDENTIFIER = "com.JJCarouselView-Demo.zgj.JJCarouselView-Demo";
482 | PRODUCT_NAME = "$(TARGET_NAME)";
483 | SWIFT_EMIT_LOC_STRINGS = YES;
484 | SWIFT_VERSION = 5.0;
485 | TARGETED_DEVICE_FAMILY = "1,2";
486 | };
487 | name = Debug;
488 | };
489 | 5E03446F2800037A00CB9C82 /* Release */ = {
490 | isa = XCBuildConfiguration;
491 | baseConfigurationReference = 359CE58ED333B2505B5CDAA4 /* Pods-JJCarouselView-Demo.release.xcconfig */;
492 | buildSettings = {
493 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
494 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
495 | CODE_SIGN_STYLE = Automatic;
496 | CURRENT_PROJECT_VERSION = 1;
497 | DEVELOPMENT_TEAM = 4288334GUU;
498 | GENERATE_INFOPLIST_FILE = YES;
499 | INFOPLIST_FILE = "JJCarouselView-Demo/Info.plist";
500 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
501 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
502 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
503 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
504 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
505 | LD_RUNPATH_SEARCH_PATHS = (
506 | "$(inherited)",
507 | "@executable_path/Frameworks",
508 | );
509 | MARKETING_VERSION = 1.0;
510 | PRODUCT_BUNDLE_IDENTIFIER = "com.JJCarouselView-Demo.zgj.JJCarouselView-Demo";
511 | PRODUCT_NAME = "$(TARGET_NAME)";
512 | SWIFT_EMIT_LOC_STRINGS = YES;
513 | SWIFT_VERSION = 5.0;
514 | TARGETED_DEVICE_FAMILY = "1,2";
515 | };
516 | name = Release;
517 | };
518 | /* End XCBuildConfiguration section */
519 |
520 | /* Begin XCConfigurationList section */
521 | 5E0344542800037900CB9C82 /* Build configuration list for PBXProject "JJCarouselView-Demo" */ = {
522 | isa = XCConfigurationList;
523 | buildConfigurations = (
524 | 5E03446B2800037A00CB9C82 /* Debug */,
525 | 5E03446C2800037A00CB9C82 /* Release */,
526 | );
527 | defaultConfigurationIsVisible = 0;
528 | defaultConfigurationName = Release;
529 | };
530 | 5E03446D2800037A00CB9C82 /* Build configuration list for PBXNativeTarget "JJCarouselView-Demo" */ = {
531 | isa = XCConfigurationList;
532 | buildConfigurations = (
533 | 5E03446E2800037A00CB9C82 /* Debug */,
534 | 5E03446F2800037A00CB9C82 /* Release */,
535 | );
536 | defaultConfigurationIsVisible = 0;
537 | defaultConfigurationName = Release;
538 | };
539 | /* End XCConfigurationList section */
540 | };
541 | rootObject = 5E0344512800037900CB9C82 /* Project object */;
542 | }
543 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo.xcodeproj/project.xcworkspace/xcuserdata/zhengguijie.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zgjff/JJCarouselView/43e41437f17c3a315e6a4d84dd856e2ffef2c28d/JJCarouselView-Demo/JJCarouselView-Demo.xcodeproj/project.xcworkspace/xcuserdata/zhengguijie.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo.xcodeproj/xcuserdata/zhengguijie.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | JJCarouselView-Demo.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 2
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // JJCarouselView-Demo
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 | var window: UIWindow?
13 |
14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
15 | window = UIWindow(frame: UIScreen.main.bounds)
16 | if #available(iOS 13.0, *) {
17 | window?.backgroundColor = .systemBackground
18 | } else {
19 | window?.backgroundColor = .black
20 | }
21 | window?.rootViewController = TabBarViewController()
22 | window?.makeKeyAndVisible()
23 | return true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/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 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/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 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-0.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zgjff/JJCarouselView/43e41437f17c3a315e6a4d84dd856e2ffef2c28d/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-0.jpeg
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zgjff/JJCarouselView/43e41437f17c3a315e6a4d84dd856e2ffef2c28d/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-1.jpeg
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zgjff/JJCarouselView/43e41437f17c3a315e6a4d84dd856e2ffef2c28d/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-2.jpeg
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zgjff/JJCarouselView/43e41437f17c3a315e6a4d84dd856e2ffef2c28d/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-3.jpeg
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-4.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zgjff/JJCarouselView/43e41437f17c3a315e6a4d84dd856e2ffef2c28d/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-4.jpeg
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-5.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zgjff/JJCarouselView/43e41437f17c3a315e6a4d84dd856e2ffef2c28d/JJCarouselView-Demo/JJCarouselView-Demo/Images/a-5.jpeg
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSAppTransportSecurity
6 |
7 | NSAllowsArbitraryLoads
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/TabBarViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabBarViewController.swift
3 | // JJCarouselView-Demo
4 | //
5 | // Created by zgjff on 2022/4/19.
6 | //
7 |
8 | import UIKit
9 |
10 | class TabBarViewController: UITabBarController {
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | tabBar.tintColor = .red
15 | let vc1 = UINavigationController(rootViewController: ViewController())
16 | vc1.title = "轮播"
17 |
18 | let vc2 = UIViewController()
19 | vc2.title = "空白"
20 | viewControllers = [vc1, vc2]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // JJCarouselView-Demo
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 | import SDWebImage
10 | import SafariServices
11 | import Combine
12 |
13 | class ViewController: UIViewController {
14 |
15 | private lazy var scrollView = UIScrollView()
16 | private lazy var subviewsMaxY: CGFloat = 0
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | setup()
20 | addLocalImageCarouselView1()
21 | addLocalImageCarouselView2()
22 | addWebImageCarouselView()
23 | addCustomCarouselView()
24 | scrollView.contentSize = CGSize(width: view.bounds.width, height: subviewsMaxY + 20)
25 | }
26 | }
27 |
28 | private extension ViewController {
29 | func setup() {
30 | navigationItem.title = "可以切换tabbar来观察轮播图是否暂停"
31 | scrollView.frame = view.bounds
32 | view.addSubview(scrollView)
33 | }
34 |
35 | func addLocalImageCarouselView1() {
36 | let carouselView: JJCarouselView = JJCarouselView(frame: CGRect(x: 50, y: 0, width: view.bounds.width - 100, height: 200), initialize: nil)
37 | carouselView.config.display = { cell, object in
38 | cell.clipsToBounds = true
39 | cell.contentMode = .scaleAspectFill
40 | cell.image = object
41 | }
42 | carouselView.config.manualSlidingEnable = false
43 | carouselView.config.direction = .rtl
44 | carouselView.backgroundColor = .random()
45 | carouselView.event.onTap = { _, obj, idx in
46 | print(obj, idx)
47 | }
48 | carouselView.event.willMove = { idx in
49 | print("willMove----", idx)
50 | }
51 | carouselView.event.didMove = { idx in
52 | print("didMove----", idx)
53 | }
54 | carouselView.event.onScroll = { fromIndex, toIndex, progress in
55 | // print("onScroll----", fromIndex, toIndex, progress)
56 | }
57 | subviewsMaxY = carouselView.frame.maxY
58 | scrollView.addSubview(carouselView)
59 | carouselView.datas = (0..<6).map { UIImage(named: "a-\($0).jpeg")! }
60 | }
61 |
62 | func addLocalImageCarouselView2() {
63 | let carouselView = JJLocalImageCarouselView(frame: CGRect(x: 50, y: subviewsMaxY + 30, width: view.bounds.width - 100, height: 200)) {
64 | return UIImageView()
65 | }
66 | carouselView.config.display = { cell, object in
67 | cell.contentMode = .scaleAspectFit
68 | cell.image = object
69 | }
70 | carouselView.config.direction = .ttb
71 | carouselView.config.pageViewFrame = { pageView, _, carouselViewSize, totalDataCount in
72 | let pageSize = pageView.size(forNumberOfPages: totalDataCount)
73 | return CGRect(x: carouselViewSize.width - pageSize.width - 12, y: carouselViewSize.height - pageSize.height - 10, width: pageSize.width, height: pageSize.height)
74 | }
75 | carouselView.pageView = JJCarouselNumberPageView()
76 | subviewsMaxY = carouselView.frame.maxY
77 | scrollView.addSubview(carouselView)
78 | carouselView.datas = (0..<3).map { UIImage(named: "a-\($0).jpeg")! }
79 | }
80 |
81 | func addWebImageCarouselView() {
82 | let carouselView = JJWebImageCarouselView(frame: CGRect(x: 50, y: subviewsMaxY + 30, width: view.bounds.width - 100, height: 200))
83 | carouselView.config.display = { cell, object in
84 | cell.clipsToBounds = true
85 | cell.contentMode = .scaleAspectFill
86 | cell.sd_setImage(with: object)
87 | }
88 | carouselView.pageView = JJCarouselNumberPageView()
89 | carouselView.config.pageViewFrame = { _, _, carouselViewSize, _ in
90 | return CGRect(x: carouselViewSize.width - 55, y: carouselViewSize.height - 30, width: 45, height: 20)
91 | }
92 | subviewsMaxY = carouselView.frame.maxY
93 | scrollView.addSubview(carouselView)
94 | carouselView.datas = (1..<25).compactMap { URL(string: String(format: "http://r0k.us/graphics/kodak/kodak/kodim%02d.png", $0)) }
95 | }
96 |
97 | func addCustomCarouselView() {
98 | let carouselView: JJCarouselView = JJCarouselView(frame: CGRect(x: 50, y: subviewsMaxY + 30, width: view.bounds.width - 100, height: 150))
99 | carouselView.config.display = { cell, object in
100 | cell.titleLabel.text = object.title
101 | cell.descLabel.text = object.desc
102 | }
103 | carouselView.event.onTap = { [weak self] _, obj, _ in
104 | let sf = SFSafariViewController(url: obj.url)
105 | self?.present(sf, animated: true)
106 | }
107 | carouselView.pageView = JJCarouselNumberPageView()
108 | subviewsMaxY = carouselView.frame.maxY
109 | scrollView.addSubview(carouselView)
110 | carouselView.datas = [
111 | WebCarouselModel(title: "这是第1个自定义轮播控件", desc: "这是第1个自定义轮播控件", url: URL(string: "https://www.baidu.com")!),
112 | WebCarouselModel(title: "这是第2个自定义轮播控件", desc: "这是第2个自定义轮播控件", url: URL(string: "https://www.zhihu.com")!),
113 | WebCarouselModel(title: "这是第3个自定义轮播控件", desc: "这是第3个自定义轮播控件", url: URL(string: "https://cn.bing.com")!),
114 | ]
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/WebCarouselModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebCarouselModel.swift
3 | // JJCarouselView-Demo
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import Foundation
9 |
10 | struct WebCarouselModel {
11 | let title: String
12 | let desc: String
13 | let url: URL
14 | }
15 |
16 | extension WebCarouselModel: Equatable {
17 | static func == (lhs: Self, rhs: Self) -> Bool {
18 | return (lhs.title == rhs.title) && (lhs.desc == rhs.desc)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/JJCarouselView-Demo/WebCarouselView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebCarouselView.swift
3 | // JJCarouselView-Demo
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | final class WebCarouselView: UIView {
11 |
12 | override init(frame: CGRect) {
13 | super.init(frame: frame)
14 | backgroundColor = .purple
15 | titleLabel.font = UIFont.boldSystemFont(ofSize: 20)
16 | titleLabel.textColor = .orange
17 | titleLabel.text = " "
18 | titleLabel.sizeToFit()
19 | addSubview(titleLabel)
20 | descLabel.font = UIFont.systemFont(ofSize: 15)
21 | descLabel.textColor = .gray
22 | descLabel.text = " "
23 | descLabel.sizeToFit()
24 | addSubview(descLabel)
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 |
31 | override func layoutSubviews() {
32 | super.layoutSubviews()
33 | titleLabel.frame = CGRect(x: 16, y: 30, width: bounds.width - 32, height: titleLabel.frame.height)
34 | descLabel.frame = CGRect(x: titleLabel.frame.minX, y: titleLabel.frame.maxY + 30, width: titleLabel.frame.width, height: descLabel.frame.height)
35 | }
36 |
37 | private(set) lazy var titleLabel = UILabel()
38 | private(set) lazy var descLabel = UILabel()
39 | }
40 |
--------------------------------------------------------------------------------
/JJCarouselView-Demo/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '11.0'
2 |
3 | target 'JJCarouselView-Demo' do
4 | use_frameworks!
5 | pod 'SDWebImage'
6 | end
7 |
--------------------------------------------------------------------------------
/JJCarouselView.podspec:
--------------------------------------------------------------------------------
1 | @version = "0.1.1"
2 | Pod::Spec.new do |spec|
3 | spec.name = "JJCarouselView"
4 | spec.version = @version
5 | spec.summary = "Swift简单好用、易于扩展的轮播图框架."
6 | spec.description = "适用于Swift的简单好用、易于扩展的轮播图框架."
7 | spec.homepage = "https://github.com/zgjff/JJCarouselView"
8 | spec.license = { :type => 'MIT', :file => 'LICENSE' }
9 | spec.author = { "zgjff" => "zguijie1005@163.com" }
10 | spec.source = { :git => "https://github.com/zgjff/JJCarouselView.git", :tag => "#{spec.version}" }
11 | spec.source_files = 'Sources/*.swift', 'Sources/**/*.{swift}'
12 | spec.platform = :ios, "9.0"
13 | spec.swift_version = '5.0'
14 | end
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 zgjff
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.6
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "JJCarouselView",
8 | platforms: [.iOS(.v9)],
9 | products: [
10 | // Products define the executables and libraries a package produces, and make them visible to other packages.
11 | .library(
12 | name: "JJCarouselView",
13 | targets: ["JJCarouselView"]),
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | // .package(url: /* package url */, from: "1.0.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
22 | .target(name: "JJCarouselView", path: "Sources")
23 | ],
24 | swiftLanguageVersions: [
25 | .v5
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | JJCarouselView
2 | =================
3 |
4 | [](https://www.apache.org/licenses/LICENSE-2.0.html)
5 | [](https://www.swift.org)
6 | 
7 | [](https://github.com/zgjff/JJCarouselView)
8 | [](https://www.swift.org/package-manager/)
9 | [](https://cocoapods.org/pods/JJCarouselView)
10 |
11 | 适用于Swift的简单好用、易于扩展的轮播图框架
12 |
13 | 使用方法
14 | =================
15 |
16 | ## 一、初始化
17 | 因为本轮播图是泛型控件,所以在初始化的时候需要指定类型。
18 | ```swift
19 | let carouselView: JJCarouselView = JJCarouselView(frame: CGRect.zero)
20 |
21 | let carouselView: JJCarouselView = JJCarouselView(frame: CGRect.zero) {
22 | return UIImageView()
23 | }
24 | ```
25 |
26 | ## 二、使用方法
27 |
28 | ### 2.1 基本设置
29 | 轮播图方向,默认从左->右
30 | ```swift
31 | cv.config.direction = .ltr
32 | ```
33 |
34 | 是否自动轮播,默认`true`自动轮播
35 | ```swift
36 | cv.config.autoLoop = true
37 | ```
38 |
39 | 轮播间隔,默认5s
40 | ```swift
41 | cv.config.loopTimeInterval = 5
42 | ```
43 |
44 | 轮播图内边局
45 | ```swift
46 | cv.config.contentInset = .zero
47 | ```
48 |
49 |
50 | ### 2.2 展示数据
51 | > 本控件没指定将对应的数据源显示到容器的方法,所以需要自己去实现。只要在初始化`JJCarouselView`之后,设置`config.display`即可。
52 |
53 | #### 2.2.1 以最基本的展示本地图片的轮播图为例:
54 | ```swift
55 | let carouselView: JJLocalImageCarouselView
56 | cv.config.display = { cell, image in
57 | cell.clipsToBounds = true
58 | cell.contentMode = .scaleAspectFill
59 | cell.image = image
60 | }
61 | ```
62 |
63 | #### 2.2.2 展示网络图片
64 | ```swift
65 | let carouselView: JJWebImageCarouselView
66 | cv.config.display = { cell, url in
67 | ...
68 | // 使用SDWebImage
69 | cell.sd_setImage(with: url)
70 | // 使用Kingfisher
71 | cell.kf.setImage(with: url)
72 | }
73 | ```
74 |
75 | #### 2.2.3 展示任何你想轮播的内容
76 | 可以使用任何`UIView`的子类来展示任意对象,只需设定轮播图类型的`Object`遵守`Equatable`协议即可。
77 | ```swift
78 | // 轮播Model,必须遵守Equatable协议
79 | struct WebCarouselModel {
80 | let title: String
81 | let desc: String
82 | let url: URL
83 | }
84 |
85 | extension WebCarouselModel: Equatable {
86 | static func == (lhs: Self, rhs: Self) -> Bool {
87 | return (lhs.title == rhs.title) && (lhs.desc == rhs.desc)
88 | }
89 | }
90 | ```
91 | ```swift
92 | // 轮播控件
93 | final class WebCarouselView: UIView {}
94 | ```
95 | ```swift
96 | let cv: JJCarouselView = JJCarouselView(frame: CGRect(x: 50, y: 0, width: 200, height: 150), initialize: nil)
97 | cv.config.display = { cell, object in
98 | cell.titleLabel.text = object.title
99 | cell.descLabel.text = object.desc
100 | }
101 | cv.datas = [
102 | WebCarouselModel(title: "这是第1个自定义轮播控件", desc: "这是第1个自定义轮播控件", url: URL(string: "https://www.baidu.com")!),
103 | WebCarouselModel(title: "这是第2个自定义轮播控件", desc: "这是第2个自定义轮播控件", url: URL(string: "https://www.zhihu.com")!),
104 | WebCarouselModel(title: "这是第3个自定义轮播控件", desc: "这是第3个自定义轮播控件", url: URL(string: "https://cn.bing.com")!),
105 | ]
106 | ```
107 |
108 | ### 2.3 轮播指示器
109 |
110 | #### 2.3.1 指示器控件
111 | 轮播图的`pageView`是可替换的,只需要替换成遵守`JJCarouselViewPageable`协议的类类即可。
112 | ```swift
113 | cv.pageView = JJCarouselNumberPageView()
114 | ```
115 | 隐藏指示器,只需要将`pageView`设置成`JJCarouselHiddenPageView`。
116 | ```swift
117 | cv.pageView = JJCarouselHiddenPageView()
118 | ```
119 | 当然你也可以自定义专属于你的指示器
120 | ```swift
121 | class YourOwnPageView: UIView, JJCarouselViewPageable {
122 | ...
123 | }
124 | ```
125 | ```swift
126 | cv.pageView = YourOwnPageView()
127 | ```
128 |
129 | #### 2.3.2 指示器控件`frame`
130 | 默认底部居中显示指示器,当然你也可以设定任何位置。
131 | ```swift
132 | // 根据数量来设定frame
133 | cv.config.pageViewFrame = { pageView, _, carouselViewSize, totalDataCount in
134 | let pageSize = pageView.size(forNumberOfPages: totalDataCount)
135 | return CGRect(x: carouselViewSize.width - pageSize.width - 12, y: carouselViewSize.height - pageSize.height - 10, width: pageSize.width, height: pageSize.height)
136 | }
137 | ```
138 | ```swift
139 | // 固定大小
140 | cv.config.pageViewFrame = { _, _, carouselViewSize, _ in
141 | return CGRect(x: carouselViewSize.width - 55, y: carouselViewSize.height - 30, width: 45, height: 20)
142 | }
143 | ```
144 |
145 | ### 2.4 事件回调
146 |
147 | 点击事件
148 | ```swift
149 | // 使用block
150 | cv.event.onTap = { view, obj, index in
151 | ...
152 | }
153 | // 使用Combine框架
154 | cv.event.onTapPublisher.sink { view, obj, idx in
155 | ...
156 | }.store(in: &cancellable)
157 | ```
158 | 准备滑动到具体的index
159 | ```swift
160 | // 使用block
161 | cv.event.willMove = { idx in
162 | ...
163 | }
164 | // 使用Combine框架
165 | cv.event.willMovePublisher.sink(receiveValue: { idx in
166 | ...
167 | }).store(in: &cancellable)
168 | ```
169 | 已经滑动到具体的index
170 | ```swift
171 | // 使用block
172 | cv.event.didMove = { idx in
173 | ...
174 | }
175 | // 使用Combine框架
176 | cv.event.didMovePublisher.sink(receiveValue: { idx in
177 | ...
178 | }).store(in: &cancellable)
179 | ```
180 | 滑动回调(当前index, 目标index, 进度)
181 | ```swift
182 | // 使用block
183 | cv.event.onScroll = { fromIndex, toIndex, progress in
184 | ...
185 | }
186 | // 使用Combine框架
187 | cv.event.onScrollPublisher.sink(receiveValue: { fromIndex, toIndex, progress in
188 | ...
189 | }).store(in: &cancellable)
190 | ```
191 |
192 |
193 | 使用需求
194 | =================
195 | * iOS 9.0+
196 | * Swift 5+
197 |
198 | 安装
199 | =================
200 | Swift Package Manager
201 | * File > Swift Packages > Add Package Dependency
202 | * Add https://github.com/zgjff/JJCarouselView.git
203 |
204 | Cocoapods
205 | ```
206 | use_frameworks!
207 | pod 'JJCarouselView'
208 | ```
209 |
--------------------------------------------------------------------------------
/Sources/CellContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CellContainer.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | extension JJCarouselView {
11 | /// 轮播图cell+index的容器类
12 | final class CellContainer {
13 | var onTap: ((Cell, Int) -> ())?
14 | init(cell: Cell, index: Int) {
15 | cell.isUserInteractionEnabled = true
16 | self.cell = cell
17 | self.index = index
18 | self.cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onClickCell)))
19 | }
20 | let cell: Cell
21 | var index = 0
22 |
23 | @IBAction private func onClickCell() {
24 | onTap?(cell, index)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Config.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Config.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | extension JJCarouselView {
11 | /// 轮播图配置
12 | public struct Config {
13 | /// 自动滑动方向,默认左->右
14 | public var direction = JJCarousel.Direction.ltr
15 |
16 | /// 是否自动轮播,默认`true`自动轮播
17 | public var autoLoop = true
18 |
19 | /// 能否手动滑动,默认`true`可以手动滑动
20 | public var manualSlidingEnable = true
21 |
22 | /// 轮播间隔,默认5s
23 | public var loopTimeInterval: TimeInterval = 5
24 |
25 | /// 轮播图内边局
26 | public var contentInset = UIEdgeInsets.zero
27 |
28 | /// 具体显示轮播内容的方法
29 | public var display: ((Cell, Object) -> ())?
30 |
31 | /// 计算pageView的frame, 默认底部居中显示
32 | public var pageViewFrame: ((_ pageView: JJCarouselViewPageable, _ direction: JJCarousel.Direction, _ carouselViewSize: CGSize, _ totalDataCount: Int) -> CGRect) = { pageView, _, carouselViewSize, totalDataCount in
33 | let pageSize = pageView.size(forNumberOfPages: totalDataCount)
34 | return CGRect(x: (carouselViewSize.width - pageSize.width) * 0.5, y: carouselViewSize.height - pageSize.height - 5, width: pageSize.width, height: pageSize.height)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/Direction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Direction.swift
3 | // JJCarouselView-Demo
4 | //
5 | // Created by zgjff on 2022/7/12.
6 | //
7 |
8 | import Foundation
9 |
10 | extension JJCarousel {
11 | /// 轮播图滑动方向
12 | public enum Direction {
13 | /// 左->右
14 | case ltr
15 | /// 右->左
16 | case rtl
17 | /// 上->下
18 | case ttb
19 | /// 下->上
20 | case btt
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Event.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Event.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/11.
6 | //
7 |
8 | import UIKit
9 | import Combine
10 |
11 | extension JJCarouselView {
12 | /// 轮播图事件
13 | public final class Event {
14 | /// 点击事件
15 | public var onTap: ((_ view: Cell, _ object: Object, _ index: Int) -> ())?
16 |
17 | /// 滑动回调(当前index, 目标index, 进度)
18 | public var onScroll: ((_ fronIndex: Int, _ toIndex: Int, _ progress: Float) -> ())?
19 |
20 | /// 准备滑动到目标的index
21 | public var willMove: ((_ index: Int) -> ())?
22 |
23 | /// 已经滑动到具体的index
24 | public var didMove: ((_ index: Int) -> ())?
25 |
26 | // @available(iOS 13.0, *)
27 | // internal lazy var _onTapPublisher = PassthroughSubject<(Cell, Object, Int), Never>()
28 | //
29 | // @available(iOS 13.0, *)
30 | // internal lazy var _onScrollPublisher = PassthroughSubject<(Int, Int, Float), Never>()
31 | //
32 | // @available(iOS 13.0, *)
33 | // internal lazy var _willMovePublisher = PassthroughSubject()
34 | //
35 | // @available(iOS 13.0, *)
36 | // internal lazy var _didMovePublisher = PassthroughSubject()
37 | }
38 | }
39 |
40 | internal extension JJCarouselView.Event {
41 | func onTapFunction(view: Cell, object: Object, index: Int) {
42 | onTap?(view, object, index)
43 | }
44 |
45 | func onScrollFunction(fronIndex: Int, toIndex: Int, progress: Float) {
46 | onScroll?(fronIndex, toIndex, progress)
47 | }
48 |
49 | func willMoveFunction(index: Int) {
50 | willMove?(index)
51 | }
52 |
53 | func didMoveFunction(index: Int) {
54 | didMove?(index)
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/JJCarousel+namespace.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JJCarousel+namespace.swift
3 | // JJCarouselView-Demo
4 | //
5 | // Created by zgjff on 2022/7/12.
6 | //
7 |
8 | import Foundation
9 |
10 | /// 命名空间
11 | public enum JJCarousel {}
12 |
--------------------------------------------------------------------------------
/Sources/JJCarouselContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JJCarouselContainerView.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/13.
6 | //
7 |
8 | import UIKit
9 |
10 | /// 轮播图容器协议
11 | protocol JJCarouselContainerView: UIView {
12 | var delegate: JJCarouselContainerViewDelegate? { get set }
13 | var dataSource: JJCarouselContainerViewDataSource? { get set }
14 | /// 刷新数据
15 | func reload()
16 | /// 需要滑动下一位置
17 | func needAutoScrollToNextIndex()
18 | /// 处理能否手动滑动
19 | func dealManualSliding(enable: Bool)
20 | }
21 |
22 | /// 轮播图容器代理
23 | protocol JJCarouselContainerViewDelegate: NSObjectProtocol {
24 | /// 展示轮播图数据
25 | func displayCell(_ cell: UIView, atIndex index: Int)
26 | /// 点击具体的cell容器
27 | func onClickCell(_ cell: UIView, atIndex index: Int)
28 | /// 将要到index位置
29 | func willScroll(to index: Int)
30 | /// 滑动
31 | func onScroll(from fromIndex: Int, to toIndex: Int, progress: Float)
32 | /// 滑动到index位置
33 | func didScroll(to index: Int)
34 | /// 开始手动拖动scrollView
35 | func scrollViewWillBeginDragging()
36 | /// scrollView停止滑动
37 | func scrollViewDidEndDecelerating()
38 | }
39 |
40 | /// 轮播图容器数据源
41 | protocol JJCarouselContainerViewDataSource: NSObjectProtocol {
42 | /// 轮播方向
43 | func loopDirection() -> JJCarousel.Direction
44 | /// 数据源数量
45 | func numberOfDatas() -> Int
46 | /// 轮播图内边局
47 | func cellContentInset() -> UIEdgeInsets
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/JJCarouselView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JJCarouselView.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | /// 展示`UIImage`的轮播图别名
11 | public typealias JJLocalImageCarouselView = JJCarouselView
12 |
13 | /// 展示网络图片的轮播图别名
14 | public typealias JJWebImageCarouselView = JJCarouselView
15 |
16 | /// 轮播图组件
17 | public final class JJCarouselView: UIView {
18 | /// 配置
19 | public var config = Config() {
20 | didSet {
21 | containerView.dealManualSliding(enable: config.manualSlidingEnable)
22 | }
23 | }
24 |
25 | /// 数据源
26 | public var datas: [Object] = [] {
27 | didSet {
28 | onGetDatas(old: oldValue, new: datas)
29 | }
30 | }
31 |
32 | /// 事件回调
33 | public var event = Event()
34 |
35 | /// 用于设置轮播图的指示器
36 | public var pageView: JJCarouselViewPageable = JJCarouselDotPageView() {
37 | didSet {
38 | oldValue.removeFromSuperview()
39 | pageView.numberOfPages = datas.count
40 | addSubview(pageView)
41 | bringSubviewToFront(pageView)
42 | }
43 | }
44 |
45 | /// 初始化
46 | /// - Parameters:
47 | /// - frame: frame
48 | /// - initialize: 初始化单个cell的方法。如果`nil`,则按照其初始化方法`init(frame: CGRect)`来初始化
49 | /// - style: 轮播图风格,默认`full`平铺风格
50 | public init(frame: CGRect, initialize: (() -> Cell)?, style: Style = .full) {
51 | containerView = style.createContainerView(frame: frame, initialize: initialize)
52 | super.init(frame: frame)
53 | containerView.dataSource = self
54 | containerView.delegate = self
55 | addSubview(containerView)
56 | addSubview(pageView)
57 | addObservers()
58 | }
59 |
60 | public override init(frame: CGRect) {
61 | containerView = Style.full.createContainerView(frame: .zero, initialize: nil)
62 | super.init(frame: frame)
63 | containerView.dataSource = self
64 | containerView.delegate = self
65 | addSubview(containerView)
66 | addSubview(pageView)
67 | addObservers()
68 | }
69 |
70 | required init?(coder: NSCoder) {
71 | fatalError("init(coder:) has not been implemented")
72 | }
73 |
74 | // MARK: - 组件
75 | private let containerView: JJCarouselContainerView
76 |
77 | // MARK: - 属性
78 | private var preScrollcontentOffset = CGPoint.zero
79 | private var timer: Timer?
80 | private var didEnterBackgroundObserver: NSObjectProtocol?
81 | private var willEnterForegroundObserver: NSObjectProtocol?
82 |
83 | deinit {
84 | removeObservers()
85 | destoryTimer()
86 | }
87 |
88 | public override func willMove(toWindow newWindow: UIWindow?) {
89 | super.willMove(toWindow: newWindow)
90 | if newWindow == nil {
91 | scrollViewWillBeginDragging()
92 | } else {
93 | scrollViewDidEndDecelerating()
94 | }
95 | }
96 |
97 | public override func layoutSubviews() {
98 | super.layoutSubviews()
99 | containerView.frame = bounds
100 | pageView.frame = config.pageViewFrame(pageView, config.direction, bounds.size, datas.count)
101 | }
102 |
103 | // MARK: - 私有方法
104 |
105 | /// 重启定时器
106 | @IBAction private func resumeTimer() {
107 | timer?.fireDate = Date()
108 | }
109 |
110 | @IBAction private func loopTimer_below10() {
111 | if !datas.isEmpty {
112 | containerView.needAutoScrollToNextIndex()
113 | }
114 | }
115 | }
116 |
117 | // MARK: - JJCarouselContainerViewDataSource, JJCarouselContainerViewDelegate
118 | extension JJCarouselView: JJCarouselContainerViewDataSource, JJCarouselContainerViewDelegate {
119 | func loopDirection() -> JJCarousel.Direction {
120 | return config.direction
121 | }
122 |
123 | func numberOfDatas() -> Int {
124 | return datas.count
125 | }
126 |
127 | func cellContentInset() -> UIEdgeInsets {
128 | return config.contentInset
129 | }
130 |
131 | func displayCell(_ cell: UIView, atIndex index: Int) {
132 | if let cell = cell as? Cell {
133 | config.display?(cell, datas[index])
134 | }
135 | }
136 |
137 | func onClickCell(_ cell: UIView, atIndex index: Int) {
138 | if let cell = cell as? Cell {
139 | event.onTapFunction(view: cell, object: datas[index], index: index)
140 | }
141 | }
142 |
143 | func willScroll(to index: Int) {
144 | event.willMoveFunction(index: index)
145 | }
146 |
147 | func onScroll(from fromIndex: Int, to toIndex: Int, progress: Float) {
148 | event.onScrollFunction(fronIndex: fromIndex, toIndex: toIndex, progress: progress)
149 | pageView.onScroll(from: fromIndex, to: toIndex, progress: progress)
150 | }
151 |
152 | func didScroll(to index: Int) {
153 | pageView.currentPage = index
154 | event.didMoveFunction(index: index)
155 | }
156 |
157 | func scrollViewWillBeginDragging() {
158 | if config.autoLoop {
159 | NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(resumeTimer), object: nil)
160 | pauseTimer()
161 | }
162 | }
163 |
164 | func scrollViewDidEndDecelerating() {
165 | if config.autoLoop {
166 | /// 先取消,再重启....防止多次重启
167 | NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(resumeTimer), object: nil)
168 | perform(#selector(resumeTimer), with: nil, afterDelay: config.loopTimeInterval)
169 | }
170 | }
171 | }
172 |
173 | // MARK: - private
174 | private extension JJCarouselView {
175 | func onGetDatas(old: [Object], new: [Object]) {
176 | if old.count != new.count {
177 | onGetDifferentDatas()
178 | return
179 | }
180 | if new.isEmpty {
181 | return
182 | }
183 | let isSame = new.elementsEqual(old, by: { $0 == $1 })
184 | if !isSame {
185 | onGetDifferentDatas()
186 | }
187 | }
188 |
189 | func onGetDifferentDatas() {
190 | pageView.numberOfPages = datas.count
191 | if !bounds.isEmpty {
192 | pageView.frame = config.pageViewFrame(pageView, config.direction, bounds.size, datas.count)
193 | }
194 | containerView.reload()
195 | if datas.count > 1 {
196 | createTimer()
197 | } else {
198 | destoryTimer()
199 | }
200 | }
201 | }
202 |
203 | // MARK: - NotificationCenter相关
204 | private extension JJCarouselView {
205 | func addObservers() {
206 | didEnterBackgroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: .main, using: { [weak self] _ in
207 | self?.scrollViewWillBeginDragging()
208 | })
209 | willEnterForegroundObserver = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main, using: { [weak self] _ in
210 | guard let self = self else {
211 | return
212 | }
213 | if self.config.autoLoop && (self.window != nil) {
214 | self.timer?.fireDate = Date().addingTimeInterval(self.config.loopTimeInterval)
215 | }
216 | })
217 | }
218 |
219 | func removeObservers() {
220 | if let didEnterBackgroundObserver = didEnterBackgroundObserver {
221 | NotificationCenter.default.removeObserver(didEnterBackgroundObserver)
222 | }
223 | if let willEnterForegroundObserver = willEnterForegroundObserver {
224 | NotificationCenter.default.removeObserver(willEnterForegroundObserver)
225 | }
226 | }
227 | }
228 |
229 | // MARK: - timer相关
230 | private extension JJCarouselView {
231 | func createTimer() {
232 | if !config.autoLoop {
233 | return
234 | }
235 | if #available(iOS 10.0, *) {
236 | let timer = Timer(fire: Date().addingTimeInterval(config.loopTimeInterval), interval: config.loopTimeInterval, repeats: true) { [weak self] _ in
237 | guard let self = self, !self.datas.isEmpty else {
238 | return
239 | }
240 | self.containerView.needAutoScrollToNextIndex()
241 | }
242 | self.timer = timer
243 | RunLoop.current.add(timer, forMode: .common)
244 | } else {
245 | let timer = Timer(fireAt: Date().addingTimeInterval(config.loopTimeInterval), interval: config.loopTimeInterval, target: self, selector: #selector(loopTimer_below10), userInfo: nil, repeats: true)
246 | self.timer = timer
247 | RunLoop.current.add(timer, forMode: .common)
248 | }
249 | }
250 |
251 | func pauseTimer() {
252 | timer?.fireDate = .distantFuture
253 | }
254 |
255 | func destoryTimer() {
256 | timer?.invalidate()
257 | timer = nil
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/Sources/JJCarouselViewPageable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JJCarouselViewPageable.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | /// 抽象化轮播图page指示器协议
11 | public protocol JJCarouselViewPageable: UIView {
12 | /// 如果只有一个轮播数据时,是否隐藏page指示器
13 | var hidesForSinglePage: Bool { get set }
14 |
15 | /// 轮播数据总数
16 | var numberOfPages: Int { get set }
17 |
18 | /// 当前位置
19 | var currentPage: Int { get set }
20 |
21 | /// 非当前指示器颜色
22 | var pageIndicatorTintColor: UIColor? { get set }
23 |
24 | /// 当前指示器颜色
25 | var currentPageIndicatorTintColor: UIColor? { get set }
26 |
27 | /// 根据给定的数据数量计算对应控件所需的size
28 | func size(forNumberOfPages pageCount: Int) -> CGSize
29 |
30 | /// 轮播图滑动回调
31 | /// - Parameters:
32 | /// - fromIndex: 当前index
33 | /// - toindex: 目标index
34 | /// - progress: 进度
35 | func onScroll(from fromIndex: Int, to toindex: Int, progress: Float)
36 | }
37 |
38 | extension UIPageControl: JJCarouselViewPageable {
39 | public func onScroll(from fromIndex: Int, to toindex: Int, progress: Float) {}
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Providers/FullCarouselContainerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FullCarouselContainerView.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/13.
6 | //
7 |
8 | import UIKit
9 |
10 | extension JJCarouselView {
11 | /// cell充满view风格轮播图
12 | final class FullContainerView: UIView, JJCarouselContainerView, UIScrollViewDelegate {
13 | weak var delegate: JJCarouselContainerViewDelegate?
14 | weak var dataSource: JJCarouselContainerViewDataSource?
15 |
16 | init(frame: CGRect, initialize: (() -> Cell)?) {
17 | scrollView = UIScrollView(frame: CGRect(origin: .zero, size: frame.size))
18 | scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
19 | scrollView.isPagingEnabled = true
20 | scrollView.bounces = false
21 | scrollView.showsVerticalScrollIndicator = false
22 | scrollView.showsHorizontalScrollIndicator = false
23 | if #available(iOS 11.0, *) {
24 | scrollView.contentInsetAdjustmentBehavior = .never
25 | } else {
26 | // Fallback on earlier versions
27 | }
28 | firstContainer = CellContainer(cell: initialize?() ?? Cell(frame: .zero), index: 0)
29 | secondContainer = CellContainer(cell: initialize?() ?? Cell(frame: .zero), index: 1)
30 | thirdContainer = CellContainer(cell: initialize?() ?? Cell(frame: .zero), index: 2)
31 | super.init(frame: CGRect(origin: .zero, size: frame.size))
32 | scrollView.isScrollEnabled = false
33 | scrollView.delegate = self
34 | addSubview(scrollView)
35 | [firstContainer, secondContainer, thirdContainer].forEach { [unowned self] obj in
36 | obj.cell.isHidden = true
37 | obj.onTap = { [weak self] cell, idx in
38 | self?.delegate?.onClickCell(cell, atIndex: idx)
39 | }
40 | self.scrollView.addSubview(obj.cell)
41 | }
42 | }
43 |
44 | required init?(coder: NSCoder) {
45 | fatalError("init(coder:) has not been implemented")
46 | }
47 |
48 | // MARK: - 组件
49 | private let scrollView: UIScrollView
50 | private let firstContainer: CellContainer
51 | private let secondContainer: CellContainer
52 | private let thirdContainer: CellContainer
53 |
54 | // MARK: - 属性
55 | private var currentFrame = CGRect.zero {
56 | didSet {
57 | onChangeFrame(old: oldValue, new: currentFrame)
58 | }
59 | }
60 |
61 | private var currentIndex = 0 {
62 | didSet {
63 | onChangeCurrentIndex()
64 | }
65 | }
66 |
67 | private var willScrollIndex = -1 {
68 | didSet {
69 | if (willScrollIndex != oldValue) && (willScrollIndex != -1) {
70 | delegate?.willScroll(to: willScrollIndex)
71 | }
72 | }
73 | }
74 |
75 | private var manualSlidingEnable = true
76 |
77 | override func layoutSubviews() {
78 | super.layoutSubviews()
79 | currentFrame = bounds
80 | }
81 |
82 | // MARK: - UIScrollViewDelegate
83 |
84 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
85 | let dataCount = dataSource?.numberOfDatas() ?? 0
86 | if (bounds.width == 0) || (bounds.height == 0) || (dataCount == 0) {
87 | return
88 | }
89 | let direction = dataSource?.loopDirection() ?? .ltr
90 | switch direction {
91 | case .ltr:
92 | let offsetX = scrollView.contentOffset.x
93 | if (bounds.width < offsetX) && (offsetX < bounds.width * 2) {
94 | let nextIndex = (currentIndex + 1) % dataCount
95 | willScrollIndex = nextIndex
96 | let progress = (offsetX - bounds.width) / bounds.width
97 | delegate?.onScroll(from: currentIndex, to: nextIndex, progress: Float(progress))
98 | } else if (0 < offsetX) && (offsetX < bounds.width) {
99 | let preIndex = (currentIndex - 1 + dataCount) % dataCount
100 | willScrollIndex = preIndex
101 | let progress = (bounds.width - offsetX) / bounds.width
102 | delegate?.onScroll(from: currentIndex, to: preIndex, progress: Float(progress))
103 | }
104 | case .rtl:
105 | let offsetX = scrollView.contentOffset.x
106 | if (bounds.width < offsetX) && (offsetX < bounds.width * 2) {
107 | let nextIndex = (currentIndex - 1 + dataCount) % dataCount
108 | willScrollIndex = nextIndex
109 | let progress = (offsetX - bounds.width) / bounds.width
110 | delegate?.onScroll(from: currentIndex, to: nextIndex, progress: Float(progress))
111 | } else if (0 < offsetX) && (offsetX < bounds.width) {
112 | let preIndex = (currentIndex + 1 + dataCount) % dataCount
113 | willScrollIndex = preIndex
114 | let progress = (bounds.width - offsetX) / bounds.width
115 | delegate?.onScroll(from: currentIndex, to: preIndex, progress: Float(progress))
116 | }
117 | case .ttb:
118 | let offsetY = scrollView.contentOffset.y
119 | if (bounds.height < offsetY) && (offsetY < bounds.height * 2) {
120 | let nextIndex = (currentIndex + 1) % dataCount
121 | willScrollIndex = nextIndex
122 | let progress = (offsetY - bounds.height) / bounds.height
123 | delegate?.onScroll(from: currentIndex, to: nextIndex, progress: Float(progress))
124 | } else if (0 < offsetY) && (offsetY < bounds.height) {
125 | let preIndex = (currentIndex - 1 + dataCount) % dataCount
126 | willScrollIndex = preIndex
127 | let progress = (bounds.height - offsetY) / bounds.height
128 | delegate?.onScroll(from: currentIndex, to: preIndex, progress: Float(progress))
129 | }
130 | case .btt:
131 | let offsetY = scrollView.contentOffset.y
132 | if (bounds.height < offsetY) && (offsetY < bounds.height * 2) {
133 | let nextIndex = (currentIndex - 1 + dataCount) % dataCount
134 | willScrollIndex = nextIndex
135 | let progress = (offsetY - bounds.height) / bounds.height
136 | delegate?.onScroll(from: currentIndex, to: nextIndex, progress: Float(progress))
137 | } else if (0 < offsetY) && (offsetY < bounds.height) {
138 | let preIndex = (currentIndex + 1 + dataCount) % dataCount
139 | willScrollIndex = preIndex
140 | let progress = (bounds.height - offsetY) / bounds.height
141 | delegate?.onScroll(from: currentIndex, to: preIndex, progress: Float(progress))
142 | }
143 | }
144 | }
145 |
146 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
147 | delegate?.scrollViewWillBeginDragging()
148 | }
149 |
150 | func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
151 | adjustWhenEndScrolling(animation: false)
152 | }
153 |
154 | func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
155 | adjustWhenEndScrolling(animation: true)
156 | }
157 |
158 | func reload() {
159 | onGetDatas()
160 | }
161 |
162 | func needAutoScrollToNextIndex() {
163 | let direction = dataSource?.loopDirection() ?? .ltr
164 | switch direction {
165 | case .ltr:
166 | scrollView.setContentOffset(CGPoint(x: bounds.width * 2, y: 0), animated: true)
167 | case .rtl:
168 | scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
169 | case .ttb:
170 | scrollView.setContentOffset(CGPoint(x: 0, y: bounds.height * 2), animated: true)
171 | case .btt:
172 | scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
173 | }
174 | }
175 |
176 | func dealManualSliding(enable: Bool) {
177 | manualSlidingEnable = enable
178 | let dataCount = dataSource?.numberOfDatas() ?? 0
179 | scrollView.isScrollEnabled = enable ? (dataCount > 1) : false
180 | }
181 | }
182 | }
183 |
184 | private extension JJCarouselView.FullContainerView {
185 | func onChangeFrame(old: CGRect, new: CGRect) {
186 | if old == new {
187 | return
188 | }
189 | layoutCells()
190 | let direction = dataSource?.loopDirection() ?? .ltr
191 | switch direction {
192 | case .ltr, .rtl:
193 | scrollView.contentSize = CGSize(width: bounds.width * 3, height: bounds.height)
194 | scrollView.contentOffset = CGPoint(x: bounds.width, y: 0)
195 | case .ttb, .btt:
196 | scrollView.contentSize = CGSize(width: bounds.width, height: bounds.height * 3)
197 | scrollView.contentOffset = CGPoint(x: 0, y: bounds.height)
198 | }
199 | }
200 |
201 | func layoutCells() {
202 | let contentInset = dataSource?.cellContentInset() ?? .zero
203 | let direction = dataSource?.loopDirection() ?? .ltr
204 | switch direction {
205 | case .ltr, .rtl:
206 | let contentInsetWidth = bounds.width - contentInset.left - contentInset.right
207 | let cellWidth = contentInsetWidth > 0 ? contentInsetWidth : 0
208 | let contentInsetHeight = bounds.height - contentInset.top - contentInset.bottom
209 | let cellHeight = contentInsetHeight > 0 ? contentInsetHeight : 0
210 | firstContainer.cell.frame = CGRect(x: contentInset.left, y: contentInset.top, width: cellWidth, height: cellHeight)
211 | secondContainer.cell.frame = firstContainer.cell.frame.offsetBy(dx: bounds.width, dy: 0)
212 | thirdContainer.cell.frame = secondContainer.cell.frame.offsetBy(dx: bounds.width, dy: 0)
213 | case .ttb, .btt:
214 | firstContainer.cell.frame = CGRect(origin: .zero, size: bounds.size)
215 | let contentInsetWidth = bounds.width - contentInset.left - contentInset.right
216 | let cellWidth = contentInsetWidth > 0 ? contentInsetWidth : 0
217 | let contentInsetHeight = bounds.height - contentInset.top - contentInset.bottom
218 | let cellHeight = contentInsetHeight > 0 ? contentInsetHeight : 0
219 | firstContainer.cell.frame = CGRect(x: contentInset.left, y: contentInset.top, width: cellWidth, height: cellHeight)
220 | secondContainer.cell.frame = firstContainer.cell.frame.offsetBy(dx: 0, dy: bounds.height)
221 | thirdContainer.cell.frame = secondContainer.cell.frame.offsetBy(dx: 0, dy: bounds.height)
222 | }
223 | }
224 |
225 | func onGetDatas() {
226 | let dataCount = dataSource?.numberOfDatas() ?? 0
227 | scrollView.isScrollEnabled = manualSlidingEnable ? (dataCount > 1) : false
228 | switch dataCount {
229 | case 0:
230 | [firstContainer, secondContainer, thirdContainer].forEach { $0.cell.isHidden = true }
231 | case 1:
232 | [firstContainer, thirdContainer].forEach { $0.cell.isHidden = true }
233 | secondContainer.cell.isHidden = false
234 | default:
235 | [firstContainer, secondContainer, thirdContainer].forEach { $0.cell.isHidden = false }
236 | }
237 | if bounds.isEmpty {
238 | setNeedsLayout()
239 | layoutIfNeeded()
240 | } else {
241 | let direction = dataSource?.loopDirection() ?? .ltr
242 | switch direction {
243 | case .ltr, .rtl:
244 | scrollView.contentOffset = CGPoint(x: bounds.width, y: 0)
245 | case .ttb, .btt:
246 | scrollView.contentOffset = CGPoint(x: 0, y: bounds.height)
247 | }
248 | }
249 | delegate?.willScroll(to: 0)
250 | currentIndex = 0
251 | }
252 |
253 | func onChangeCurrentIndex() {
254 | let dataCount = dataSource?.numberOfDatas() ?? 0
255 | delegate?.didScroll(to: currentIndex)
256 | switch dataCount {
257 | case 0:
258 | return
259 | case 1:
260 | secondContainer.index = 0
261 | delegate?.displayCell(secondContainer.cell, atIndex: 0)
262 | default:
263 | let direction = dataSource?.loopDirection() ?? .ltr
264 | switch direction {
265 | case .ltr, .ttb:
266 | let preIndex = (currentIndex - 1 + dataCount) % dataCount
267 | firstContainer.index = preIndex
268 | delegate?.displayCell(firstContainer.cell, atIndex: preIndex)
269 | secondContainer.index = currentIndex
270 | delegate?.displayCell(secondContainer.cell, atIndex: currentIndex)
271 | let nextIndex = (currentIndex + 1) % dataCount
272 | thirdContainer.index = nextIndex
273 | delegate?.displayCell(thirdContainer.cell, atIndex: nextIndex)
274 | case .rtl, .btt:
275 | let preIndex = (currentIndex + 1 + dataCount) % dataCount
276 | firstContainer.index = preIndex
277 | delegate?.displayCell(firstContainer.cell, atIndex: preIndex)
278 | secondContainer.index = currentIndex
279 | delegate?.displayCell(secondContainer.cell, atIndex: currentIndex)
280 | let nextIndex = (currentIndex - 1 + dataCount) % dataCount
281 | thirdContainer.index = nextIndex
282 | delegate?.displayCell(thirdContainer.cell, atIndex: nextIndex)
283 | }
284 | }
285 | }
286 |
287 | func adjustWhenEndScrolling(animation: Bool) {
288 | let dataCount = dataSource?.numberOfDatas() ?? 0
289 | if dataCount == 0 {
290 | return
291 | }
292 | if !animation {
293 | delegate?.scrollViewDidEndDecelerating()
294 | }
295 | let offset = scrollView.contentOffset
296 | let storeCurrentIndex = currentIndex
297 | var nextIndex = currentIndex
298 | let direction = dataSource?.loopDirection() ?? .ltr
299 | switch direction {
300 | case .ltr:
301 | if offset.x == 0 {
302 | nextIndex = (currentIndex - 1 + dataCount) % dataCount
303 | scrollView.contentOffset = CGPoint(x: bounds.width, y: 0)
304 | } else if offset.x == bounds.width * 2 {
305 | nextIndex = (currentIndex + 1 + dataCount) % dataCount
306 | scrollView.contentOffset = CGPoint(x: bounds.width, y: 0)
307 | }
308 | case .rtl:
309 | if offset.x == 0 {
310 | nextIndex = (currentIndex + 1 + dataCount) % dataCount
311 | scrollView.contentOffset = CGPoint(x: bounds.width, y: 0)
312 | } else if offset.x == bounds.width * 2 {
313 | nextIndex = (currentIndex - 1 + dataCount) % dataCount
314 | scrollView.contentOffset = CGPoint(x: bounds.width, y: 0)
315 | }
316 | case .ttb:
317 | if offset.y == 0 {
318 | nextIndex = (currentIndex - 1 + dataCount) % dataCount
319 | scrollView.contentOffset = CGPoint(x: 0, y: bounds.height)
320 | } else if offset.y == bounds.height * 2 {
321 | nextIndex = (currentIndex + 1 + dataCount) % dataCount
322 | scrollView.contentOffset = CGPoint(x: 0, y: bounds.height)
323 | }
324 | case .btt:
325 | if offset.y == 0 {
326 | nextIndex = (currentIndex + 1 + dataCount) % dataCount
327 | scrollView.contentOffset = CGPoint(x: 0, y: bounds.height)
328 | } else if offset.y == bounds.height * 2 {
329 | nextIndex = (currentIndex - 1 + dataCount) % dataCount
330 | scrollView.contentOffset = CGPoint(x: 0, y: bounds.height)
331 | }
332 | }
333 | delegate?.onScroll(from: storeCurrentIndex, to: nextIndex, progress: 1.0)
334 | willScrollIndex = -1
335 | currentIndex = nextIndex
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/Sources/Providers/JJCarouselDotPageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JJCarouselDotPageView.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | /// 圆点轮播图page指示器。
11 | public final class JJCarouselDotPageView: UIView, JJCarouselViewPageable {
12 | public var hidesForSinglePage: Bool = true
13 | public var numberOfPages: Int = 0 {
14 | didSet {
15 | isHidden = (numberOfPages > 1) ? false : hidesForSinglePage
16 | if numberOfPages != oldValue {
17 | dotLayers.forEach { $0.removeFromSuperlayer() }
18 | dotLayers = []
19 | createDotLayers()
20 | }
21 | }
22 | }
23 |
24 | public var currentPage: Int = 0 {
25 | didSet {
26 | if currentPage != oldValue {
27 | resetDotViewsColor()
28 | }
29 | }
30 | }
31 |
32 | public var pageIndicatorTintColor: UIColor? = UIColor.gray
33 |
34 | public var currentPageIndicatorTintColor: UIColor? = UIColor.white
35 |
36 | /// 圆点大小
37 | public var dotViewSize = CGSize(width: 6, height: 6)
38 |
39 | /// 圆点间距
40 | public var dotSpace: CGFloat = 4
41 |
42 | /// 轮播图滑动时,是否使用过渡混合色。默认`true`
43 | public var usingTransitionColorWhileScroll = true
44 |
45 | public func size(forNumberOfPages pageCount: Int) -> CGSize {
46 | return sizeThatFits(.zero)
47 | }
48 |
49 | public func onScroll(from fromIndex: Int, to toindex: Int, progress: Float) {
50 | if !usingTransitionColorWhileScroll {
51 | return
52 | }
53 | let currentColor = currentPageIndicatorTintColor ?? .clear
54 | let pageColor = pageIndicatorTintColor ?? .clear
55 | let fc = currentColor.mix(secondColor: pageColor, secondColorRatio: progress)
56 | dotLayers[fromIndex].backgroundColor = fc.cgColor
57 | let tc = pageColor.mix(secondColor: currentColor, secondColorRatio: progress)
58 | dotLayers[toindex].backgroundColor = tc.cgColor
59 | }
60 |
61 | public override func sizeThatFits(_ size: CGSize) -> CGSize {
62 | if numberOfPages <= 1 {
63 | return dotViewSize
64 | }
65 | let w = (dotViewSize.width + dotSpace) * CGFloat(numberOfPages) - dotSpace
66 | return CGSize(width: w, height: dotViewSize.height)
67 | }
68 |
69 | public override func layoutSubviews() {
70 | super.layoutSubviews()
71 | let ty = (bounds.height - dotViewSize.height) * 0.5
72 | for (idx, pl) in dotLayers.enumerated() {
73 | pl.frame = CGRect(x: (dotViewSize.width + dotSpace) * CGFloat(idx), y: ty, width: dotViewSize.width, height: dotViewSize.height)
74 | }
75 | }
76 |
77 | private var dotLayers: [CALayer] = []
78 |
79 | private func createDotLayers() {
80 | if numberOfPages <= 0 {
81 | return
82 | }
83 | for idx in 0.. CGSize {
23 | return .zero
24 | }
25 |
26 | public func onScroll(from fromIndex: Int, to toindex: Int, progress: Float) { }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Providers/JJCarouselNumberPageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JJCarouselNumberPageView.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/8.
6 | //
7 |
8 | import UIKit
9 |
10 | /// 数字化轮播图page指示器。具体变现为: 1/6
11 | public final class JJCarouselNumberPageView: UIView, JJCarouselViewPageable {
12 | public var hidesForSinglePage: Bool = true
13 |
14 | public var numberOfPages: Int = 0 {
15 | didSet {
16 | isHidden = (numberOfPages > 1) ? false : hidesForSinglePage
17 | totalLabel.text = "\(numberOfPages)"
18 | totalLabel.sizeToFit()
19 | }
20 | }
21 |
22 | public var currentPage: Int = 0 {
23 | didSet {
24 | currentLabel.text = "\(currentPage + 1)"
25 | currentLabel.sizeToFit()
26 | }
27 | }
28 |
29 | public var pageIndicatorTintColor: UIColor? = .white {
30 | didSet {
31 | totalLabel.textColor = pageIndicatorTintColor
32 | }
33 | }
34 |
35 | public var dotTintColor: UIColor? = UIColor.white {
36 | didSet {
37 | dotLabel.textColor = dotTintColor
38 | }
39 | }
40 |
41 | public var currentPageIndicatorTintColor: UIColor? = .white {
42 | didSet {
43 | currentLabel.textColor = currentPageIndicatorTintColor
44 | }
45 | }
46 |
47 | /// 字体大小
48 | public var font = UIFont.systemFont(ofSize: 11) {
49 | didSet {
50 | dotLabel.font = font
51 | currentLabel.font = font
52 | totalLabel.font = font
53 | currentLabel.sizeToFit()
54 | dotLabel.sizeToFit()
55 | totalLabel.sizeToFit()
56 | }
57 | }
58 |
59 | override init(frame: CGRect) {
60 | super.init(frame: frame)
61 | setup()
62 | }
63 |
64 | required init?(coder: NSCoder) {
65 | super.init(coder: coder)
66 | setup()
67 | }
68 |
69 | private lazy var currentLabel = UILabel()
70 | private lazy var dotLabel = UILabel()
71 | private lazy var totalLabel = UILabel()
72 |
73 | public func size(forNumberOfPages pageCount: Int) -> CGSize {
74 | return sizeThatFits(.zero)
75 | }
76 |
77 | public func onScroll(from fromIndex: Int, to toindex: Int, progress: Float) { }
78 |
79 | public override func sizeThatFits(_ size: CGSize) -> CGSize {
80 | let w = dotLabel.bounds.width + totalLabel.bounds.width * 2
81 | let h = max(currentLabel.bounds.height, totalLabel.bounds.width)
82 | return CGSize(width: w + 16, height: h + 8)
83 | }
84 |
85 | public override func layoutSubviews() {
86 | super.layoutSubviews()
87 | layer.cornerRadius = bounds.height * 0.5
88 |
89 | dotLabel.frame = CGRect(x: (bounds.width - dotLabel.bounds.width) * 0.5, y: (bounds.height - dotLabel.bounds.height) * 0.5, width: dotLabel.bounds.width, height: dotLabel.bounds.height)
90 |
91 | currentLabel.frame = CGRect(x: dotLabel.frame.minX - currentLabel.frame.width - 2, y: (bounds.height - currentLabel.bounds.height) * 0.5, width: currentLabel.bounds.width, height: currentLabel.bounds.height)
92 |
93 | totalLabel.frame = CGRect(x: dotLabel.frame.maxX + 2, y: (bounds.height - totalLabel.bounds.height) * 0.5, width: totalLabel.bounds.width, height: totalLabel.bounds.height)
94 | }
95 |
96 | private func setup() {
97 | dotLabel.text = "/"
98 | currentLabel.text = " "
99 | totalLabel.text = " "
100 | pageIndicatorTintColor = .white
101 | currentPageIndicatorTintColor = .white
102 | dotTintColor = .white
103 | font = UIFont.systemFont(ofSize: 11)
104 | currentLabel.textAlignment = .right
105 | totalLabel.textAlignment = .left
106 | dotLabel.sizeToFit()
107 | currentLabel.sizeToFit()
108 | totalLabel.sizeToFit()
109 | [currentLabel, dotLabel, totalLabel].forEach { self.addSubview($0) }
110 | clipsToBounds = true
111 | backgroundColor = UIColor.black.withAlphaComponent(0.4)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Sources/Style.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Style.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/13.
6 | //
7 |
8 | import CoreGraphics
9 |
10 | extension JJCarouselView {
11 | /// 轮播图风格
12 | public enum Style {
13 | /// 平铺式-----单个cell充满整个轮播图容器
14 | case full
15 | // TODO: - 其它风格
16 | }
17 | }
18 |
19 | extension JJCarouselView.Style {
20 | func createContainerView(frame: CGRect, initialize: (() -> Cell)?) -> JJCarouselContainerView {
21 | switch self {
22 | case .full:
23 | return JJCarouselView.FullContainerView(frame: frame, initialize: initialize)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/UIColor+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor+Extension.swift
3 | // JJCarouselView
4 | //
5 | // Created by zgjff on 2022/4/21.
6 | //
7 |
8 | import UIKit
9 |
10 | extension UIColor {
11 | func mix(secondColor color: UIColor, secondColorRatio ratio: Float) -> UIColor {
12 | let rio = CGFloat(ratio > 1 ? 1 : (ratio < 0) ? 0 : ratio)
13 |
14 | var fr: CGFloat = 0
15 | var fg: CGFloat = 0
16 | var fb: CGFloat = 0
17 | var fa: CGFloat = 0
18 |
19 | var sr: CGFloat = 0
20 | var sg: CGFloat = 0
21 | var sb: CGFloat = 0
22 | var sa: CGFloat = 0
23 |
24 | guard getRed(&fr, green: &fg, blue: &fb, alpha: &fa),
25 | color.getRed(&sr, green: &sg, blue: &sb, alpha: &sa) else {
26 | return color
27 | }
28 | let r = fr * (1 - rio) + sr * rio
29 | let g = fg * (1 - rio) + sg * rio
30 | let b = fb * (1 - rio) + sb * rio
31 | let a = fa * (1 - rio) + sa * rio
32 | return UIColor(red: r, green: g, blue: b, alpha: a)
33 | }
34 |
35 | static func random() -> UIColor {
36 | return UIColor(hue: CGFloat(arc4random() % 256) / 256.0, saturation: CGFloat(arc4random() % 128) / 256.0 + 0.5, brightness: CGFloat(arc4random() % 128) / 256.0 + 0.5, alpha: 1)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Tests/JJCarouselViewTests/JJCarouselViewTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import JJCarouselView
3 |
4 | final class JJCarouselViewTests: XCTestCase {
5 | func testExample() throws {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(JJCarouselView().text, "Hello, World!")
10 | }
11 | }
12 |
--------------------------------------------------------------------------------