├── .gitignore
├── LICENSE
├── README.md
└── UICollectionViewDemo
├── Podfile
├── Podfile.lock
├── Pods
├── Kingfisher
│ ├── README.md
│ └── Sources
│ │ ├── AnimatedImageView.swift
│ │ ├── Box.swift
│ │ ├── CacheSerializer.swift
│ │ ├── Filter.swift
│ │ ├── FormatIndicatedCacheSerializer.swift
│ │ ├── Image.swift
│ │ ├── ImageCache.swift
│ │ ├── ImageDownloader.swift
│ │ ├── ImagePrefetcher.swift
│ │ ├── ImageProcessor.swift
│ │ ├── ImageTransition.swift
│ │ ├── ImageView+Kingfisher.swift
│ │ ├── Indicator.swift
│ │ ├── Kingfisher.h
│ │ ├── Kingfisher.swift
│ │ ├── KingfisherManager.swift
│ │ ├── KingfisherOptionsInfo.swift
│ │ ├── RequestModifier.swift
│ │ ├── Resource.swift
│ │ ├── String+MD5.swift
│ │ ├── ThreadHelper.swift
│ │ └── UIButton+Kingfisher.swift
├── Manifest.lock
├── Pods.xcodeproj
│ └── project.pbxproj
└── Target Support Files
│ ├── Kingfisher
│ ├── Info.plist
│ ├── Kingfisher-dummy.m
│ ├── Kingfisher-prefix.pch
│ ├── Kingfisher-umbrella.h
│ ├── Kingfisher.modulemap
│ └── Kingfisher.xcconfig
│ └── Pods-UICollectionViewDemo
│ ├── Info.plist
│ ├── Pods-UICollectionViewDemo-acknowledgements.markdown
│ ├── Pods-UICollectionViewDemo-acknowledgements.plist
│ ├── Pods-UICollectionViewDemo-dummy.m
│ ├── Pods-UICollectionViewDemo-frameworks.sh
│ ├── Pods-UICollectionViewDemo-resources.sh
│ ├── Pods-UICollectionViewDemo-umbrella.h
│ ├── Pods-UICollectionViewDemo.debug.xcconfig
│ ├── Pods-UICollectionViewDemo.modulemap
│ └── Pods-UICollectionViewDemo.release.xcconfig
├── UICollectionViewDemo.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
├── UICollectionViewDemo.xcworkspace
└── contents.xcworkspacedata
├── UICollectionViewDemo
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── collection_error.imageset
│ │ ├── Contents.json
│ │ └── collection_error@2x.png
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Base
│ ├── BaseCollectionViewCell.swift
│ ├── BaseCollectionViewController.swift
│ ├── BaseHeaderAndFooterCollectionReusableView.swift
│ └── BaseHeaderAndFooterCollectionReusableView.xib
├── Circular
│ ├── CircularCollectionViewCell.swift
│ ├── CircularCollectionViewController.swift
│ └── CircularCollectionViewLayout.swift
├── CoverFlow
│ ├── CoverFlowLayout.swift
│ └── CoverFlowViewController.swift
├── HorizontalCollection
│ ├── HorizontalCollectionFlowLayout.swift
│ └── HorizontalCollectionViewController.swift
├── Info.plist
├── Interactive
│ └── InteractiveMoveViewController.swift
├── Prefeching
│ ├── PrefectchCollectionViewCell.swift
│ ├── PrefectchCollectionViewCell.xib
│ └── PrefecthingCollectionViewController.swift
├── TodayNews
│ ├── MoveCollectionViewCell.swift
│ ├── MoveCollectionViewCell.xib
│ ├── MoveCollectionViewController.swift
│ ├── MoveHeaderCollectionReusableView.swift
│ └── MoveHeaderCollectionReusableView.xib
├── ViewController.swift
└── WaterFlow
│ ├── WaterFlowLayout.swift
│ └── WaterFlowViewController.swift
├── UICollectionViewDemoTests
├── Info.plist
└── UICollectionViewDemoTests.swift
└── UICollectionViewDemoUITests
├── Info.plist
└── UICollectionViewDemoUITests.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 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | # Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 alenpaulkevin
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UICollectionViewDemo
2 | 用collectionView实现各种效果
3 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Podfile:
--------------------------------------------------------------------------------
1 | # platform :ios, '9.0'
2 | target 'UICollectionViewDemo' do
3 | use_frameworks!
4 | pod 'Kingfisher', '~> 3.10.1'
5 | end
6 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Kingfisher (3.10.1)
3 |
4 | DEPENDENCIES:
5 | - Kingfisher (~> 3.10.1)
6 |
7 | SPEC CHECKSUMS:
8 | Kingfisher: 33a1b82140fcb14a9fc13f78dfde2a03ff8de8cb
9 |
10 | PODFILE CHECKSUM: f366e64ee1eb9167a86ae09cc5178ed888607b7d
11 |
12 | COCOAPODS: 1.2.1
13 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Kingfisher is a lightweight, pure-Swift library for downloading and caching images from the web. This project is heavily inspired by the popular [SDWebImage](https://github.com/rs/SDWebImage). It provides you a chance to use a pure-Swift alternative in your next app.
19 |
20 | ## Features
21 |
22 | - [x] Asynchronous image downloading and caching.
23 | - [x] `URLSession`-based networking. Basic image processors and filters supplied.
24 | - [x] Multiple-layer cache for both memory and disk.
25 | - [x] Cancelable downloading and processing tasks to improve performance.
26 | - [x] Independent components. Use the downloader or caching system separately as you need.
27 | - [x] Prefetching images and showing them from cache later when necessary.
28 | - [x] Extensions for `UIImageView`, `NSImage` and `UIButton` to directly set an image from a URL.
29 | - [x] Built-in transition animation when setting images.
30 | - [x] Extensible image processing and image format support.
31 |
32 | The simplest use-case is setting an image to an image view with the `UIImageView` extension:
33 |
34 | ```swift
35 | let url = URL(string: "url_of_your_image")
36 | imageView.kf.setImage(with: url)
37 | ```
38 |
39 | Kingfisher will download the image from `url`, send it to both the memory cache and the disk cache, and display it in `imageView`. When you use the same code later, the image will be retrieved from cache and shown immediately.
40 |
41 | ## Requirements
42 |
43 | - iOS 8.0+ / macOS 10.10+ / tvOS 9.0+ / watchOS 2.0+
44 | - Swift 3 (Kingfisher 3.x), Swift 2.3 (Kingfisher 2.x)
45 |
46 | Main development of Kingfisher will support Swift 3. Only critical bug fixes will be made for Kingfisher 2.x.
47 |
48 | [Kingfisher 3.0 Migration Guide](https://github.com/onevcat/Kingfisher/wiki/Kingfisher-3.0-Migration-Guide) - If you are upgrading to Kingfisher 3.x from an earlier version, please read this for more information.
49 |
50 | ## Next Steps
51 |
52 | We prepared a [wiki page](https://github.com/onevcat/Kingfisher/wiki). You can find tons of useful things there.
53 |
54 | * [Installation Guide](https://github.com/onevcat/Kingfisher/wiki/Installation-Guide) - Follow it to integrate Kingfisher into your project.
55 | * [Cheat Sheet](https://github.com/onevcat/Kingfisher/wiki/Cheat-Sheet)- Curious about what Kingfisher could do and how would it look like when used in your project? See this page for useful code snippets. If you are already familiar with Kingfisher, you could also learn new tricks to improve the way you use Kingfisher!
56 | * [API Reference](http://onevcat.github.io/Kingfisher/) - Lastly, please remember to read the full whenever you may need a more detailed reference.
57 |
58 | ## Other
59 |
60 | ### Future of Kingfisher
61 |
62 | I want to keep Kingfisher lightweight. This framework will focus on providing a simple solution for downloading and caching images. This doesn’t mean the framework can’t be improved. Kingfisher is far from perfect, so necessary and useful updates will be made to make it better.
63 |
64 | ### Developments and Tests
65 |
66 | Any contributing and pull requests are warmly welcome. However, before you plan to implement some features or try to fix an uncertain issue, it is recommended to open a discussion first.
67 |
68 | The test images are contained in another project to keep this project repo fast and slim. You could run `./setup.sh` in the root folder of Kingfisher to clone the test images when you need to run the tests target. It would be appreciated if your pull requests could build and with all tests green. :)
69 |
70 | ### About the logo
71 |
72 | The logo of Kingfisher is inspired by [Tangram (七巧板)](http://en.wikipedia.org/wiki/Tangram), a dissection puzzle consisting of seven flat shapes from China. I believe she's a kingfisher bird instead of a swift, but someone insists that she is a pigeon. I guess I should give her a name. Hi, guys, do you have any suggestions?
73 |
74 | ### Contact
75 |
76 | Follow and contact me on [Twitter](http://twitter.com/onevcat) or [Sina Weibo](http://weibo.com/onevcat). If you find an issue, just [open a ticket](https://github.com/onevcat/Kingfisher/issues/new). Pull requests are warmly welcome as well.
77 |
78 | ### License
79 |
80 | Kingfisher is released under the MIT license. See LICENSE for details.
81 |
82 |
83 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/AnimatedImageView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimatableImageView.swift
3 | // Kingfisher
4 | //
5 | // Created by bl4ckra1sond3tre on 4/22/16.
6 | //
7 | // The AnimatableImageView, AnimatedFrame and Animator is a modified version of
8 | // some classes from kaishin's Gifu project (https://github.com/kaishin/Gifu)
9 | //
10 | // The MIT License (MIT)
11 | //
12 | // Copyright (c) 2017 Reda Lemeden.
13 | //
14 | // Permission is hereby granted, free of charge, to any person obtaining a copy of
15 | // this software and associated documentation files (the "Software"), to deal in
16 | // the Software without restriction, including without limitation the rights to
17 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
18 | // the Software, and to permit persons to whom the Software is furnished to do so,
19 | // subject to the following conditions:
20 | //
21 | // The above copyright notice and this permission notice shall be included in all
22 | // copies or substantial portions of the Software.
23 | //
24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
26 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
27 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
28 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 | //
31 | // The name and characters used in the demo of this software are property of their
32 | // respective owners.
33 |
34 | import UIKit
35 | import ImageIO
36 |
37 | /// `AnimatedImageView` is a subclass of `UIImageView` for displaying animated image.
38 | open class AnimatedImageView: UIImageView {
39 |
40 | /// Proxy object for prevending a reference cycle between the CADDisplayLink and AnimatedImageView.
41 | class TargetProxy {
42 | private weak var target: AnimatedImageView?
43 |
44 | init(target: AnimatedImageView) {
45 | self.target = target
46 | }
47 |
48 | @objc func onScreenUpdate() {
49 | target?.updateFrame()
50 | }
51 | }
52 |
53 | // MARK: - Public property
54 | /// Whether automatically play the animation when the view become visible. Default is true.
55 | public var autoPlayAnimatedImage = true
56 |
57 | /// The size of the frame cache.
58 | public var framePreloadCount = 10
59 |
60 | /// Specifies whether the GIF frames should be pre-scaled to save memory. Default is true.
61 | public var needsPrescaling = true
62 |
63 | /// The animation timer's run loop mode. Default is `NSRunLoopCommonModes`. Set this property to `NSDefaultRunLoopMode` will make the animation pause during UIScrollView scrolling.
64 | public var runLoopMode = RunLoopMode.commonModes {
65 | willSet {
66 | if runLoopMode == newValue {
67 | return
68 | } else {
69 | stopAnimating()
70 | displayLink.remove(from: .main, forMode: runLoopMode)
71 | displayLink.add(to: .main, forMode: newValue)
72 | startAnimating()
73 | }
74 | }
75 | }
76 |
77 | // MARK: - Private property
78 | /// `Animator` instance that holds the frames of a specific image in memory.
79 | private var animator: Animator?
80 |
81 | /// A flag to avoid invalidating the displayLink on deinit if it was never created, because displayLink is so lazy. :D
82 | private var isDisplayLinkInitialized: Bool = false
83 |
84 | /// A display link that keeps calling the `updateFrame` method on every screen refresh.
85 | private lazy var displayLink: CADisplayLink = {
86 | self.isDisplayLinkInitialized = true
87 | let displayLink = CADisplayLink(target: TargetProxy(target: self), selector: #selector(TargetProxy.onScreenUpdate))
88 | displayLink.add(to: .main, forMode: self.runLoopMode)
89 | displayLink.isPaused = true
90 | return displayLink
91 | }()
92 |
93 | // MARK: - Override
94 | override open var image: Image? {
95 | didSet {
96 | if image != oldValue {
97 | reset()
98 | }
99 | setNeedsDisplay()
100 | layer.setNeedsDisplay()
101 | }
102 | }
103 |
104 | deinit {
105 | if isDisplayLinkInitialized {
106 | displayLink.invalidate()
107 | }
108 | }
109 |
110 | override open var isAnimating: Bool {
111 | if isDisplayLinkInitialized {
112 | return !displayLink.isPaused
113 | } else {
114 | return super.isAnimating
115 | }
116 | }
117 |
118 | /// Starts the animation.
119 | override open func startAnimating() {
120 | if self.isAnimating {
121 | return
122 | } else {
123 | displayLink.isPaused = false
124 | }
125 | }
126 |
127 | /// Stops the animation.
128 | override open func stopAnimating() {
129 | super.stopAnimating()
130 | if isDisplayLinkInitialized {
131 | displayLink.isPaused = true
132 | }
133 | }
134 |
135 | override open func display(_ layer: CALayer) {
136 | if let currentFrame = animator?.currentFrame {
137 | layer.contents = currentFrame.cgImage
138 | } else {
139 | layer.contents = image?.cgImage
140 | }
141 | }
142 |
143 | override open func didMoveToWindow() {
144 | super.didMoveToWindow()
145 | didMove()
146 | }
147 |
148 | override open func didMoveToSuperview() {
149 | super.didMoveToSuperview()
150 | didMove()
151 | }
152 |
153 | // This is for back compatibility that using regular UIImageView to show animated image.
154 | override func shouldPreloadAllAnimation() -> Bool {
155 | return false
156 | }
157 |
158 | // MARK: - Private method
159 | /// Reset the animator.
160 | private func reset() {
161 | animator = nil
162 | if let imageSource = image?.kf.imageSource?.imageRef {
163 | animator = Animator(imageSource: imageSource, contentMode: contentMode, size: bounds.size, framePreloadCount: framePreloadCount)
164 | animator?.needsPrescaling = needsPrescaling
165 | animator?.prepareFramesAsynchronously()
166 | }
167 | didMove()
168 | }
169 |
170 | private func didMove() {
171 | if autoPlayAnimatedImage && animator != nil {
172 | if let _ = superview, let _ = window {
173 | startAnimating()
174 | } else {
175 | stopAnimating()
176 | }
177 | }
178 | }
179 |
180 | /// Update the current frame with the displayLink duration.
181 | private func updateFrame() {
182 | if animator?.updateCurrentFrame(duration: displayLink.duration) ?? false {
183 | layer.setNeedsDisplay()
184 | }
185 | }
186 | }
187 |
188 | /// Keeps a reference to an `Image` instance and its duration as a GIF frame.
189 | struct AnimatedFrame {
190 | var image: Image?
191 | let duration: TimeInterval
192 |
193 | static let null: AnimatedFrame = AnimatedFrame(image: .none, duration: 0.0)
194 | }
195 |
196 | // MARK: - Animator
197 | class Animator {
198 | // MARK: Private property
199 | fileprivate let size: CGSize
200 | fileprivate let maxFrameCount: Int
201 | fileprivate let imageSource: CGImageSource
202 |
203 | fileprivate var animatedFrames = [AnimatedFrame]()
204 | fileprivate let maxTimeStep: TimeInterval = 1.0
205 | fileprivate var frameCount = 0
206 | fileprivate var currentFrameIndex = 0
207 | fileprivate var currentPreloadIndex = 0
208 | fileprivate var timeSinceLastFrameChange: TimeInterval = 0.0
209 | fileprivate var needsPrescaling = true
210 |
211 | /// Loop count of animatd image.
212 | private var loopCount = 0
213 |
214 | var currentFrame: UIImage? {
215 | return frame(at: currentFrameIndex)
216 | }
217 |
218 | var contentMode = UIViewContentMode.scaleToFill
219 |
220 | private lazy var preloadQueue: DispatchQueue = {
221 | return DispatchQueue(label: "com.onevcat.Kingfisher.Animator.preloadQueue")
222 | }()
223 |
224 | /**
225 | Init an animator with image source reference.
226 |
227 | - parameter imageSource: The reference of animated image.
228 | - parameter contentMode: Content mode of AnimatedImageView.
229 | - parameter size: Size of AnimatedImageView.
230 | - parameter framePreloadCount: Frame cache size.
231 |
232 | - returns: The animator object.
233 | */
234 | init(imageSource source: CGImageSource, contentMode mode: UIViewContentMode, size: CGSize, framePreloadCount count: Int) {
235 | self.imageSource = source
236 | self.contentMode = mode
237 | self.size = size
238 | self.maxFrameCount = count
239 | }
240 |
241 | func frame(at index: Int) -> Image? {
242 | return animatedFrames[safe: index]?.image
243 | }
244 |
245 | func prepareFramesAsynchronously() {
246 | preloadQueue.async { [weak self] in
247 | self?.prepareFrames()
248 | }
249 | }
250 |
251 | private func prepareFrames() {
252 | frameCount = CGImageSourceGetCount(imageSource)
253 |
254 | if let properties = CGImageSourceCopyProperties(imageSource, nil),
255 | let gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary,
256 | let loopCount = gifInfo[kCGImagePropertyGIFLoopCount as String] as? Int
257 | {
258 | self.loopCount = loopCount
259 | }
260 |
261 | let frameToProcess = min(frameCount, maxFrameCount)
262 | animatedFrames.reserveCapacity(frameToProcess)
263 | animatedFrames = (0.. AnimatedFrame {
268 |
269 | guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, index, nil) else {
270 | return AnimatedFrame.null
271 | }
272 |
273 | let defaultGIFFrameDuration = 0.100
274 | let frameDuration = imageSource.kf.gifProperties(at: index).map {
275 | gifInfo -> Double in
276 |
277 | let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as Double?
278 | let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as Double?
279 | let duration = unclampedDelayTime ?? delayTime ?? 0.0
280 |
281 | /**
282 | http://opensource.apple.com/source/WebCore/WebCore-7600.1.25/platform/graphics/cg/ImageSourceCG.cpp
283 | Many annoying ads specify a 0 duration to make an image flash as quickly as
284 | possible. We follow Safari and Firefox's behavior and use a duration of 100 ms
285 | for any frames that specify a duration of <= 10 ms.
286 | See and for more information.
287 |
288 | See also: http://nullsleep.tumblr.com/post/16524517190/animated-gif-minimum-frame-delay-browser.
289 | */
290 | return duration > 0.011 ? duration : defaultGIFFrameDuration
291 | } ?? defaultGIFFrameDuration
292 |
293 | let image = Image(cgImage: imageRef)
294 | let scaledImage: Image?
295 |
296 | if needsPrescaling {
297 | scaledImage = image.kf.resize(to: size, for: contentMode)
298 | } else {
299 | scaledImage = image
300 | }
301 |
302 | return AnimatedFrame(image: scaledImage, duration: frameDuration)
303 | }
304 |
305 | /**
306 | Updates the current frame if necessary using the frame timer and the duration of each frame in `animatedFrames`.
307 | */
308 | func updateCurrentFrame(duration: CFTimeInterval) -> Bool {
309 | timeSinceLastFrameChange += min(maxTimeStep, duration)
310 | guard let frameDuration = animatedFrames[safe: currentFrameIndex]?.duration, frameDuration <= timeSinceLastFrameChange else {
311 | return false
312 | }
313 |
314 | timeSinceLastFrameChange -= frameDuration
315 |
316 | let lastFrameIndex = currentFrameIndex
317 | currentFrameIndex += 1
318 | currentFrameIndex = currentFrameIndex % animatedFrames.count
319 |
320 | if animatedFrames.count < frameCount {
321 | preloadFrameAsynchronously(at: lastFrameIndex)
322 | }
323 | return true
324 | }
325 |
326 | private func preloadFrameAsynchronously(at index: Int) {
327 | preloadQueue.async { [weak self] in
328 | self?.preloadFrame(at: index)
329 | }
330 | }
331 |
332 | private func preloadFrame(at index: Int) {
333 | animatedFrames[index] = prepareFrame(at: currentPreloadIndex)
334 | currentPreloadIndex += 1
335 | currentPreloadIndex = currentPreloadIndex % frameCount
336 | }
337 | }
338 |
339 | extension CGImageSource: KingfisherCompatible { }
340 | extension Kingfisher where Base: CGImageSource {
341 | func gifProperties(at index: Int) -> [String: Double]? {
342 | let properties = CGImageSourceCopyPropertiesAtIndex(base, index, nil) as Dictionary?
343 | return properties?[kCGImagePropertyGIFDictionary] as? [String: Double]
344 | }
345 | }
346 |
347 | extension Array {
348 | subscript(safe index: Int) -> Element? {
349 | return indices ~= index ? self[index] : nil
350 | }
351 | }
352 |
353 | private func pure(_ value: T) -> [T] {
354 | return [value]
355 | }
356 |
357 | // MARK: - Deprecated. Only for back compatibility.
358 | extension AnimatedImageView {
359 | // This is for back compatibility that using regular UIImageView to show GIF.
360 | @available(*, deprecated, renamed: "shouldPreloadAllAnimation")
361 | override func shouldPreloadAllGIF() -> Bool {
362 | return false
363 | }
364 | }
365 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/Box.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Box.swift
3 | // Kingfisher
4 | //
5 | // Created by WANG WEI on 2016/09/12.
6 | // Copyright © 2016年 Wei Wang. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Box {
12 | let value: T
13 | init(value: T) {
14 | self.value = value
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/CacheSerializer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CacheSerializer.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2016/09/02.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// An `CacheSerializer` would be used to convert some data to an image object for
30 | /// retrieving from disk cache and vice versa for storing to disk cache.
31 | public protocol CacheSerializer {
32 |
33 | /// Get the serialized data from a provided image
34 | /// and optional original data for caching to disk.
35 | ///
36 | ///
37 | /// - parameter image: The image needed to be serialized.
38 | /// - parameter original: The original data which is just downloaded.
39 | /// If the image is retrieved from cache instead of
40 | /// downloaded, it will be `nil`.
41 | ///
42 | /// - returns: A data which will be stored to cache, or `nil` when no valid
43 | /// data could be serialized.
44 | func data(with image: Image, original: Data?) -> Data?
45 |
46 | /// Get an image deserialized from provided data.
47 | ///
48 | /// - parameter data: The data from which an image should be deserialized.
49 | /// - parameter options: Options for deserialization.
50 | ///
51 | /// - returns: An image deserialized or `nil` when no valid image
52 | /// could be deserialized.
53 | func image(with data: Data, options: KingfisherOptionsInfo?) -> Image?
54 | }
55 |
56 |
57 | /// `DefaultCacheSerializer` is a basic `CacheSerializer` used in default cache of
58 | /// Kingfisher. It could serialize and deserialize PNG, JEPG and GIF images. For
59 | /// image other than these formats, a normalized `pngRepresentation` will be used.
60 | public struct DefaultCacheSerializer: CacheSerializer {
61 |
62 | public static let `default` = DefaultCacheSerializer()
63 | private init() {}
64 |
65 | public func data(with image: Image, original: Data?) -> Data? {
66 | let imageFormat = original?.kf.imageFormat ?? .unknown
67 |
68 | let data: Data?
69 | switch imageFormat {
70 | case .PNG: data = image.kf.pngRepresentation()
71 | case .JPEG: data = image.kf.jpegRepresentation(compressionQuality: 1.0)
72 | case .GIF: data = image.kf.gifRepresentation()
73 | case .unknown: data = original ?? image.kf.normalized.kf.pngRepresentation()
74 | }
75 |
76 | return data
77 | }
78 |
79 | public func image(with data: Data, options: KingfisherOptionsInfo?) -> Image? {
80 | let options = options ?? KingfisherEmptyOptionsInfo
81 | return Kingfisher.image(
82 | data: data,
83 | scale: options.scaleFactor,
84 | preloadAllAnimationData: options.preloadAllAnimationData,
85 | onlyFirstFrame: options.onlyLoadFirstFrame)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/Filter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Filter.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2016/08/31.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 |
28 |
29 | import CoreImage
30 | import Accelerate
31 |
32 | // Reuse the same CI Context for all CI drawing.
33 | private let ciContext = CIContext(options: nil)
34 |
35 | /// Transformer method which will be used in to provide a `Filter`.
36 | public typealias Transformer = (CIImage) -> CIImage?
37 |
38 | /// Supply a filter to create an `ImageProcessor`.
39 | public protocol CIImageProcessor: ImageProcessor {
40 | var filter: Filter { get }
41 | }
42 |
43 | extension CIImageProcessor {
44 | public func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? {
45 | switch item {
46 | case .image(let image):
47 | return image.kf.apply(filter)
48 | case .data(_):
49 | return (DefaultImageProcessor.default >> self).process(item: item, options: options)
50 | }
51 | }
52 | }
53 |
54 | /// Wrapper for a `Transformer` of CIImage filters.
55 | public struct Filter {
56 |
57 | let transform: Transformer
58 |
59 | public init(tranform: @escaping Transformer) {
60 | self.transform = tranform
61 | }
62 |
63 | /// Tint filter which will apply a tint color to images.
64 | public static var tint: (Color) -> Filter = {
65 | color in
66 | Filter { input in
67 | let colorFilter = CIFilter(name: "CIConstantColorGenerator")!
68 | colorFilter.setValue(CIColor(color: color), forKey: kCIInputColorKey)
69 |
70 | let colorImage = colorFilter.outputImage
71 | let filter = CIFilter(name: "CISourceOverCompositing")!
72 | filter.setValue(colorImage, forKey: kCIInputImageKey)
73 | filter.setValue(input, forKey: kCIInputBackgroundImageKey)
74 | return filter.outputImage?.cropping(to: input.extent)
75 | }
76 | }
77 |
78 | public typealias ColorElement = (CGFloat, CGFloat, CGFloat, CGFloat)
79 |
80 | /// Color control filter which will apply color control change to images.
81 | public static var colorControl: (ColorElement) -> Filter = {
82 | brightness, contrast, saturation, inputEV in
83 | Filter { input in
84 | let paramsColor = [kCIInputBrightnessKey: brightness,
85 | kCIInputContrastKey: contrast,
86 | kCIInputSaturationKey: saturation]
87 |
88 | let blackAndWhite = input.applyingFilter("CIColorControls", withInputParameters: paramsColor)
89 | let paramsExposure = [kCIInputEVKey: inputEV]
90 | return blackAndWhite.applyingFilter("CIExposureAdjust", withInputParameters: paramsExposure)
91 | }
92 |
93 | }
94 | }
95 |
96 | extension Kingfisher where Base: Image {
97 | /// Apply a `Filter` containing `CIImage` transformer to `self`.
98 | ///
99 | /// - parameter filter: The filter used to transform `self`.
100 | ///
101 | /// - returns: A transformed image by input `Filter`.
102 | ///
103 | /// - Note: Only CG-based images are supported. If any error happens during transforming, `self` will be returned.
104 | public func apply(_ filter: Filter) -> Image {
105 |
106 | guard let cgImage = cgImage else {
107 | assertionFailure("[Kingfisher] Tint image only works for CG-based image.")
108 | return base
109 | }
110 |
111 | let inputImage = CIImage(cgImage: cgImage)
112 | guard let outputImage = filter.transform(inputImage) else {
113 | return base
114 | }
115 |
116 | guard let result = ciContext.createCGImage(outputImage, from: outputImage.extent) else {
117 | assertionFailure("[Kingfisher] Can not make an tint image within context.")
118 | return base
119 | }
120 |
121 | #if os(macOS)
122 | return fixedForRetinaPixel(cgImage: result, to: size)
123 | #else
124 | return Image(cgImage: result, scale: base.scale, orientation: base.imageOrientation)
125 | #endif
126 | }
127 |
128 | }
129 |
130 | public extension Image {
131 |
132 | /// Apply a `Filter` containing `CIImage` transformer to `self`.
133 | ///
134 | /// - parameter filter: The filter used to transform `self`.
135 | ///
136 | /// - returns: A transformed image by input `Filter`.
137 | ///
138 | /// - Note: Only CG-based images are supported. If any error happens during transforming, `self` will be returned.
139 | @available(*, deprecated,
140 | message: "Extensions directly on Image are deprecated. Use `kf.apply` instead.",
141 | renamed: "kf.apply")
142 | public func kf_apply(_ filter: Filter) -> Image {
143 | return kf.apply(filter)
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/FormatIndicatedCacheSerializer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestModifier.swift
3 | // Kingfisher
4 | //
5 | // Created by Junyu Kuang on 5/28/17.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// `FormatIndicatedCacheSerializer` let you indicate an image format for serialized caches.
30 | ///
31 | /// It could serialize and deserialize PNG, JEPG and GIF images. For
32 | /// image other than these formats, a normalized `pngRepresentation` will be used.
33 | ///
34 | /// Example:
35 | /// ````
36 | /// private let profileImageSize = CGSize(width: 44, height: 44)
37 | ///
38 | /// private let imageProcessor = RoundCornerImageProcessor(
39 | /// cornerRadius: profileImageSize.width / 2, targetSize: profileImageSize)
40 | ///
41 | /// private let optionsInfo: KingfisherOptionsInfo = [
42 | /// .cacheSerializer(FormatIndicatedCacheSerializer.png),
43 | /// .backgroundDecode, .processor(imageProcessor), .scaleFactor(UIScreen.main.scale)]
44 | ///
45 | /// extension UIImageView {
46 | /// func setProfileImage(with url: URL) {
47 | /// // Image will always cached as PNG format to preserve alpha channel for round rect.
48 | /// _ = kf.setImage(with: url, options: optionsInfo)
49 | /// }
50 | ///}
51 | /// ````
52 | public struct FormatIndicatedCacheSerializer: CacheSerializer {
53 |
54 | public static let png = FormatIndicatedCacheSerializer(imageFormat: .PNG)
55 | public static let jpeg = FormatIndicatedCacheSerializer(imageFormat: .JPEG)
56 | public static let gif = FormatIndicatedCacheSerializer(imageFormat: .GIF)
57 |
58 | /// The indicated image format.
59 | private let imageFormat: ImageFormat
60 |
61 | public func data(with image: Image, original: Data?) -> Data? {
62 |
63 | func imageData(withFormat imageFormat: ImageFormat) -> Data? {
64 | switch imageFormat {
65 | case .PNG: return image.kf.pngRepresentation()
66 | case .JPEG: return image.kf.jpegRepresentation(compressionQuality: 1.0)
67 | case .GIF: return image.kf.gifRepresentation()
68 | case .unknown: return nil
69 | }
70 | }
71 |
72 | // generate data with indicated image format
73 | if let data = imageData(withFormat: imageFormat) {
74 | return data
75 | }
76 |
77 | let originalFormat = original?.kf.imageFormat ?? .unknown
78 |
79 | // generate data with original image's format
80 | if originalFormat != imageFormat, let data = imageData(withFormat: originalFormat) {
81 | return data
82 | }
83 |
84 | return original ?? image.kf.normalized.kf.pngRepresentation()
85 | }
86 |
87 | /// Same implementation as `DefaultCacheSerializer`.
88 | public func image(with data: Data, options: KingfisherOptionsInfo?) -> Image? {
89 | let options = options ?? KingfisherEmptyOptionsInfo
90 | return Kingfisher.image(
91 | data: data,
92 | scale: options.scaleFactor,
93 | preloadAllAnimationData: options.preloadAllAnimationData,
94 | onlyFirstFrame: options.onlyLoadFirstFrame)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/ImagePrefetcher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePrefetcher.swift
3 | // Kingfisher
4 | //
5 | // Created by Claire Knight on 24/02/2016
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 |
28 | #if os(macOS)
29 | import AppKit
30 | #else
31 | import UIKit
32 | #endif
33 |
34 |
35 | /// Progress update block of prefetcher.
36 | ///
37 | /// - `skippedResources`: An array of resources that are already cached before the prefetching starting.
38 | /// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while downloading, encountered an error when downloading or the download not being started at all.
39 | /// - `completedResources`: An array of resources that are downloaded and cached successfully.
40 | public typealias PrefetcherProgressBlock = ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> ())
41 |
42 | /// Completion block of prefetcher.
43 | ///
44 | /// - `skippedResources`: An array of resources that are already cached before the prefetching starting.
45 | /// - `failedResources`: An array of resources that fail to be downloaded. It could because of being cancelled while downloading, encountered an error when downloading or the download not being started at all.
46 | /// - `completedResources`: An array of resources that are downloaded and cached successfully.
47 | public typealias PrefetcherCompletionHandler = ((_ skippedResources: [Resource], _ failedResources: [Resource], _ completedResources: [Resource]) -> ())
48 |
49 | /// `ImagePrefetcher` represents a downloading manager for requesting many images via URLs, then caching them.
50 | /// This is useful when you know a list of image resources and want to download them before showing.
51 | public class ImagePrefetcher {
52 |
53 | /// The maximum concurrent downloads to use when prefetching images. Default is 5.
54 | public var maxConcurrentDownloads = 5
55 |
56 | private let prefetchResources: [Resource]
57 | private let optionsInfo: KingfisherOptionsInfo
58 | private var progressBlock: PrefetcherProgressBlock?
59 | private var completionHandler: PrefetcherCompletionHandler?
60 |
61 | private var tasks = [URL: RetrieveImageDownloadTask]()
62 |
63 | private var pendingResources: ArraySlice
64 | private var skippedResources = [Resource]()
65 | private var completedResources = [Resource]()
66 | private var failedResources = [Resource]()
67 |
68 | private var stopped = false
69 |
70 | // The created manager used for prefetch. We will use the helper method in manager.
71 | private let manager: KingfisherManager
72 |
73 | private var finished: Bool {
74 | return failedResources.count + skippedResources.count + completedResources.count == prefetchResources.count && self.tasks.isEmpty
75 | }
76 |
77 | /**
78 | Init an image prefetcher with an array of URLs.
79 |
80 | The prefetcher should be initiated with a list of prefetching targets. The URLs list is immutable.
81 | After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process.
82 | The images already cached will be skipped without downloading again.
83 |
84 | - parameter urls: The URLs which should be prefetched.
85 | - parameter options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
86 | - parameter progressBlock: Called every time an resource is downloaded, skipped or cancelled.
87 | - parameter completionHandler: Called when the whole prefetching process finished.
88 |
89 | - returns: An `ImagePrefetcher` object.
90 |
91 | - Note: By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
92 | the downloader and cache target respectively. You can specify another downloader or cache by using a customized `KingfisherOptionsInfo`.
93 | Both the progress and completion block will be invoked in main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method.
94 | */
95 | public convenience init(urls: [URL],
96 | options: KingfisherOptionsInfo? = nil,
97 | progressBlock: PrefetcherProgressBlock? = nil,
98 | completionHandler: PrefetcherCompletionHandler? = nil)
99 | {
100 | let resources: [Resource] = urls.map { $0 }
101 | self.init(resources: resources, options: options, progressBlock: progressBlock, completionHandler: completionHandler)
102 | }
103 |
104 | /**
105 | Init an image prefetcher with an array of resources.
106 |
107 | The prefetcher should be initiated with a list of prefetching targets. The resources list is immutable.
108 | After you get a valid `ImagePrefetcher` object, you could call `start()` on it to begin the prefetching process.
109 | The images already cached will be skipped without downloading again.
110 |
111 | - parameter resources: The resources which should be prefetched. See `Resource` type for more.
112 | - parameter options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
113 | - parameter progressBlock: Called every time an resource is downloaded, skipped or cancelled.
114 | - parameter completionHandler: Called when the whole prefetching process finished.
115 |
116 | - returns: An `ImagePrefetcher` object.
117 |
118 | - Note: By default, the `ImageDownloader.defaultDownloader` and `ImageCache.defaultCache` will be used as
119 | the downloader and cache target respectively. You can specify another downloader or cache by using a customized `KingfisherOptionsInfo`.
120 | Both the progress and completion block will be invoked in main thread. The `CallbackDispatchQueue` in `optionsInfo` will be ignored in this method.
121 | */
122 | public init(resources: [Resource],
123 | options: KingfisherOptionsInfo? = nil,
124 | progressBlock: PrefetcherProgressBlock? = nil,
125 | completionHandler: PrefetcherCompletionHandler? = nil)
126 | {
127 | prefetchResources = resources
128 | pendingResources = ArraySlice(resources)
129 |
130 | // We want all callbacks from main queue, so we ignore the call back queue in options
131 | let optionsInfoWithoutQueue = options?.removeAllMatchesIgnoringAssociatedValue(.callbackDispatchQueue(nil))
132 | self.optionsInfo = optionsInfoWithoutQueue ?? KingfisherEmptyOptionsInfo
133 |
134 | let cache = self.optionsInfo.targetCache
135 | let downloader = self.optionsInfo.downloader
136 | manager = KingfisherManager(downloader: downloader, cache: cache)
137 |
138 | self.progressBlock = progressBlock
139 | self.completionHandler = completionHandler
140 | }
141 |
142 | /**
143 | Start to download the resources and cache them. This can be useful for background downloading
144 | of assets that are required for later use in an app. This code will not try and update any UI
145 | with the results of the process.
146 | */
147 | public func start()
148 | {
149 | // Since we want to handle the resources cancellation in main thread only.
150 | DispatchQueue.main.safeAsync {
151 |
152 | guard !self.stopped else {
153 | assertionFailure("You can not restart the same prefetcher. Try to create a new prefetcher.")
154 | self.handleComplete()
155 | return
156 | }
157 |
158 | guard self.maxConcurrentDownloads > 0 else {
159 | assertionFailure("There should be concurrent downloads value should be at least 1.")
160 | self.handleComplete()
161 | return
162 | }
163 |
164 | guard self.prefetchResources.count > 0 else {
165 | self.handleComplete()
166 | return
167 | }
168 |
169 | let initialConcurentDownloads = min(self.prefetchResources.count, self.maxConcurrentDownloads)
170 | for _ in 0 ..< initialConcurentDownloads {
171 | if let resource = self.pendingResources.popFirst() {
172 | self.startPrefetching(resource)
173 | }
174 | }
175 | }
176 | }
177 |
178 |
179 | /**
180 | Stop current downloading progress, and cancel any future prefetching activity that might be occuring.
181 | */
182 | public func stop() {
183 | DispatchQueue.main.safeAsync {
184 |
185 | if self.finished { return }
186 |
187 | self.stopped = true
188 | self.tasks.forEach { (_, task) -> () in
189 | task.cancel()
190 | }
191 | }
192 | }
193 |
194 | func downloadAndCache(_ resource: Resource) {
195 |
196 | let downloadTaskCompletionHandler: CompletionHandler = { (image, error, _, _) -> () in
197 | self.tasks.removeValue(forKey: resource.downloadURL)
198 | if let _ = error {
199 | self.failedResources.append(resource)
200 | } else {
201 | self.completedResources.append(resource)
202 | }
203 |
204 | self.reportProgress()
205 | if self.stopped {
206 | if self.tasks.isEmpty {
207 | self.failedResources.append(contentsOf: self.pendingResources)
208 | self.handleComplete()
209 | }
210 | } else {
211 | self.reportCompletionOrStartNext()
212 | }
213 | }
214 |
215 | let downloadTask = manager.downloadAndCacheImage(
216 | with: resource.downloadURL,
217 | forKey: resource.cacheKey,
218 | retrieveImageTask: RetrieveImageTask(),
219 | progressBlock: nil,
220 | completionHandler: downloadTaskCompletionHandler,
221 | options: optionsInfo)
222 |
223 | if let downloadTask = downloadTask {
224 | tasks[resource.downloadURL] = downloadTask
225 | }
226 | }
227 |
228 | func append(cached resource: Resource) {
229 | skippedResources.append(resource)
230 |
231 | reportProgress()
232 | reportCompletionOrStartNext()
233 | }
234 |
235 | func startPrefetching(_ resource: Resource)
236 | {
237 | if optionsInfo.forceRefresh {
238 | downloadAndCache(resource)
239 | } else {
240 | let alreadyInCache = manager.cache.isImageCached(forKey: resource.cacheKey,
241 | processorIdentifier: optionsInfo.processor.identifier).cached
242 |
243 | if alreadyInCache {
244 | append(cached: resource)
245 | } else {
246 | downloadAndCache(resource)
247 | }
248 | }
249 | }
250 |
251 | func reportProgress() {
252 | progressBlock?(skippedResources, failedResources, completedResources)
253 | }
254 |
255 | func reportCompletionOrStartNext() {
256 | if let resource = pendingResources.popFirst() {
257 | startPrefetching(resource)
258 | } else {
259 | guard tasks.isEmpty else { return }
260 | handleComplete()
261 | }
262 | }
263 |
264 | func handleComplete() {
265 | completionHandler?(skippedResources, failedResources, completedResources)
266 | completionHandler = nil
267 | progressBlock = nil
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/ImageTransition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageTransition.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 15/9/18.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if os(macOS)
28 | // Not implemented for macOS and watchOS yet.
29 |
30 | import AppKit
31 |
32 | /// Image transition is not supported on macOS.
33 | public enum ImageTransition {
34 | case none
35 | var duration: TimeInterval {
36 | return 0
37 | }
38 | }
39 |
40 | #elseif os(watchOS)
41 | import UIKit
42 | /// Image transition is not supported on watchOS.
43 | public enum ImageTransition {
44 | case none
45 | var duration: TimeInterval {
46 | return 0
47 | }
48 | }
49 | #else
50 | import UIKit
51 |
52 | /**
53 | Transition effect which will be used when an image downloaded and set by `UIImageView` extension API in Kingfisher.
54 | You can assign an enum value with transition duration as an item in `KingfisherOptionsInfo`
55 | to enable the animation transition.
56 |
57 | Apple's UIViewAnimationOptions is used under the hood.
58 | For custom transition, you should specified your own transition options, animations and
59 | comletion handler as well.
60 | */
61 | public enum ImageTransition {
62 | /// No animation transistion.
63 | case none
64 |
65 | /// Fade in the loaded image.
66 | case fade(TimeInterval)
67 |
68 | /// Flip from left transition.
69 | case flipFromLeft(TimeInterval)
70 |
71 | /// Flip from right transition.
72 | case flipFromRight(TimeInterval)
73 |
74 | /// Flip from top transition.
75 | case flipFromTop(TimeInterval)
76 |
77 | /// Flip from bottom transition.
78 | case flipFromBottom(TimeInterval)
79 |
80 | /// Custom transition.
81 | case custom(duration: TimeInterval,
82 | options: UIViewAnimationOptions,
83 | animations: ((UIImageView, UIImage) -> Void)?,
84 | completion: ((Bool) -> Void)?)
85 |
86 | var duration: TimeInterval {
87 | switch self {
88 | case .none: return 0
89 | case .fade(let duration): return duration
90 |
91 | case .flipFromLeft(let duration): return duration
92 | case .flipFromRight(let duration): return duration
93 | case .flipFromTop(let duration): return duration
94 | case .flipFromBottom(let duration): return duration
95 |
96 | case .custom(let duration, _, _, _): return duration
97 | }
98 | }
99 |
100 | var animationOptions: UIViewAnimationOptions {
101 | switch self {
102 | case .none: return []
103 | case .fade(_): return .transitionCrossDissolve
104 |
105 | case .flipFromLeft(_): return .transitionFlipFromLeft
106 | case .flipFromRight(_): return .transitionFlipFromRight
107 | case .flipFromTop(_): return .transitionFlipFromTop
108 | case .flipFromBottom(_): return .transitionFlipFromBottom
109 |
110 | case .custom(_, let options, _, _): return options
111 | }
112 | }
113 |
114 | var animations: ((UIImageView, UIImage) -> Void)? {
115 | switch self {
116 | case .custom(_, _, let animations, _): return animations
117 | default: return { $0.image = $1 }
118 | }
119 | }
120 |
121 | var completion: ((Bool) -> Void)? {
122 | switch self {
123 | case .custom(_, _, _, let completion): return completion
124 | default: return nil
125 | }
126 | }
127 | }
128 | #endif
129 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/Indicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Indicator.swift
3 | // Kingfisher
4 | //
5 | // Created by João D. Moreira on 30/08/16.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if os(macOS)
28 | import AppKit
29 | #else
30 | import UIKit
31 | #endif
32 |
33 | #if os(macOS)
34 | public typealias IndicatorView = NSView
35 | #else
36 | public typealias IndicatorView = UIView
37 | #endif
38 |
39 | public enum IndicatorType {
40 | /// No indicator.
41 | case none
42 | /// Use system activity indicator.
43 | case activity
44 | /// Use an image as indicator. GIF is supported.
45 | case image(imageData: Data)
46 | /// Use a custom indicator, which conforms to the `Indicator` protocol.
47 | case custom(indicator: Indicator)
48 | }
49 |
50 | // MARK: - Indicator Protocol
51 | public protocol Indicator {
52 | func startAnimatingView()
53 | func stopAnimatingView()
54 |
55 | var viewCenter: CGPoint { get set }
56 | var view: IndicatorView { get }
57 | }
58 |
59 | extension Indicator {
60 | #if os(macOS)
61 | public var viewCenter: CGPoint {
62 | get {
63 | let frame = view.frame
64 | return CGPoint(x: frame.origin.x + frame.size.width / 2.0, y: frame.origin.y + frame.size.height / 2.0 )
65 | }
66 | set {
67 | let frame = view.frame
68 | let newFrame = CGRect(x: newValue.x - frame.size.width / 2.0,
69 | y: newValue.y - frame.size.height / 2.0,
70 | width: frame.size.width,
71 | height: frame.size.height)
72 | view.frame = newFrame
73 | }
74 | }
75 | #else
76 | public var viewCenter: CGPoint {
77 | get {
78 | return view.center
79 | }
80 | set {
81 | view.center = newValue
82 | }
83 | }
84 | #endif
85 | }
86 |
87 | // MARK: - ActivityIndicator
88 | // Displays a NSProgressIndicator / UIActivityIndicatorView
89 | class ActivityIndicator: Indicator {
90 |
91 | #if os(macOS)
92 | private let activityIndicatorView: NSProgressIndicator
93 | #else
94 | private let activityIndicatorView: UIActivityIndicatorView
95 | #endif
96 | private var animatingCount = 0
97 |
98 | var view: IndicatorView {
99 | return activityIndicatorView
100 | }
101 |
102 | func startAnimatingView() {
103 | animatingCount += 1
104 | // Alrady animating
105 | if animatingCount == 1 {
106 | #if os(macOS)
107 | activityIndicatorView.startAnimation(nil)
108 | #else
109 | activityIndicatorView.startAnimating()
110 | #endif
111 | activityIndicatorView.isHidden = false
112 | }
113 | }
114 |
115 | func stopAnimatingView() {
116 | animatingCount = max(animatingCount - 1, 0)
117 | if animatingCount == 0 {
118 | #if os(macOS)
119 | activityIndicatorView.stopAnimation(nil)
120 | #else
121 | activityIndicatorView.stopAnimating()
122 | #endif
123 | activityIndicatorView.isHidden = true
124 | }
125 | }
126 |
127 | init() {
128 | #if os(macOS)
129 | activityIndicatorView = NSProgressIndicator(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
130 | activityIndicatorView.controlSize = .small
131 | activityIndicatorView.style = .spinningStyle
132 | #else
133 | #if os(tvOS)
134 | let indicatorStyle = UIActivityIndicatorViewStyle.white
135 | #else
136 | let indicatorStyle = UIActivityIndicatorViewStyle.gray
137 | #endif
138 | activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle:indicatorStyle)
139 | activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleBottomMargin, .flexibleTopMargin]
140 | #endif
141 | }
142 | }
143 |
144 | // MARK: - ImageIndicator
145 | // Displays an ImageView. Supports gif
146 | class ImageIndicator: Indicator {
147 | private let animatedImageIndicatorView: ImageView
148 |
149 | var view: IndicatorView {
150 | return animatedImageIndicatorView
151 | }
152 |
153 | init?(imageData data: Data, processor: ImageProcessor = DefaultImageProcessor.default, options: KingfisherOptionsInfo = KingfisherEmptyOptionsInfo) {
154 |
155 | var options = options
156 | // Use normal image view to show animations, so we need to preload all animation data.
157 | if !options.preloadAllAnimationData {
158 | options.append(.preloadAllAnimationData)
159 | }
160 |
161 | guard let image = processor.process(item: .data(data), options: options) else {
162 | return nil
163 | }
164 |
165 | animatedImageIndicatorView = ImageView()
166 | animatedImageIndicatorView.image = image
167 |
168 | #if os(macOS)
169 | // Need for gif to animate on macOS
170 | self.animatedImageIndicatorView.imageScaling = .scaleNone
171 | self.animatedImageIndicatorView.canDrawSubviewsIntoLayer = true
172 | #else
173 | animatedImageIndicatorView.contentMode = .center
174 |
175 | animatedImageIndicatorView.autoresizingMask = [.flexibleLeftMargin,
176 | .flexibleRightMargin,
177 | .flexibleBottomMargin,
178 | .flexibleTopMargin]
179 | #endif
180 | }
181 |
182 | func startAnimatingView() {
183 | #if os(macOS)
184 | animatedImageIndicatorView.animates = true
185 | #else
186 | animatedImageIndicatorView.startAnimating()
187 | #endif
188 | animatedImageIndicatorView.isHidden = false
189 | }
190 |
191 | func stopAnimatingView() {
192 | #if os(macOS)
193 | animatedImageIndicatorView.animates = false
194 | #else
195 | animatedImageIndicatorView.stopAnimating()
196 | #endif
197 | animatedImageIndicatorView.isHidden = true
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/Kingfisher.h:
--------------------------------------------------------------------------------
1 | //
2 | // Kingfisher.h
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 15/4/6.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #import
28 |
29 | //! Project version number for Kingfisher.
30 | FOUNDATION_EXPORT double KingfisherVersionNumber;
31 |
32 | //! Project version string for Kingfisher.
33 | FOUNDATION_EXPORT const unsigned char KingfisherVersionString[];
34 |
35 | // In this header, you should import all the public headers of your framework using statements like #import
36 |
37 |
38 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/Kingfisher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Kingfisher.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 16/9/14.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 | import ImageIO
29 |
30 | #if os(macOS)
31 | import AppKit
32 | public typealias Image = NSImage
33 | public typealias Color = NSColor
34 | public typealias ImageView = NSImageView
35 | typealias Button = NSButton
36 | #else
37 | import UIKit
38 | public typealias Image = UIImage
39 | public typealias Color = UIColor
40 | #if !os(watchOS)
41 | public typealias ImageView = UIImageView
42 | typealias Button = UIButton
43 | #endif
44 | #endif
45 |
46 | public final class Kingfisher {
47 | public let base: Base
48 | public init(_ base: Base) {
49 | self.base = base
50 | }
51 | }
52 |
53 | /**
54 | A type that has Kingfisher extensions.
55 | */
56 | public protocol KingfisherCompatible {
57 | associatedtype CompatibleType
58 | var kf: CompatibleType { get }
59 | }
60 |
61 | public extension KingfisherCompatible {
62 | public var kf: Kingfisher {
63 | get { return Kingfisher(self) }
64 | }
65 | }
66 |
67 | extension Image: KingfisherCompatible { }
68 | #if !os(watchOS)
69 | extension ImageView: KingfisherCompatible { }
70 | extension Button: KingfisherCompatible { }
71 | #endif
72 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/KingfisherManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KingfisherManager.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 15/4/6.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if os(macOS)
28 | import AppKit
29 | #else
30 | import UIKit
31 | #endif
32 |
33 | public typealias DownloadProgressBlock = ((_ receivedSize: Int64, _ totalSize: Int64) -> ())
34 | public typealias CompletionHandler = ((_ image: Image?, _ error: NSError?, _ cacheType: CacheType, _ imageURL: URL?) -> ())
35 |
36 | /// RetrieveImageTask represents a task of image retrieving process.
37 | /// It contains an async task of getting image from disk and from network.
38 | public class RetrieveImageTask {
39 |
40 | public static let empty = RetrieveImageTask()
41 |
42 | // If task is canceled before the download task started (which means the `downloadTask` is nil),
43 | // the download task should not begin.
44 | var cancelledBeforeDownloadStarting: Bool = false
45 |
46 | /// The disk retrieve task in this image task. Kingfisher will try to look up in cache first. This task represent the cache search task.
47 | @available(*, deprecated,
48 | message: "diskRetrieveTask is not in use anymore. You cannot cancel a disk retrieve task anymore once it started.")
49 | public var diskRetrieveTask: RetrieveImageDiskTask?
50 |
51 | /// The network retrieve task in this image task.
52 | public var downloadTask: RetrieveImageDownloadTask?
53 |
54 | /**
55 | Cancel current task. If this task is already done, do nothing.
56 | */
57 | public func cancel() {
58 | if let downloadTask = downloadTask {
59 | downloadTask.cancel()
60 | } else {
61 | cancelledBeforeDownloadStarting = true
62 | }
63 | }
64 | }
65 |
66 | /// Error domain of Kingfisher
67 | public let KingfisherErrorDomain = "com.onevcat.Kingfisher.Error"
68 |
69 | /// Main manager class of Kingfisher. It connects Kingfisher downloader and cache.
70 | /// You can use this class to retrieve an image via a specified URL from web or cache.
71 | public class KingfisherManager {
72 |
73 | /// Shared manager used by the extensions across Kingfisher.
74 | public static let shared = KingfisherManager()
75 |
76 | /// Cache used by this manager
77 | public var cache: ImageCache
78 |
79 | /// Downloader used by this manager
80 | public var downloader: ImageDownloader
81 |
82 | /// Default options used by the manager. This option will be used in
83 | /// Kingfisher manager related methods, including all image view and
84 | /// button extension methods. You can also passing the options per image by
85 | /// sending an `options` parameter to Kingfisher's APIs, the per image option
86 | /// will overwrite the default ones if exist.
87 | ///
88 | /// - Note: This option will not be applied to independent using of `ImageDownloader` or `ImageCache`.
89 | public var defaultOptions = KingfisherEmptyOptionsInfo
90 |
91 | var currentDefaultOptions: KingfisherOptionsInfo {
92 | return [.downloader(downloader), .targetCache(cache)] + defaultOptions
93 | }
94 |
95 | convenience init() {
96 | self.init(downloader: .default, cache: .default)
97 | }
98 |
99 | init(downloader: ImageDownloader, cache: ImageCache) {
100 | self.downloader = downloader
101 | self.cache = cache
102 | }
103 |
104 | /**
105 | Get an image with resource.
106 | If KingfisherOptions.None is used as `options`, Kingfisher will seek the image in memory and disk first.
107 | If not found, it will download the image at `resource.downloadURL` and cache it with `resource.cacheKey`.
108 | These default behaviors could be adjusted by passing different options. See `KingfisherOptions` for more.
109 |
110 | - parameter resource: Resource object contains information such as `cacheKey` and `downloadURL`.
111 | - parameter options: A dictionary could control some behaviors. See `KingfisherOptionsInfo` for more.
112 | - parameter progressBlock: Called every time downloaded data changed. This could be used as a progress UI.
113 | - parameter completionHandler: Called when the whole retrieving process finished.
114 |
115 | - returns: A `RetrieveImageTask` task object. You can use this object to cancel the task.
116 | */
117 | @discardableResult
118 | public func retrieveImage(with resource: Resource,
119 | options: KingfisherOptionsInfo?,
120 | progressBlock: DownloadProgressBlock?,
121 | completionHandler: CompletionHandler?) -> RetrieveImageTask
122 | {
123 | let task = RetrieveImageTask()
124 | let options = currentDefaultOptions + (options ?? KingfisherEmptyOptionsInfo)
125 | if options.forceRefresh {
126 | _ = downloadAndCacheImage(
127 | with: resource.downloadURL,
128 | forKey: resource.cacheKey,
129 | retrieveImageTask: task,
130 | progressBlock: progressBlock,
131 | completionHandler: completionHandler,
132 | options: options)
133 | } else {
134 | tryToRetrieveImageFromCache(
135 | forKey: resource.cacheKey,
136 | with: resource.downloadURL,
137 | retrieveImageTask: task,
138 | progressBlock: progressBlock,
139 | completionHandler: completionHandler,
140 | options: options)
141 | }
142 |
143 | return task
144 | }
145 |
146 | @discardableResult
147 | func downloadAndCacheImage(with url: URL,
148 | forKey key: String,
149 | retrieveImageTask: RetrieveImageTask,
150 | progressBlock: DownloadProgressBlock?,
151 | completionHandler: CompletionHandler?,
152 | options: KingfisherOptionsInfo) -> RetrieveImageDownloadTask?
153 | {
154 | let downloader = options.downloader
155 | return downloader.downloadImage(with: url, retrieveImageTask: retrieveImageTask, options: options,
156 | progressBlock: { receivedSize, totalSize in
157 | progressBlock?(receivedSize, totalSize)
158 | },
159 | completionHandler: { image, error, imageURL, originalData in
160 |
161 | let targetCache = options.targetCache
162 | if let error = error, error.code == KingfisherError.notModified.rawValue {
163 | // Not modified. Try to find the image from cache.
164 | // (The image should be in cache. It should be guaranteed by the framework users.)
165 | targetCache.retrieveImage(forKey: key, options: options, completionHandler: { (cacheImage, cacheType) -> () in
166 | completionHandler?(cacheImage, nil, cacheType, url)
167 | })
168 | return
169 | }
170 |
171 | if let image = image, let originalData = originalData {
172 | targetCache.store(image,
173 | original: originalData,
174 | forKey: key,
175 | processorIdentifier:options.processor.identifier,
176 | cacheSerializer: options.cacheSerializer,
177 | toDisk: !options.cacheMemoryOnly,
178 | completionHandler: nil)
179 | if options.cacheOriginalImage {
180 | let defaultProcessor = DefaultImageProcessor.default
181 | if let originaliImage = defaultProcessor.process(item: .data(originalData), options: options) {
182 | targetCache.store(originaliImage,
183 | original: originalData,
184 | forKey: key,
185 | processorIdentifier: defaultProcessor.identifier,
186 | cacheSerializer: options.cacheSerializer,
187 | toDisk: !options.cacheMemoryOnly,
188 | completionHandler: nil)
189 | }
190 |
191 | }
192 | }
193 |
194 | completionHandler?(image, error, .none, url)
195 |
196 | })
197 | }
198 |
199 | func tryToRetrieveImageFromCache(forKey key: String,
200 | with url: URL,
201 | retrieveImageTask: RetrieveImageTask,
202 | progressBlock: DownloadProgressBlock?,
203 | completionHandler: CompletionHandler?,
204 | options: KingfisherOptionsInfo)
205 | {
206 |
207 |
208 | let diskTaskCompletionHandler: CompletionHandler = { (image, error, cacheType, imageURL) -> () in
209 | completionHandler?(image, error, cacheType, imageURL)
210 | }
211 |
212 | func handleNoCache() {
213 | if options.onlyFromCache {
214 | let error = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notCached.rawValue, userInfo: nil)
215 | diskTaskCompletionHandler(nil, error, .none, url)
216 | return
217 | }
218 | self.downloadAndCacheImage(
219 | with: url,
220 | forKey: key,
221 | retrieveImageTask: retrieveImageTask,
222 | progressBlock: progressBlock,
223 | completionHandler: diskTaskCompletionHandler,
224 | options: options)
225 |
226 | }
227 |
228 | let targetCache = options.targetCache
229 | targetCache.retrieveImage(forKey: key, options: options) { image, cacheType in
230 | if image != nil {
231 | diskTaskCompletionHandler(image, nil, cacheType, url)
232 | return
233 | }
234 |
235 | let processor = options.processor
236 | if processor != DefaultImageProcessor.default {
237 | let optionsWithoutProcessor = options.removeAllMatchesIgnoringAssociatedValue(.processor(processor))
238 | targetCache.retrieveImage(forKey: key, options: optionsWithoutProcessor) { image, cacheType in
239 | guard let image = image else {
240 | handleNoCache()
241 | return
242 | }
243 |
244 | let processedImage = processor.process(item: .image(image), options: options)
245 | diskTaskCompletionHandler(processedImage, nil, .none, url)
246 | return
247 | }
248 | return
249 | }
250 |
251 | handleNoCache()
252 | }
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/RequestModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestModifier.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2016/09/05.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Request modifier of image downloader.
30 | public protocol ImageDownloadRequestModifier {
31 | func modified(for request: URLRequest) -> URLRequest?
32 | }
33 |
34 | struct NoModifier: ImageDownloadRequestModifier {
35 | static let `default` = NoModifier()
36 | private init() {}
37 | func modified(for request: URLRequest) -> URLRequest? {
38 | return request
39 | }
40 | }
41 |
42 | public struct AnyModifier: ImageDownloadRequestModifier {
43 |
44 | let block: (URLRequest) -> URLRequest?
45 |
46 | public func modified(for request: URLRequest) -> URLRequest? {
47 | return block(request)
48 | }
49 |
50 | public init(modify: @escaping (URLRequest) -> URLRequest? ) {
51 | block = modify
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/Resource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Resource.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 15/4/6.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 |
30 | /// `Resource` protocol defines how to download and cache a resource from network.
31 | public protocol Resource {
32 | /// The key used in cache.
33 | var cacheKey: String { get }
34 |
35 | /// The target image URL.
36 | var downloadURL: URL { get }
37 | }
38 |
39 | /**
40 | ImageResource is a simple combination of `downloadURL` and `cacheKey`.
41 |
42 | When passed to image view set methods, Kingfisher will try to download the target
43 | image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache.
44 | */
45 | public struct ImageResource: Resource {
46 | /// The key used in cache.
47 | public let cacheKey: String
48 |
49 | /// The target image URL.
50 | public let downloadURL: URL
51 |
52 | /**
53 | Create a resource.
54 |
55 | - parameter downloadURL: The target image URL.
56 | - parameter cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key.
57 |
58 | - returns: A resource.
59 | */
60 | public init(downloadURL: URL, cacheKey: String? = nil) {
61 | self.downloadURL = downloadURL
62 | self.cacheKey = cacheKey ?? downloadURL.absoluteString
63 | }
64 | }
65 |
66 | /**
67 | URL conforms to `Resource` in Kingfisher.
68 | The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`.
69 | If you need customize the url and/or cache key, use `ImageResource` instead.
70 | */
71 | extension URL: Resource {
72 | public var cacheKey: String { return absoluteString }
73 | public var downloadURL: URL { return self }
74 | }
75 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/String+MD5.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+MD5.swift
3 | // Kingfisher
4 | //
5 | // To date, adding CommonCrypto to a Swift framework is problematic. See:
6 | // http://stackoverflow.com/questions/25248598/importing-commoncrypto-in-a-swift-framework
7 | // We're using a subset and modified version of CryptoSwift as an alternative.
8 | // The following is an altered source version that only includes MD5. The original software can be found at:
9 | // https://github.com/krzyzanowskim/CryptoSwift
10 | // This is the original copyright notice:
11 |
12 | /*
13 | Copyright (C) 2014 Marcin Krzyżanowski
14 | This software is provided 'as-is', without any express or implied warranty.
15 | In no event will the authors be held liable for any damages arising from the use of this software.
16 | Permission is granted to anyone to use this software for any purpose,including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
17 | - The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation is required.
18 | - Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
19 | - This notice may not be removed or altered from any source or binary distribution.
20 | */
21 |
22 | import Foundation
23 |
24 | public struct StringProxy {
25 | fileprivate let base: String
26 | init(proxy: String) {
27 | base = proxy
28 | }
29 | }
30 |
31 | extension String: KingfisherCompatible {
32 | public typealias CompatibleType = StringProxy
33 | public var kf: CompatibleType {
34 | return StringProxy(proxy: self)
35 | }
36 | }
37 |
38 | extension StringProxy {
39 | var md5: String {
40 | if let data = base.data(using: .utf8, allowLossyConversion: true) {
41 |
42 | let message = data.withUnsafeBytes { bytes -> [UInt8] in
43 | return Array(UnsafeBufferPointer(start: bytes, count: data.count))
44 | }
45 |
46 | let MD5Calculator = MD5(message)
47 | let MD5Data = MD5Calculator.calculate()
48 |
49 | var MD5String = String()
50 | for c in MD5Data {
51 | MD5String += String(format: "%02x", c)
52 | }
53 | return MD5String
54 |
55 | } else {
56 | return base
57 | }
58 | }
59 | }
60 |
61 |
62 | /** array of bytes, little-endian representation */
63 | func arrayOfBytes(_ value: T, length: Int? = nil) -> [UInt8] {
64 | let totalBytes = length ?? (MemoryLayout.size * 8)
65 |
66 | let valuePointer = UnsafeMutablePointer.allocate(capacity: 1)
67 | valuePointer.pointee = value
68 |
69 | let bytes = valuePointer.withMemoryRebound(to: UInt8.self, capacity: totalBytes) { (bytesPointer) -> [UInt8] in
70 | var bytes = [UInt8](repeating: 0, count: totalBytes)
71 | for j in 0...size, totalBytes) {
72 | bytes[totalBytes - 1 - j] = (bytesPointer + j).pointee
73 | }
74 | return bytes
75 | }
76 |
77 | valuePointer.deinitialize()
78 | valuePointer.deallocate(capacity: 1)
79 |
80 | return bytes
81 | }
82 |
83 | extension Int {
84 | /** Array of bytes with optional padding (little-endian) */
85 | func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] {
86 | return arrayOfBytes(self, length: totalBytes)
87 | }
88 |
89 | }
90 |
91 | extension NSMutableData {
92 |
93 | /** Convenient way to append bytes */
94 | func appendBytes(_ arrayOfBytes: [UInt8]) {
95 | append(arrayOfBytes, length: arrayOfBytes.count)
96 | }
97 |
98 | }
99 |
100 | protocol HashProtocol {
101 | var message: Array { get }
102 |
103 | /** Common part for hash calculation. Prepare header data. */
104 | func prepare(_ len: Int) -> Array
105 | }
106 |
107 | extension HashProtocol {
108 |
109 | func prepare(_ len: Int) -> Array {
110 | var tmpMessage = message
111 |
112 | // Step 1. Append Padding Bits
113 | tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
114 |
115 | // append "0" bit until message length in bits ≡ 448 (mod 512)
116 | var msgLength = tmpMessage.count
117 | var counter = 0
118 |
119 | while msgLength % len != (len - 8) {
120 | counter += 1
121 | msgLength += 1
122 | }
123 |
124 | tmpMessage += Array(repeating: 0, count: counter)
125 | return tmpMessage
126 | }
127 | }
128 |
129 | func toUInt32Array(_ slice: ArraySlice) -> Array {
130 | var result = Array()
131 | result.reserveCapacity(16)
132 |
133 | for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) {
134 | let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24
135 | let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16
136 | let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8
137 | let d3 = UInt32(slice[idx])
138 | let val: UInt32 = d0 | d1 | d2 | d3
139 |
140 | result.append(val)
141 | }
142 | return result
143 | }
144 |
145 | struct BytesIterator: IteratorProtocol {
146 |
147 | let chunkSize: Int
148 | let data: [UInt8]
149 |
150 | init(chunkSize: Int, data: [UInt8]) {
151 | self.chunkSize = chunkSize
152 | self.data = data
153 | }
154 |
155 | var offset = 0
156 |
157 | mutating func next() -> ArraySlice? {
158 | let end = min(chunkSize, data.count - offset)
159 | let result = data[offset.. 0 ? result : nil
162 | }
163 | }
164 |
165 | struct BytesSequence: Sequence {
166 | let chunkSize: Int
167 | let data: [UInt8]
168 |
169 | func makeIterator() -> BytesIterator {
170 | return BytesIterator(chunkSize: chunkSize, data: data)
171 | }
172 | }
173 |
174 | func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 {
175 | return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits))
176 | }
177 |
178 | class MD5: HashProtocol {
179 |
180 | static let size = 16 // 128 / 8
181 | let message: [UInt8]
182 |
183 | init (_ message: [UInt8]) {
184 | self.message = message
185 | }
186 |
187 | /** specifies the per-round shift amounts */
188 | private let shifts: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
189 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
190 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
191 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]
192 |
193 | /** binary integer part of the sines of integers (Radians) */
194 | private let sines: [UInt32] = [0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
195 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
196 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
197 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
198 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
199 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
200 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
201 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
202 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
203 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
204 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05,
205 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
206 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
207 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
208 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
209 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391]
210 |
211 | private let hashes: [UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]
212 |
213 | func calculate() -> [UInt8] {
214 | var tmpMessage = prepare(64)
215 | tmpMessage.reserveCapacity(tmpMessage.count + 4)
216 |
217 | // hash values
218 | var hh = hashes
219 |
220 | // Step 2. Append Length a 64-bit representation of lengthInBits
221 | let lengthInBits = (message.count * 8)
222 | let lengthBytes = lengthInBits.bytes(64 / 8)
223 | tmpMessage += lengthBytes.reversed()
224 |
225 | // Process the message in successive 512-bit chunks:
226 | let chunkSizeBytes = 512 / 8 // 64
227 |
228 | for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
229 | // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
230 | var M = toUInt32Array(chunk)
231 | assert(M.count == 16, "Invalid array")
232 |
233 | // Initialize hash value for this chunk:
234 | var A: UInt32 = hh[0]
235 | var B: UInt32 = hh[1]
236 | var C: UInt32 = hh[2]
237 | var D: UInt32 = hh[3]
238 |
239 | var dTemp: UInt32 = 0
240 |
241 | // Main loop
242 | for j in 0 ..< sines.count {
243 | var g = 0
244 | var F: UInt32 = 0
245 |
246 | switch j {
247 | case 0...15:
248 | F = (B & C) | ((~B) & D)
249 | g = j
250 | break
251 | case 16...31:
252 | F = (D & B) | (~D & C)
253 | g = (5 * j + 1) % 16
254 | break
255 | case 32...47:
256 | F = B ^ C ^ D
257 | g = (3 * j + 5) % 16
258 | break
259 | case 48...63:
260 | F = C ^ (B | (~D))
261 | g = (7 * j) % 16
262 | break
263 | default:
264 | break
265 | }
266 | dTemp = D
267 | D = C
268 | C = B
269 | B = B &+ rotateLeft((A &+ F &+ sines[j] &+ M[g]), bits: shifts[j])
270 | A = dTemp
271 | }
272 |
273 | hh[0] = hh[0] &+ A
274 | hh[1] = hh[1] &+ B
275 | hh[2] = hh[2] &+ C
276 | hh[3] = hh[3] &+ D
277 | }
278 |
279 | var result = [UInt8]()
280 | result.reserveCapacity(hh.count / 4)
281 |
282 | hh.forEach {
283 | let itemLE = $0.littleEndian
284 | let r1 = UInt8(itemLE & 0xff)
285 | let r2 = UInt8((itemLE >> 8) & 0xff)
286 | let r3 = UInt8((itemLE >> 16) & 0xff)
287 | let r4 = UInt8((itemLE >> 24) & 0xff)
288 | result += [r1, r2, r3, r4]
289 | }
290 | return result
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Kingfisher/Sources/ThreadHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThreadHelper.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 15/10/9.
6 | //
7 | // Copyright (c) 2017 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | extension DispatchQueue {
30 | // This method will dispatch the `block` to self.
31 | // If `self` is the main queue, and current thread is main thread, the block
32 | // will be invoked immediately instead of being dispatched.
33 | func safeAsync(_ block: @escaping ()->()) {
34 | if self === DispatchQueue.main && Thread.isMainThread {
35 | block()
36 | } else {
37 | async { block() }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Kingfisher (3.10.1)
3 |
4 | DEPENDENCIES:
5 | - Kingfisher (~> 3.10.1)
6 |
7 | SPEC CHECKSUMS:
8 | Kingfisher: 33a1b82140fcb14a9fc13f78dfde2a03ff8de8cb
9 |
10 | PODFILE CHECKSUM: f366e64ee1eb9167a86ae09cc5178ed888607b7d
11 |
12 | COCOAPODS: 1.2.1
13 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Kingfisher/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 3.10.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Kingfisher/Kingfisher-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Kingfisher : NSObject
3 | @end
4 | @implementation PodsDummy_Kingfisher
5 | @end
6 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Kingfisher/Kingfisher-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Kingfisher/Kingfisher-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 | #import "Kingfisher.h"
14 |
15 | FOUNDATION_EXPORT double KingfisherVersionNumber;
16 | FOUNDATION_EXPORT const unsigned char KingfisherVersionString[];
17 |
18 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Kingfisher/Kingfisher.modulemap:
--------------------------------------------------------------------------------
1 | framework module Kingfisher {
2 | umbrella header "Kingfisher-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Kingfisher/Kingfisher.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/Kingfisher
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public"
4 | OTHER_LDFLAGS = -framework "CFNetwork"
5 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
6 | PODS_BUILD_DIR = $BUILD_DIR
7 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8 | PODS_ROOT = ${SRCROOT}
9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Kingfisher
10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
11 | SKIP_INSTALL = YES
12 | SWIFT_VERSION = 3.0
13 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 |
4 | ## Kingfisher
5 |
6 | The MIT License (MIT)
7 |
8 | Copyright (c) 2017 Wei Wang
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 |
29 | Generated by CocoaPods - https://cocoapods.org
30 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | The MIT License (MIT)
18 |
19 | Copyright (c) 2017 Wei Wang
20 |
21 | Permission is hereby granted, free of charge, to any person obtaining a copy
22 | of this software and associated documentation files (the "Software"), to deal
23 | in the Software without restriction, including without limitation the rights
24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25 | copies of the Software, and to permit persons to whom the Software is
26 | furnished to do so, subject to the following conditions:
27 |
28 | The above copyright notice and this permission notice shall be included in all
29 | copies or substantial portions of the Software.
30 |
31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37 | SOFTWARE.
38 |
39 |
40 | License
41 | MIT
42 | Title
43 | Kingfisher
44 | Type
45 | PSGroupSpecifier
46 |
47 |
48 | FooterText
49 | Generated by CocoaPods - https://cocoapods.org
50 | Title
51 |
52 | Type
53 | PSGroupSpecifier
54 |
55 |
56 | StringsTable
57 | Acknowledgements
58 | Title
59 | Acknowledgements
60 |
61 |
62 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_UICollectionViewDemo : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_UICollectionViewDemo
5 | @end
6 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo-frameworks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
5 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
6 |
7 | SWIFT_STDLIB_PATH="${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}"
8 |
9 | install_framework()
10 | {
11 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then
12 | local source="${BUILT_PRODUCTS_DIR}/$1"
13 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then
14 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")"
15 | elif [ -r "$1" ]; then
16 | local source="$1"
17 | fi
18 |
19 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
20 |
21 | if [ -L "${source}" ]; then
22 | echo "Symlinked..."
23 | source="$(readlink "${source}")"
24 | fi
25 |
26 | # use filter instead of exclude so missing patterns dont' throw errors
27 | echo "rsync -av --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\""
28 | rsync -av --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}"
29 |
30 | local basename
31 | basename="$(basename -s .framework "$1")"
32 | binary="${destination}/${basename}.framework/${basename}"
33 | if ! [ -r "$binary" ]; then
34 | binary="${destination}/${basename}"
35 | fi
36 |
37 | # Strip invalid architectures so "fat" simulator / device frameworks work on device
38 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then
39 | strip_invalid_archs "$binary"
40 | fi
41 |
42 | # Resign the code if required by the build settings to avoid unstable apps
43 | code_sign_if_enabled "${destination}/$(basename "$1")"
44 |
45 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7.
46 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then
47 | local swift_runtime_libs
48 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u && exit ${PIPESTATUS[0]})
49 | for lib in $swift_runtime_libs; do
50 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\""
51 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}"
52 | code_sign_if_enabled "${destination}/${lib}"
53 | done
54 | fi
55 | }
56 |
57 | # Signs a framework with the provided identity
58 | code_sign_if_enabled() {
59 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY}" -a "${CODE_SIGNING_REQUIRED}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then
60 | # Use the current code_sign_identitiy
61 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
62 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS} --preserve-metadata=identifier,entitlements '$1'"
63 |
64 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
65 | code_sign_cmd="$code_sign_cmd &"
66 | fi
67 | echo "$code_sign_cmd"
68 | eval "$code_sign_cmd"
69 | fi
70 | }
71 |
72 | # Strip invalid architectures
73 | strip_invalid_archs() {
74 | binary="$1"
75 | # Get architectures for current file
76 | archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | rev)"
77 | stripped=""
78 | for arch in $archs; do
79 | if ! [[ "${VALID_ARCHS}" == *"$arch"* ]]; then
80 | # Strip non-valid architectures in-place
81 | lipo -remove "$arch" -output "$binary" "$binary" || exit 1
82 | stripped="$stripped $arch"
83 | fi
84 | done
85 | if [[ "$stripped" ]]; then
86 | echo "Stripped $binary of architectures:$stripped"
87 | fi
88 | }
89 |
90 |
91 | if [[ "$CONFIGURATION" == "Debug" ]]; then
92 | install_framework "$BUILT_PRODUCTS_DIR/Kingfisher/Kingfisher.framework"
93 | fi
94 | if [[ "$CONFIGURATION" == "Release" ]]; then
95 | install_framework "$BUILT_PRODUCTS_DIR/Kingfisher/Kingfisher.framework"
96 | fi
97 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then
98 | wait
99 | fi
100 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
5 |
6 | RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
7 | > "$RESOURCES_TO_COPY"
8 |
9 | XCASSET_FILES=()
10 |
11 | case "${TARGETED_DEVICE_FAMILY}" in
12 | 1,2)
13 | TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
14 | ;;
15 | 1)
16 | TARGET_DEVICE_ARGS="--target-device iphone"
17 | ;;
18 | 2)
19 | TARGET_DEVICE_ARGS="--target-device ipad"
20 | ;;
21 | 3)
22 | TARGET_DEVICE_ARGS="--target-device tv"
23 | ;;
24 | 4)
25 | TARGET_DEVICE_ARGS="--target-device watch"
26 | ;;
27 | *)
28 | TARGET_DEVICE_ARGS="--target-device mac"
29 | ;;
30 | esac
31 |
32 | install_resource()
33 | {
34 | if [[ "$1" = /* ]] ; then
35 | RESOURCE_PATH="$1"
36 | else
37 | RESOURCE_PATH="${PODS_ROOT}/$1"
38 | fi
39 | if [[ ! -e "$RESOURCE_PATH" ]] ; then
40 | cat << EOM
41 | error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
42 | EOM
43 | exit 1
44 | fi
45 | case $RESOURCE_PATH in
46 | *.storyboard)
47 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
48 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
49 | ;;
50 | *.xib)
51 | echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}"
52 | ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
53 | ;;
54 | *.framework)
55 | echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
56 | mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
57 | echo "rsync -av $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
58 | rsync -av "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
59 | ;;
60 | *.xcdatamodel)
61 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\""
62 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
63 | ;;
64 | *.xcdatamodeld)
65 | echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\""
66 | xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
67 | ;;
68 | *.xcmappingmodel)
69 | echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\""
70 | xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
71 | ;;
72 | *.xcassets)
73 | ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
74 | XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
75 | ;;
76 | *)
77 | echo "$RESOURCE_PATH"
78 | echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
79 | ;;
80 | esac
81 | }
82 |
83 | mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
84 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
85 | if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
86 | mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
87 | rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
88 | fi
89 | rm -f "$RESOURCES_TO_COPY"
90 |
91 | if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "$XCASSET_FILES" ]
92 | then
93 | # Find all other xcassets (this unfortunately includes those of path pods and other targets).
94 | OTHER_XCASSETS=$(find "$PWD" -iname "*.xcassets" -type d)
95 | while read line; do
96 | if [[ $line != "${PODS_ROOT}*" ]]; then
97 | XCASSET_FILES+=("$line")
98 | fi
99 | done <<<"$OTHER_XCASSETS"
100 |
101 | printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
102 | fi
103 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_UICollectionViewDemoVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_UICollectionViewDemoVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Kingfisher"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = $BUILD_DIR
9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_UICollectionViewDemo {
2 | umbrella header "Pods-UICollectionViewDemo-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/Pods/Target Support Files/Pods-UICollectionViewDemo/Pods-UICollectionViewDemo.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/Kingfisher/Kingfisher.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "Kingfisher"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = $BUILD_DIR
9 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/19.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 |
20 | UINavigationBar.appearance().tintColor = .black
21 | return true
22 | }
23 |
24 | func applicationWillResignActive(_ application: UIApplication) {
25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
27 | }
28 |
29 | func applicationDidEnterBackground(_ application: UIApplication) {
30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
32 | }
33 |
34 | func applicationWillEnterForeground(_ application: UIApplication) {
35 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
36 | }
37 |
38 | func applicationDidBecomeActive(_ application: UIApplication) {
39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
40 | }
41 |
42 | func applicationWillTerminate(_ application: UIApplication) {
43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
44 | }
45 |
46 |
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | }
43 | ],
44 | "info" : {
45 | "version" : 1,
46 | "author" : "xcode"
47 | }
48 | }
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Assets.xcassets/collection_error.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "collection_error@2x.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Assets.xcassets/collection_error.imageset/collection_error@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alenpaulkevin/UICollectionViewDemo/71fd9b6188764554a395d94df24cbfcbea4692fd/UICollectionViewDemo/UICollectionViewDemo/Assets.xcassets/collection_error.imageset/collection_error@2x.png
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Base/BaseCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseCollectionViewCell.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/20.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class BaseCollectionViewCell: UICollectionViewCell {
12 |
13 | var cellIndex: Int = 0 {
14 | didSet {
15 | textLabel.text = "\(cellIndex)"
16 | }
17 | }
18 |
19 | private var textLabel: UILabel!
20 |
21 | override init(frame: CGRect) {
22 | super.init(frame: frame)
23 | textLabel = UILabel()
24 | textLabel.textAlignment = .center
25 | contentView.addSubview(textLabel)
26 | backgroundColor = .orange
27 | }
28 |
29 | /// 流水布局,因为控件会实时变化,需要在这里计算frame,如果控件大小固定,推荐直接在init方法里算
30 | override func layoutSubviews() {
31 | super.layoutSubviews()
32 | textLabel.frame = bounds
33 | }
34 |
35 | // 设置选中的背景颜色
36 | override var isSelected: Bool {
37 | didSet {
38 | if isSelected {
39 | contentView.backgroundColor = .lightGray
40 | } else {
41 | contentView.backgroundColor = nil
42 | }
43 | super.isSelected = isSelected
44 | }
45 | }
46 |
47 | // 设置高亮颜色
48 | override var isHighlighted: Bool {
49 | didSet {
50 | if isHighlighted {
51 | contentView.backgroundColor = .purple
52 | } else {
53 | contentView.backgroundColor = nil
54 | }
55 | super.isHighlighted = isHighlighted
56 | }
57 | }
58 |
59 | required init?(coder aDecoder: NSCoder) {
60 | fatalError("init(coder:) has not been implemented")
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Base/BaseCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseCollectionViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/20.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let baseCellID = "baseCellID"
12 | private let baseReuseHeaderID = "baseReuseHeaderID"
13 | private let baseReuseFooterID = "baseReuseFooterID"
14 |
15 | class BaseCollectionViewController: UIViewController {
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | navigationItem.title = "基本使用"
20 | setUpView()
21 | }
22 |
23 | private func setUpView() {
24 | // 创建flowLayout对象
25 | let layout = UICollectionViewFlowLayout()
26 | let margin: CGFloat = 8
27 | let itemW = (view.bounds.width - margin * 4) / 3
28 | let itemH = itemW
29 | layout.itemSize = CGSize(width: itemW, height: itemH)
30 |
31 | // 最小行间距
32 | layout.minimumLineSpacing = margin
33 |
34 | // 最小item之间的距离
35 | layout.minimumInteritemSpacing = margin
36 |
37 | // 每组item的边缘切距
38 | layout.sectionInset = UIEdgeInsetsMake(0, margin, 0, margin)
39 |
40 | // 滚动方向
41 | layout.scrollDirection = .vertical
42 |
43 | // 创建collection
44 | let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
45 | collectionView.backgroundColor = .white
46 |
47 | collectionView.delegate = self
48 | collectionView.dataSource = self
49 |
50 | // 注册cell
51 | collectionView.register(BaseCollectionViewCell.self, forCellWithReuseIdentifier: baseCellID)
52 |
53 | // 注册头尾部视图,它们必须继承自UICollectionReuseView
54 | collectionView.register(UINib(nibName: "BaseHeaderAndFooterCollectionReusableView", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: baseReuseHeaderID)
55 | collectionView.register(UINib(nibName: "BaseHeaderAndFooterCollectionReusableView", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: baseReuseFooterID)
56 |
57 | view.addSubview(collectionView)
58 | }
59 | }
60 |
61 | extension BaseCollectionViewController: UICollectionViewDataSource {
62 | func numberOfSections(in collectionView: UICollectionView) -> Int {
63 | return 3
64 | }
65 |
66 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
67 | return 5 + section * 3
68 | }
69 |
70 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
71 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: baseCellID, for: indexPath) as! BaseCollectionViewCell
72 | cell.cellIndex = indexPath.item
73 | return cell
74 | }
75 |
76 | // 头尾部的数据源协议
77 | func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
78 | if kind == UICollectionElementKindSectionHeader {
79 | let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: baseReuseHeaderID, for: indexPath) as! BaseHeaderAndFooterCollectionReusableView
80 | header.backgroundColor = .purple
81 | header.textLabel.text = "第 \(indexPath.section) 组的头部"
82 | return header
83 | }
84 | let footer = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionFooter, withReuseIdentifier: baseReuseFooterID, for: indexPath) as! BaseHeaderAndFooterCollectionReusableView
85 | footer.textLabel.text = "第 \(indexPath.section) 组的尾部"
86 | footer.backgroundColor = .lightGray
87 | return footer
88 | }
89 | }
90 |
91 | extension BaseCollectionViewController: UICollectionViewDelegate {
92 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
93 | print("选中了第\(indexPath.section)组 第 \(indexPath.item) 个");
94 | }
95 |
96 | // 实现以下三个方法,长按会弹出编辑菜单
97 | func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
98 | return true
99 | }
100 |
101 | func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
102 | return true
103 | }
104 |
105 | func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
106 |
107 | }
108 | }
109 |
110 | // MARK: - flowLayout协议
111 | extension BaseCollectionViewController: UICollectionViewDelegateFlowLayout {
112 |
113 | // section的header的大小,宽度设置不影响,如果固定,最好直接设置,别走代理方法
114 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
115 | return CGSize(width: 0, height: CGFloat(36 + 10 * section))
116 | }
117 |
118 | // section的footer的大小,宽度设置不影响,如果固定,最好直接设置,别走代理方法
119 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
120 | return CGSize(width: 0, height: CGFloat(36 + 10 * section))
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Base/BaseHeaderAndFooterCollectionReusableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseHeaderAndFooterCollectionReusableView.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/20.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class BaseHeaderAndFooterCollectionReusableView: UICollectionReusableView {
12 |
13 | @IBOutlet weak var textLabel: UILabel!
14 |
15 | override func awakeFromNib() {
16 | super.awakeFromNib()
17 | // Initialization code
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Base/BaseHeaderAndFooterCollectionReusableView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Circular/CircularCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CircularCollectionViewCell.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/24.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class CircularCollectionViewCell: UICollectionViewCell {
12 | var cellIndex: Int = 0 {
13 | didSet {
14 | textLabel.text = "\(cellIndex)"
15 | }
16 | }
17 |
18 | private var textLabel: UILabel!
19 |
20 | override init(frame: CGRect) {
21 | super.init(frame: frame)
22 | textLabel = UILabel(frame: bounds)
23 | textLabel.textAlignment = .center
24 | contentView.addSubview(textLabel)
25 | backgroundColor = .orange
26 | }
27 |
28 | // 设置选中的背景颜色
29 | override var isSelected: Bool {
30 | didSet {
31 | if isSelected {
32 | contentView.backgroundColor = .lightGray
33 | } else {
34 | contentView.backgroundColor = nil
35 | }
36 | super.isSelected = isSelected
37 | }
38 | }
39 |
40 | // 设置高亮颜色
41 | override var isHighlighted: Bool {
42 | didSet {
43 | if isHighlighted {
44 | contentView.backgroundColor = .purple
45 | } else {
46 | contentView.backgroundColor = nil
47 | }
48 | super.isHighlighted = isHighlighted
49 | }
50 | }
51 |
52 | override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
53 | super.apply(layoutAttributes)
54 | let circularlayoutAttributes = layoutAttributes as! CircularCollectionViewLayoutAttributes
55 | layer.anchorPoint = circularlayoutAttributes.anchorPoint
56 | layer.position.y = layer.position.y + (circularlayoutAttributes.anchorPoint.y - 0.5) * bounds.height
57 | }
58 |
59 | required init?(coder aDecoder: NSCoder) {
60 | fatalError("init(coder:) has not been implemented")
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Circular/CircularCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CircularCollectionViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/24.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let reuseIdentifier = "Cell"
12 |
13 | class CircularCollectionViewController: UICollectionViewController {
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 |
18 | // Register cell classes
19 | self.collectionView!.register(CircularCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
20 | }
21 | }
22 |
23 | extension CircularCollectionViewController {
24 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
25 | return 10
26 | }
27 |
28 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
29 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
30 | as! CircularCollectionViewCell
31 | cell.cellIndex = indexPath.item
32 | cell.backgroundColor = indexPath.item % 2 == 0 ? .purple : .lightGray
33 | return cell
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Circular/CircularCollectionViewLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CircularCollectionViewLayout.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/24.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class CircularCollectionViewLayout: UICollectionViewLayout {
12 |
13 | /// 每个item的大小
14 | let itemSize = CGSize(width: 133, height: 173)
15 |
16 | /// 属性数组
17 | var attributesList: [CircularCollectionViewLayoutAttributes] = []
18 |
19 | /// 设置半径,需要重新设置布局
20 | var radius: CGFloat = 500 {
21 | didSet {
22 | invalidateLayout()
23 | }
24 | }
25 |
26 | /// 每两个item 之间的角度,任意值
27 | var anglePerItem: CGFloat {
28 | return atan(itemSize.width / radius) // atan反正切
29 | }
30 |
31 | /// 当collectionView滑到极端时,第 0个item的角度 (第0个开始是 0 度, 当滑到极端时, 最后一个是 0 度)
32 | var angleAtextreme: CGFloat {
33 | return collectionView!.numberOfItems(inSection: 0) > 0 ? -CGFloat(collectionView!.numberOfItems(inSection: 0) - 1) * anglePerItem : 0
34 | }
35 |
36 | /// 滑动时,第0个item的角度
37 | var angle: CGFloat {
38 | return angleAtextreme * collectionView!.contentOffset.x / (collectionViewContentSize.width - collectionView!.bounds.width)
39 | }
40 |
41 |
42 | override var collectionViewContentSize: CGSize {
43 | return CGSize(width: CGFloat(collectionView!.numberOfItems(inSection: 0)) * itemSize.width, height: collectionView!.bounds.height)
44 | }
45 |
46 | /// 告诉类使用CircularCollectionViewLayoutAttributes类布局
47 | override class var layoutAttributesClass: Swift.AnyClass {
48 | return CircularCollectionViewLayoutAttributes.self
49 | }
50 |
51 | override func prepare() {
52 | super.prepare()
53 |
54 | // 整体布局是将每个item设置在屏幕中心,然后旋转 anglePerItem * i 度
55 | let centerX = collectionView!.contentOffset.x + collectionView!.bounds.width / 2.0
56 | // 锚点的y值,多增加了raidus的值
57 | let anchorPointY = ((itemSize.height / 2.0) + radius) / itemSize.height
58 |
59 | /// 不要计算所有的item,只计算在屏幕中的item,theta最大倾斜
60 | let theta = atan2(collectionView!.bounds.width / 2, radius + (itemSize.height / 2.0) - collectionView!.bounds.height / 2)
61 | var startIndex = 0
62 | var endIndex = collectionView!.numberOfItems(inSection: 0) - 1
63 | // 开始位置
64 | if angle < -theta {
65 | startIndex = Int(floor((-theta - angle) / anglePerItem))
66 | }
67 | // 结束为止
68 | endIndex = min(endIndex, Int(ceil((theta - angle) / anglePerItem)))
69 |
70 | if endIndex < startIndex {
71 | endIndex = 0
72 | startIndex = 0
73 | }
74 | // startIndex...endIndex
75 | attributesList = (startIndex...endIndex).map({ (i) -> CircularCollectionViewLayoutAttributes in
76 | let attributes = CircularCollectionViewLayoutAttributes(forCellWith: IndexPath(item: i, section: 0))
77 | attributes.size = self.itemSize
78 | // 设置居中
79 | attributes.center = CGPoint(x: centerX, y: collectionView!.bounds.midY)
80 | // 设置偏移角度
81 | attributes.transform = CGAffineTransform(rotationAngle: self.angle + anglePerItem * CGFloat(i))
82 | // 锚点,我们自定义的属性
83 | attributes.anchorPoint = CGPoint(x: 0.5, y: anchorPointY)
84 | return attributes
85 | })
86 | }
87 |
88 | // override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
89 | //
90 | // }
91 |
92 | // MARK: - 使卡片停在中间
93 |
94 | /// 重写滚动时停下的位置
95 | ///
96 | /// - Parameters:
97 | /// - proposedContentOffset: 将要停止的点
98 | /// - velocity: 滚动速度
99 | /// - Returns: 滚动停止的点
100 | override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
101 |
102 | var finalContentOffset = proposedContentOffset
103 |
104 | // 每单位偏移量对应的偏移角度
105 | let factor = -angleAtextreme / (collectionViewContentSize.width - collectionView!.bounds.width)
106 | let proposedAngle = proposedContentOffset.x * factor
107 |
108 | // 大约偏移了多少个
109 | let ratio = proposedAngle / anglePerItem
110 |
111 | var multiplier: CGFloat
112 |
113 | // 往左滑动
114 | if velocity.x > 0 {
115 | multiplier = ceil(ratio)
116 | } else if (velocity.x < 0) { // 往右滑动
117 | multiplier = floor(ratio)
118 | } else {
119 | multiplier = round(ratio)
120 | }
121 |
122 | finalContentOffset.x = multiplier * anglePerItem / factor
123 |
124 | return finalContentOffset
125 |
126 | }
127 |
128 | // 返回布局数组
129 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
130 | return attributesList
131 | }
132 |
133 |
134 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
135 | return true
136 | }
137 | }
138 |
139 |
140 | // MARK: - 自定义UICollectionViewLayoutAttributes
141 | /// 主要为了存储 anchorPoint,好在cell的apply(_:)方法中使用来旋转cell,因为UICollectionViewLayoutAttributes没有这个属性
142 | class CircularCollectionViewLayoutAttributes: UICollectionViewLayoutAttributes {
143 |
144 | var anchorPoint = CGPoint(x: 0.5, y: 0.5)
145 |
146 | /// 需要实现这个方法,collection View 实时布局时,会copy参数,确保自身的参数被copy
147 | override func copy(with zone: NSZone? = nil) -> Any {
148 | let copiedAttributes: CircularCollectionViewLayoutAttributes = super.copy(with: zone) as! CircularCollectionViewLayoutAttributes
149 | copiedAttributes.anchorPoint = anchorPoint
150 | return copiedAttributes
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/CoverFlow/CoverFlowLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoverFlowLayout.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/23.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class CoverFlowLayout: UICollectionViewFlowLayout {
12 |
13 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
14 | // 获取这个范围的布局数组
15 | let attributes = super.layoutAttributesForElements(in: rect)
16 | // 找到中心点
17 | let centerX = collectionView!.contentOffset.x + collectionView!.bounds.width / 2
18 |
19 | // 每个点根据距离中心点距离进行缩放
20 | attributes!.forEach({ (attr) in
21 | let pad = abs(centerX - attr.center.x)
22 | let scale = 1.8 - pad / collectionView!.bounds.width
23 | attr.transform = CGAffineTransform(scaleX: scale, y: scale)
24 | })
25 | return attributes
26 | }
27 |
28 | /// 重写滚动时停下的位置
29 | ///
30 | /// - Parameters:
31 | /// - proposedContentOffset: 将要停止的点
32 | /// - velocity: 滚动速度
33 | /// - Returns: 滚动停止的点
34 | override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
35 | var targetPoint = proposedContentOffset
36 |
37 | // 中心点
38 | let centerX = proposedContentOffset.x + collectionView!.bounds.width
39 |
40 | // 获取这个范围的布局数组
41 | let attributes = self.layoutAttributesForElements(in: CGRect(x: proposedContentOffset.x, y: proposedContentOffset.y, width: collectionView!.bounds.width, height: collectionView!.bounds.height))
42 |
43 | // 需要移动的最小距离
44 | var moveDistance: CGFloat = CGFloat(MAXFLOAT)
45 | // 遍历数组找出最小距离
46 | attributes!.forEach { (attr) in
47 | if abs(attr.center.x - centerX) < abs(moveDistance) {
48 | moveDistance = attr.center.x - centerX
49 | }
50 | }
51 | // 只有在ContentSize范围内,才进行移动
52 | if targetPoint.x > 0 && targetPoint.x < collectionViewContentSize.width - collectionView!.bounds.width {
53 | targetPoint.x += moveDistance
54 | }
55 |
56 | return targetPoint
57 | }
58 |
59 | override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
60 | return true
61 | }
62 |
63 | override var collectionViewContentSize: CGSize {
64 | return CGSize(width:sectionInset.left + sectionInset.right + (CGFloat(collectionView!.numberOfItems(inSection: 0)) * (itemSize.width + minimumLineSpacing)) - minimumLineSpacing, height: 0)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/CoverFlow/CoverFlowViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoverFlowViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/23.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let baseCellID = "baseCellID"
12 |
13 | class CoverFlowViewController: UIViewController {
14 |
15 | var collectionView: UICollectionView!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | view.backgroundColor = .white
20 | automaticallyAdjustsScrollViewInsets = false
21 | setUpView()
22 | }
23 |
24 |
25 |
26 | override func viewDidLayoutSubviews() {
27 | super.viewDidLayoutSubviews()
28 | bringMiddleCellToFront() // 把中间的cell的放在最前面
29 | }
30 |
31 | /// 把中间的cell带到最前面
32 | fileprivate func bringMiddleCellToFront() {
33 | let pointX = (collectionView.contentOffset.x + collectionView.bounds.width / 2)
34 | let point = CGPoint(x: pointX, y: collectionView.bounds.height / 2)
35 | let indexPath = collectionView.indexPathForItem(at: point)
36 | if let letIndexPath = indexPath {
37 | let cell = collectionView.cellForItem(at: letIndexPath)
38 | guard let letCell = cell else {
39 | return
40 | }
41 | // 把cell放到最前面
42 | collectionView.bringSubview(toFront: letCell)
43 | }
44 | }
45 |
46 | private func setUpView() {
47 | // 创建flowLayout对象
48 | let layout = CoverFlowLayout()
49 |
50 | let margin: CGFloat = 40
51 | let collH: CGFloat = 200
52 | let itemH = collH - margin * 2
53 | let itemW = (view.bounds.width - margin * 4) / 3
54 | layout.itemSize = CGSize(width: itemW, height: itemH);
55 | layout.minimumLineSpacing = margin
56 | layout.minimumInteritemSpacing = margin
57 | layout.sectionInset = UIEdgeInsetsMake(margin, margin, margin, margin)
58 | layout.scrollDirection = .horizontal
59 | // 创建collection
60 | collectionView = UICollectionView(frame: CGRect(x: 0, y: 180, width: view.bounds.width, height: collH), collectionViewLayout: layout)
61 | collectionView.backgroundColor = .black
62 | collectionView.dataSource = self
63 | collectionView.delegate = self
64 |
65 | // 注册cell
66 | collectionView.register(BaseCollectionViewCell.self, forCellWithReuseIdentifier: baseCellID)
67 | view.addSubview(collectionView)
68 |
69 | }
70 | }
71 |
72 | extension CoverFlowViewController: UICollectionViewDataSource {
73 |
74 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
75 | return 15
76 | }
77 |
78 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
79 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: baseCellID, for: indexPath) as! BaseCollectionViewCell
80 | cell.cellIndex = indexPath.item
81 | cell.backgroundColor = indexPath.item % 2 == 0 ? .purple : .lightGray
82 | return cell
83 | }
84 | }
85 |
86 | extension CoverFlowViewController: UICollectionViewDelegate {
87 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
88 | bringMiddleCellToFront()
89 | }
90 | }
91 |
92 |
93 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/HorizontalCollection/HorizontalCollectionFlowLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HorizontalCollectionFlowLayout.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/21.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class HorizontalCollectionFlowLayout: UICollectionViewFlowLayout {
12 |
13 | var cols = 4 // 列数
14 | var line = 4 // 行数
15 |
16 | /// contentSize的最大宽度
17 | fileprivate var maxWidth: CGFloat = 0
18 |
19 | /// 布局frame数组
20 | fileprivate lazy var layoutAttributeArray: [UICollectionViewLayoutAttributes] = []
21 |
22 |
23 | override func prepare() {
24 | super.prepare()
25 | // 每个item的宽度
26 | let itemW = (collectionView!.bounds.width - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(cols - 1)) / CGFloat(cols)
27 | // 每个item的高度
28 | let itemH = (collectionView!.bounds.height - sectionInset.top - sectionInset.bottom - minimumLineSpacing * CGFloat(line - 1)) / CGFloat(line)
29 |
30 | // 求出对应的组数
31 | let sections = collectionView?.numberOfSections
32 | // 每个item所在组的 前面总的页数
33 | var prePageCount: Int = 0
34 | for i in 0.. [UICollectionViewLayoutAttributes]? {
65 | // 找出相交的那些,别全部返回
66 | return layoutAttributeArray.filter { $0.frame.intersects(rect)}
67 | }
68 |
69 | override var collectionViewContentSize: CGSize {
70 | return CGSize(width: maxWidth, height: 0)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/HorizontalCollection/HorizontalCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HorizontalCollectionViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/23.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let baseCellID = "baseCellID"
12 | class HorizontalCollectionViewController: UIViewController {
13 |
14 | var collectionView: UICollectionView!
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | view.backgroundColor = .white
19 | automaticallyAdjustsScrollViewInsets = false
20 | setUpView()
21 | }
22 |
23 | private func setUpView() {
24 | // 创建flowLayout对象
25 | let layout = HorizontalCollectionFlowLayout()
26 | // 设置列数
27 | layout.cols = 4
28 | layout.line = 4
29 |
30 |
31 | let margin: CGFloat = 8
32 | // layout.itemSize = CGSize(width: (view.bounds.width - margin * 5) / 4, height: (view.bounds.width - margin * 3) / 4);
33 | layout.minimumLineSpacing = margin
34 | layout.minimumInteritemSpacing = margin
35 | layout.sectionInset = UIEdgeInsetsMake(margin, margin, margin, margin)
36 | layout.scrollDirection = .horizontal
37 | // 创建collection
38 | collectionView = UICollectionView(frame: CGRect(x: 0, y: 80, width: view.bounds.width, height: 380), collectionViewLayout: layout)
39 | collectionView.isPagingEnabled = true
40 | collectionView.backgroundColor = .white
41 | collectionView.dataSource = self
42 |
43 | // 注册cell
44 | collectionView.register(BaseCollectionViewCell.self, forCellWithReuseIdentifier: baseCellID)
45 | view.addSubview(collectionView)
46 |
47 | }
48 | }
49 |
50 | extension HorizontalCollectionViewController: UICollectionViewDataSource {
51 | func numberOfSections(in collectionView: UICollectionView) -> Int {
52 | return 5
53 | }
54 |
55 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
56 | return 13 + section
57 | }
58 |
59 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
60 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: baseCellID, for: indexPath) as! BaseCollectionViewCell
61 | cell.cellIndex = indexPath.item
62 | cell.backgroundColor = indexPath.item % 2 == 0 ? .purple : .lightGray
63 | return cell
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIMainStoryboardFile
31 | Main
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Interactive/InteractiveMoveViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InteractiveMoveViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by CaoXuan on 2017/7/9.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let interactiveMoveCellID = "interactiveMoveCellID"
12 | class InteractiveMoveViewController: UIViewController {
13 |
14 | var collectionView: UICollectionView!
15 |
16 | fileprivate lazy var dataNumbers: [Int] = {
17 | var number = [Int]()
18 | for num in 0...100 {
19 | let height = num
20 | number.append(height)
21 | }
22 | return number
23 | }()
24 |
25 | override func viewDidLoad() {
26 | super.viewDidLoad()
27 |
28 | // Do any additional setup after loading the view.
29 | // 创建flowLayout对象
30 | let layout = UICollectionViewFlowLayout()
31 | let margin: CGFloat = 8
32 | let itemW = (view.bounds.width - margin * 4) / 3
33 | let itemH = itemW
34 | layout.itemSize = CGSize(width: itemW, height: itemH)
35 |
36 | // 最小行间距
37 | layout.minimumLineSpacing = margin
38 |
39 | // 最小item之间的距离
40 | layout.minimumInteritemSpacing = margin
41 |
42 | // 每组item的边缘切距
43 | layout.sectionInset = UIEdgeInsetsMake(0, margin, 0, margin)
44 |
45 | // 滚动方向
46 | layout.scrollDirection = .vertical
47 |
48 | // 创建collection
49 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
50 | collectionView.backgroundColor = .white
51 |
52 | collectionView.delegate = self
53 | collectionView.dataSource = self
54 | // 分页拖动
55 | // collectionView.isPagingEnabled = true
56 |
57 |
58 | // 注册cell
59 | collectionView.register(BaseCollectionViewCell.self, forCellWithReuseIdentifier: interactiveMoveCellID)
60 | view.addSubview(collectionView)
61 |
62 | // 添加手势进行重排
63 | let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(_:)))
64 | collectionView.addGestureRecognizer(longPressGesture)
65 | }
66 |
67 | func handleLongGesture(_ gesture: UILongPressGestureRecognizer) {
68 |
69 | switch(gesture.state) {
70 |
71 | case .began:
72 | guard let selectedIndexPath = self.collectionView.indexPathForItem(at: gesture.location(in: self.collectionView)) else {
73 | break
74 | }
75 | // 开始交互
76 | collectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
77 | case .changed:
78 | // 更新位置
79 | collectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
80 | case .ended:
81 | // 结束交互
82 | collectionView.endInteractiveMovement()
83 | default:
84 | // 默认取消交互
85 | collectionView.cancelInteractiveMovement()
86 | }
87 | }
88 |
89 | }
90 |
91 | extension InteractiveMoveViewController: UICollectionViewDataSource, UICollectionViewDelegate {
92 |
93 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
94 | return dataNumbers.count
95 | }
96 |
97 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
98 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: interactiveMoveCellID, for: indexPath) as! BaseCollectionViewCell
99 | cell.cellIndex = dataNumbers[indexPath.item]
100 | cell.backgroundColor = indexPath.item % 2 == 0 ? .purple : .lightGray
101 | return cell
102 | }
103 |
104 | /// 更新我们自己的数据源
105 | func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
106 | let number = dataNumbers.remove(at: sourceIndexPath.item)
107 | dataNumbers.insert(number, at: destinationIndexPath.item)
108 | }
109 | }
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Prefeching/PrefectchCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrefectchCollectionViewCell.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by CaoXuan on 2017/7/9.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class PrefectchCollectionViewCell: UICollectionViewCell {
12 |
13 | @IBOutlet weak var iconImageView: UIImageView!
14 | override func awakeFromNib() {
15 | super.awakeFromNib()
16 | // Initialization code
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Prefeching/PrefectchCollectionViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/Prefeching/PrefecthingCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrefecthingCollectionViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by CaoXuan on 2017/7/9.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import Kingfisher
11 |
12 |
13 | private let reuseIdentifier = "Cell"
14 |
15 | class PrefecthingCollectionViewController: UICollectionViewController {
16 |
17 | fileprivate var dataArray = ["http://img.jiaodong.net/pic/0/10/95/81/10958118_604510.jpg",
18 | "http://i01.pic.sogou.com/47907287c1f7d45b",
19 | "http://i01.pic.sogou.com/c61c287fc43cb6bf",
20 | "http://i03.pictn.sogoucdn.com/a3922ed43d6d7027",
21 | "http://i01.pictn.sogoucdn.com/400a032e4c831fb1",
22 | "http://i03.pictn.sogoucdn.com/64a055f3497f295f",
23 | "http://i04.pictn.sogoucdn.com/40778a743abae44a",
24 | "http://i02.pictn.sogoucdn.com/a20381b4283dc77d",
25 | "http://i01.pictn.sogoucdn.com/4120d236bb1627b3",
26 | "http://i04.pic.sogou.com/8a7ab42259a7778e",
27 | "http://i04.pic.sogou.com/d0cc38f5775ae2dc",
28 | "http://sports.sun0769.com/photo/composite/201303/W020130331542966530065.jpg",
29 | "http://ztd00.photos.bdimg.com/ztd/w=350;q=70/sign=1d1c9b312f2dd42a5f0907ae33002a88/fd039245d688d43f4da25faa771ed21b0ef43b5b.jpg"
30 | ,
31 | "http://i02.pictn.sogoucdn.com/f0da5d1d2e399f54",
32 | "http://www.manjpg.com/uploads/allimg/140712/628-140G2151G3.jpg",
33 | "http://b.hiphotos.baidu.com/zhidao/pic/item/3b292df5e0fe992535a1545f3ca85edf8db1710b.jpg"
34 | ,
35 | "http://d.hiphotos.baidu.com/zhidao/wh%3D600%2C800/sign=a2e684445b82b2b7a7ca31c2019de7d7/622762d0f703918fa34e218a533d269758eec4d6.jpg",
36 | "http://www.laonanren.cc/uploads/allimg/160215/3-1602151642164A.jpg",
37 | "http://img3.duitang.com/uploads/item/201501/01/20150101084426_sVcze.jpeg",
38 | "http://pic22.photophoto.cn/20120113/0036036771604425_b.jpg",
39 | "http://i04.pictn.sogoucdn.com/473008194fe06391",
40 | "http://upload.mnw.cn/2014/1030/1414658148257.jpg",
41 | "http://img5.duitang.com/uploads/item/201502/23/20150223111936_XH3m8.jpeg",
42 | "http://i01.pictn.sogoucdn.com/6e7a1bfdcb65926b"
43 | ]
44 |
45 | override func viewDidLoad() {
46 | super.viewDidLoad()
47 | // 设置layout
48 | let layout = UICollectionViewFlowLayout()
49 | let margin: CGFloat = 8
50 | let itemW = (view.bounds.width - margin * 2) / 1
51 | let itemH: CGFloat = itemW
52 | layout.itemSize = CGSize(width: itemW, height: itemH)
53 | // 最小行间距
54 | layout.minimumLineSpacing = margin
55 | // 最小item之间的距离
56 | layout.minimumInteritemSpacing = margin
57 | // 每组item的边缘切距
58 | layout.sectionInset = UIEdgeInsetsMake(0, margin, 15, margin)
59 | collectionView?.collectionViewLayout = layout
60 | collectionView?.backgroundColor = .white
61 |
62 |
63 | // 注册cell和头部视图
64 | self.collectionView!.register(UINib.init(nibName: "PrefectchCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)
65 | if #available(iOS 10.0, *) {
66 | // 设置代理
67 | self.collectionView?.prefetchDataSource = self
68 | // iOS 10的预加载跟iOS9,不一样,如果要跟iOS 9 一样,需要主动设置它为false,默认为true
69 | // collectionView?.isPrefetchingEnabled = false
70 | }
71 |
72 |
73 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "清除缓存", style: .done, target: self, action: #selector(clearCache))
74 |
75 | }
76 |
77 | func clearCache() {
78 | KingfisherManager.shared.cache.clearMemoryCache()
79 | KingfisherManager.shared.cache.clearDiskCache()
80 | }
81 |
82 |
83 | // MARK: UICollectionViewDataSource
84 |
85 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
86 | return dataArray.count
87 | }
88 |
89 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
90 | let url = URL(string: dataArray[indexPath.item])
91 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! PrefectchCollectionViewCell
92 | cell.backgroundColor = .lightGray
93 |
94 | cell.iconImageView.kf.setImage(with: url)
95 | return cell
96 | }
97 | }
98 | /*
99 |
100 | */
101 |
102 | extension PrefecthingCollectionViewController: UICollectionViewDataSourcePrefetching {
103 | // 预加载,每次都会领先基本一页的数据
104 | func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
105 | let urls = indexPaths.flatMap {
106 | URL(string: dataArray[$0.item])
107 | }
108 | // 开始下载
109 | ImagePrefetcher(urls: urls).start()
110 | }
111 |
112 | // 取消预加载,会在快速滑动还没停下来时,突然往相反方向快速滑动调用,当它调用, 程序也基本不会走cellForItemAt 方法, 直接走 willDisplaycell方法
113 | func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
114 | let urls = indexPaths.flatMap {
115 | URL(string: dataArray[$0.item])
116 | }
117 | // 取消下载
118 | ImagePrefetcher(urls: urls).stop()
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/TodayNews/MoveCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoveCollectionViewCell.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/24.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MoveCollectionViewCell: UICollectionViewCell {
12 |
13 | @IBOutlet weak var removeBtn: UIButton!
14 | @IBOutlet weak var titleLabel: UILabel!
15 |
16 | override func awakeFromNib() {
17 | super.awakeFromNib()
18 | // Initialization code
19 | }
20 |
21 | func begainAnimation() {
22 | let animation = CABasicAnimation(keyPath: "transform.rotation")
23 | animation.fromValue = -Double.pi / 40
24 | animation.toValue = Double.pi / 40
25 | animation.duration = 0.15
26 | animation.isRemovedOnCompletion = false
27 | animation.repeatCount = MAXFLOAT
28 | animation.autoreverses = true
29 | layer.add(animation, forKey: "animation")
30 | }
31 |
32 | func stopAnimation() {
33 | layer.removeAllAnimations()
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/TodayNews/MoveCollectionViewCell.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
33 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/TodayNews/MoveCollectionViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoveCollectionViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/7/7.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let cellReuseIdentifier = "Cell"
12 | private let headerReuseIdentifier = "Cell"
13 |
14 | class MoveCollectionViewController: UICollectionViewController {
15 |
16 | /// 手指按住collectionView的位置
17 | fileprivate var fingerLocation: CGPoint!
18 |
19 | /// 手指开始长按时对应的IndexPath,可能为nil
20 | fileprivate var originalIndexPath: IndexPath?
21 |
22 | /// 手指移动时对应的IndexPath,可能为nil
23 | fileprivate var relocatedIndexPath: IndexPath?
24 |
25 | /// 截屏View
26 | fileprivate var snapshot: UIView?
27 |
28 | /// 是否进入编辑状态
29 | fileprivate var isEdit: Bool = false
30 |
31 | fileprivate lazy var collectionDatas: Array = { [
32 | ["推荐", "热点", "视频", "问答", "体育", "历史", "科技", "健康", "娱乐", "时尚", "故事", "直播", "数码", "辟谣", "养生"],
33 | ["美图", "正能量", "搞笑", "文化", "小说", "语录", "深圳", "社会", "汽车", "财经", "军事", "段子", "美女", "国际", "趣图", "特卖", "房产", "育儿", "美食", "电影"]
34 | ] }()
35 |
36 | override func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | collectionView?.backgroundColor = .white
40 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "编辑", style: .done, target: self, action: #selector(rightItemClick(_:)))
41 |
42 | // 设置layout
43 | let layout = UICollectionViewFlowLayout()
44 | let margin: CGFloat = 8
45 | let itemW = (view.bounds.width - margin * 5) / 4
46 | let itemH: CGFloat = 48
47 | layout.itemSize = CGSize(width: itemW, height: itemH)
48 | // 最小行间距
49 | layout.minimumLineSpacing = margin
50 | // 最小item之间的距离
51 | layout.minimumInteritemSpacing = margin
52 | // 每组item的边缘切距
53 | layout.sectionInset = UIEdgeInsetsMake(0, margin, 15, margin)
54 | // 头部视图的大小
55 | layout.headerReferenceSize = CGSize(width: view.bounds.width, height: 50)
56 | collectionView?.collectionViewLayout = layout
57 |
58 | // 注册cell和头部视图
59 | self.collectionView!.register(UINib.init(nibName: "MoveCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: cellReuseIdentifier)
60 | self.collectionView?.register(UINib.init(nibName: "MoveHeaderCollectionReusableView", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerReuseIdentifier)
61 |
62 | // 给collectionView添加长按手势
63 | let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureRecognized(_:)))
64 | collectionView?.addGestureRecognizer(longPress)
65 |
66 | // 给collectionView添加滑动手势
67 | let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:)))
68 | panGesture.delegate = self;
69 | collectionView?.addGestureRecognizer(panGesture)
70 | }
71 |
72 | // MARK: Events
73 |
74 | func rightItemClick(_ item: UIBarButtonItem) {
75 | if navigationItem.rightBarButtonItem?.title == "编辑" {
76 | navigationItem.rightBarButtonItem?.title = "完成"
77 | isEdit = true
78 | collectionView?.reloadSections([0])
79 | } else {
80 | navigationItem.rightBarButtonItem?.title = "编辑"
81 | isEdit = false
82 | collectionView?.reloadSections([0])
83 | }
84 | }
85 |
86 | /// 给collectionView添加pan手势
87 | func panGestureRecognized(_ sender: UIPanGestureRecognizer) {
88 | guard self.isEdit else { return }
89 | handleGesture(sender)
90 | }
91 |
92 | /// 给collectionView添加长按手势
93 | func longPressGestureRecognized(_ sender: UILongPressGestureRecognizer) {
94 | handleGesture(sender)
95 | }
96 |
97 |
98 | // MARK: - Help Methods
99 |
100 | /// 处理长按和滑动时候的手势
101 | ///
102 | /// - Parameter sender: 手势
103 | func handleGesture(_ sender: UIGestureRecognizer) {
104 | let senderState = sender.state
105 | // 手指在collectionView中的位置
106 | fingerLocation = sender.location(in: collectionView)
107 | // 手指按住位置对应的indexPath,可能为nil
108 | relocatedIndexPath = collectionView?.indexPathForItem(at: fingerLocation)
109 |
110 | switch senderState {
111 | case .began:
112 | originalIndexPath = collectionView?.indexPathForItem(at: fingerLocation)
113 | guard let letOriginalIndexPath = originalIndexPath, originalIndexPath?.section == 0, snapshot == nil else {
114 | return
115 | }
116 | // 如果还没进入编辑状态,就让他进入编辑状态,cell同时显示移除按钮
117 | if !isEdit {
118 | isEdit = true
119 | navigationItem.rightBarButtonItem?.title = "完成"
120 | // 这里不用刷新,是因为如果刷新,进不能根据indexpath 找到cell
121 | for i in 0 ..< collectionDatas[0].count {
122 | let index = IndexPath(item: i, section: 0)
123 | let cell = collectionView?.cellForItem(at: index) as! MoveCollectionViewCell
124 | cell.begainAnimation()
125 | cell.removeBtn.isHidden = false
126 | }
127 | }
128 | cellSelectedAtIndexPath(letOriginalIndexPath)
129 | case .changed:
130 | guard snapshot != nil, originalIndexPath != nil else { return }
131 |
132 | // 移动cell
133 | var center = snapshot!.center
134 | center.y = fingerLocation.y
135 | center.x = fingerLocation.x
136 | snapshot!.center = center
137 |
138 | // 判断是否进入了其它cell的范围, 并且不是第二个sections
139 | guard let letrelocatedIndexPath = relocatedIndexPath, relocatedIndexPath != originalIndexPath, relocatedIndexPath?.section == 0 else {
140 | return
141 | }
142 | // 进行排序
143 | cellRelocatedToNewIndexPath(letrelocatedIndexPath)
144 |
145 | case .ended:
146 | guard let letOriginalIndexPath = originalIndexPath, snapshot != nil else {
147 | return
148 | }
149 | let attributes = collectionView?.layoutAttributesForSupplementaryElement(ofKind: UICollectionElementKindSectionHeader, at: IndexPath(item: 0, section: 1))
150 | // 如果在第二组的位置,让它移动到第二组
151 | if fingerLocation.y > attributes!.frame.maxY {
152 | collectionView?.performBatchUpdates({
153 | self.betweenSectionsMoveCell(with: letOriginalIndexPath)
154 | }, completion: { (_) in
155 | // self.collectionView?.reloadData()
156 | self.didEndDraging()
157 |
158 | })
159 | } else {
160 | didEndDraging()
161 | }
162 |
163 | default:
164 | break
165 | }
166 | }
167 |
168 |
169 | /// 在不同sections之间的cell移动
170 | ///
171 | /// - Parameter indexPath: 需要移动indexPath
172 | func betweenSectionsMoveCell(with indexPath: IndexPath) {
173 | let obj = self.collectionDatas[indexPath.section][indexPath.item]
174 | self.collectionDatas[indexPath.section].remove(at: indexPath.item)
175 | var anotherIndexPath = IndexPath(item: 0, section: 1)
176 | if indexPath.section == 1 {
177 | anotherIndexPath = IndexPath(item: self.collectionDatas[0].count, section: 0)
178 | }
179 | self.collectionDatas[anotherIndexPath.section].insert(obj, at: anotherIndexPath.item)
180 | self.collectionView?.moveItem(at: indexPath, to: anotherIndexPath)
181 | originalIndexPath = anotherIndexPath;
182 | }
183 |
184 | /// 拖动结束,显示cell,并移除截图
185 | func didEndDraging() {
186 | guard let letoriginalIndexPath = originalIndexPath else { return }
187 | let cell = collectionView?.cellForItem(at: letoriginalIndexPath) as? MoveCollectionViewCell
188 | cell?.isHidden = false
189 | cell?.alpha = 0
190 | UIView.animate(withDuration: 0.2, animations: {
191 | self.snapshot!.center = cell!.center
192 | self.snapshot!.alpha = 0
193 | self.snapshot!.transform = .identity
194 | cell?.alpha = 1
195 | if letoriginalIndexPath.section == 1 {
196 | cell?.removeBtn.isHidden = true
197 | cell?.stopAnimation()
198 | }
199 | }) { (_) in
200 | self.snapshot!.removeFromSuperview()
201 | self.snapshot = nil
202 | self.originalIndexPath = nil
203 | self.relocatedIndexPath = nil
204 | }
205 |
206 | }
207 |
208 | /// 移动cell,并更新数据源
209 | func cellRelocatedToNewIndexPath(_ indexPath: IndexPath) {
210 |
211 | if indexPath.section == 0 {
212 | // 移动的时候更新数据源
213 | let obj = collectionDatas[0][originalIndexPath!.item]
214 | collectionDatas[0].remove(at: originalIndexPath!.item)
215 | collectionDatas[0].insert(obj, at: indexPath.item)
216 |
217 | collectionView?.moveItem(at: originalIndexPath!, to: indexPath)
218 |
219 | //更新cell的原始indexPath为当前indexPath
220 | originalIndexPath = indexPath;
221 | }
222 | }
223 |
224 | /// 隐藏cell, 并显示截图
225 | func cellSelectedAtIndexPath(_ indexPath: IndexPath) {
226 | let cell = collectionView?.cellForItem(at: indexPath)
227 | snapshot = customSnapshotFromView(cell!)
228 | collectionView?.addSubview(snapshot!)
229 | cell?.isHidden = true
230 | var center = snapshot!.center
231 | center.y = fingerLocation.y
232 | UIView.animate(withDuration: 0.2) {
233 | self.snapshot!.transform = CGAffineTransform(scaleX: 1.03, y: 1.03)
234 | self.snapshot!.alpha = 0.98
235 | self.snapshot!.center = center
236 | }
237 | }
238 |
239 |
240 | /// 返回一个截图
241 | func customSnapshotFromView(_ inputView: UIView) -> UIView {
242 | // 生成一张图片
243 | UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0)
244 | inputView.layer.render(in: UIGraphicsGetCurrentContext()!)
245 | let image = UIGraphicsGetImageFromCurrentImageContext()
246 | UIGraphicsEndImageContext()
247 |
248 | // 生成View
249 | let snapshot = UIImageView(image: image)
250 | snapshot.center = inputView.center
251 | snapshot.alpha = 0.6
252 | snapshot.layer.masksToBounds = true
253 | snapshot.layer.cornerRadius = 0.0
254 | return snapshot
255 | }
256 | }
257 |
258 | // MARK: - UIGestureRecognizerDelegate
259 | extension MoveCollectionViewController: UIGestureRecognizerDelegate {
260 | func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
261 | let sender = gestureRecognizer as! UIPanGestureRecognizer
262 | let trnslationPoint = sender.translation(in: collectionView)
263 | // 结束动画有时间,扫的手势很容易出问题,需要保证 snapshot == nil,才让它开始,pan手势结束和开始可能会特别快,需要格外留心,为了保证pan手势不影响collectionView的竖直滑动,竖直方向偏移不让它开始
264 | if abs(trnslationPoint.x) > 0.2 && snapshot == nil {
265 | return true
266 | }
267 | return false
268 | }
269 | }
270 |
271 | // MARK: - UICollectionViewDelegate
272 | extension MoveCollectionViewController {
273 | override func numberOfSections(in collectionView: UICollectionView) -> Int {
274 | return collectionDatas.count
275 | }
276 |
277 |
278 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
279 | return collectionDatas[section].count
280 | }
281 |
282 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
283 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath) as! MoveCollectionViewCell
284 | cell.titleLabel.text = collectionDatas[indexPath.section][indexPath.item]
285 |
286 | /// 进入编辑状态,第一组显示删除
287 | if isEdit && indexPath.section == 0 {
288 | cell.removeBtn.isHidden = false
289 | cell.begainAnimation()
290 | } else {
291 | cell.removeBtn.isHidden = true
292 | }
293 | return cell
294 | }
295 |
296 | /// 头部视图
297 | override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
298 | let headReuseView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerReuseIdentifier, for: indexPath) as! MoveHeaderCollectionReusableView
299 | if indexPath.section == 0 {
300 | headReuseView.titleLabel.text = "我的频道"
301 | headReuseView.detaiLabel.text = "拖拽可以排序"
302 | } else {
303 | headReuseView.titleLabel.text = "频道推荐"
304 | headReuseView.detaiLabel.text = "点击添加频道"
305 | }
306 | return headReuseView
307 | }
308 |
309 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
310 | // 如果不是编辑状态,不需要
311 | if !isEdit && indexPath.section == 0 {
312 | return
313 | }
314 |
315 | /// 先移动数据, 后刷新
316 | collectionView.performBatchUpdates({
317 | self.betweenSectionsMoveCell(with: indexPath)
318 | }, completion: { (_) in
319 | collectionView.reloadItems(at: [self.originalIndexPath!])
320 | })
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/TodayNews/MoveHeaderCollectionReusableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoveHeaderCollectionReusableView.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/7/7.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MoveHeaderCollectionReusableView: UICollectionReusableView {
12 |
13 | @IBOutlet weak var titleLabel: UILabel!
14 |
15 | @IBOutlet weak var detaiLabel: UILabel!
16 |
17 | override func awakeFromNib() {
18 | super.awakeFromNib()
19 | // Initialization code
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/TodayNews/MoveHeaderCollectionReusableView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/19.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UITableViewController {
12 |
13 | let data: [[String: Any]] = [
14 | ["text": "基本使用", "class": "BaseCollectionViewController"],
15 | ["text": "瀑布流", "class": "WaterFlowViewController"],
16 | ["text": "每页多个Cell水平滚动布局", "class": "HorizontalCollectionViewController"],
17 | ["text": "CoverFlow效果", "class": "CoverFlowViewController"],
18 | ["text": "轮转卡片", "class": "CircularCollectionViewController"],
19 | ["text": "模仿今日头条实现Cell重排", "class": "MoveCollectionViewController"],
20 | ["text": "iOS9用系统属性实现Cell重排", "class": "InteractiveMoveViewController"],
21 | ["text": "iOS10预加载", "class": "PrefecthingCollectionViewController"]
22 | ]
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | }
27 |
28 |
29 | /// 类文件字符串转换为ViewController
30 | ///
31 | /// - Parameter childControllerName: VC的字符串
32 | /// - Returns: ViewController
33 | func convertController(_ childControllerName: String) -> UIViewController? {
34 |
35 | // 1.获取命名空间
36 | // 通过字典的键来取值,如果键名不存在,那么取出来的值有可能就为没值.所以通过字典取出的值的类型为AnyObject?
37 | guard let clsName = Bundle.main.infoDictionary!["CFBundleExecutable"] else {
38 | return nil
39 | }
40 | // 2.通过命名空间和类名转换成类
41 | let cls : AnyClass? = NSClassFromString((clsName as! String) + "." + childControllerName)
42 |
43 | // swift 中通过Class创建一个对象,必须告诉系统Class的类型
44 | guard let clsType = cls as? UIViewController.Type else {
45 | return nil
46 | }
47 |
48 | if clsType is UICollectionViewController.Type {
49 | let coll = clsType as! UICollectionViewController.Type
50 | let collCls = coll.init(collectionViewLayout: CircularCollectionViewLayout())
51 | return collCls
52 | }
53 |
54 | // 3.通过Class创建对象
55 | let childController = clsType.init()
56 |
57 | return childController
58 | }
59 | }
60 |
61 | // MARK: - 代理
62 | extension ViewController {
63 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
64 | return data.count
65 | }
66 |
67 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
68 | let cell = tableView.dequeueReusableCell(withIdentifier: "collectionCell", for: indexPath)
69 | cell.textLabel?.text = data[indexPath.row]["text"] as? String
70 | return cell
71 | }
72 |
73 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
74 | let className = data[indexPath.row]["class"] as! String
75 | let cls = convertController(className)!
76 | navigationController?.pushViewController(cls, animated: true)
77 | }
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/WaterFlow/WaterFlowLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WaterFlowLayout.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/20.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol WaterFlowLayoutDelegate: NSObjectProtocol {
12 | func waterFlowLayout(_ waterFlowLayout: WaterFlowLayout, itemHeightAt indexPath: IndexPath) -> CGFloat
13 | }
14 |
15 | class WaterFlowLayout: UICollectionViewFlowLayout {
16 |
17 | weak var delegate: WaterFlowLayoutDelegate?
18 |
19 | var cols = 4 // 列数
20 |
21 | /// 布局frame数组
22 | fileprivate lazy var layoutAttributeArray: [UICollectionViewLayoutAttributes] = []
23 |
24 | /// 每列的高度
25 | fileprivate lazy var yArray: [CGFloat] = Array(repeating: self.sectionInset.top, count: self.cols)
26 |
27 | fileprivate var maxHeight: CGFloat = 0
28 |
29 | override func prepare() {
30 | // 刷新时会重新调用次方法
31 | super.prepare()
32 | waterLayout()
33 | }
34 |
35 | /// 布局瀑布流
36 | private func waterLayout() {
37 | let itemW = (collectionView!.bounds.width - sectionInset.left - sectionInset.right - minimumInteritemSpacing * CGFloat(cols - 1)) / CGFloat(cols)
38 |
39 | let itemCount = collectionView!.numberOfItems(inSection: 0)
40 |
41 | // 最小高度的那一个的索引
42 | var minHeightIndex = 0
43 |
44 | // 从 layoutAttributeArray.count 开始,避免重复加载
45 | for j in layoutAttributeArray.count ..< itemCount {
46 | let indexPath = IndexPath(item: j, section: 0)
47 | let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath)
48 |
49 | let itemH = delegate?.waterFlowLayout(self, itemHeightAt: indexPath)
50 |
51 | // 找出最小高度的那一列
52 | let value = yArray.min()
53 | minHeightIndex = yArray.index(of: value!)!
54 |
55 | var itemY = yArray[minHeightIndex]
56 | // 大于第一行的高度才相加
57 | if j >= cols {
58 | itemY += minimumInteritemSpacing
59 | }
60 |
61 | let itemX = sectionInset.left + (itemW + minimumInteritemSpacing) * CGFloat(minHeightIndex)
62 |
63 | attr.frame = CGRect(x: itemX, y: itemY, width: itemW, height: CGFloat(itemH!))
64 | layoutAttributeArray.append(attr)
65 | // 重新设置高度
66 | yArray[minHeightIndex] = attr.frame.maxY
67 | }
68 | maxHeight = yArray.max()! + sectionInset.bottom
69 | }
70 | }
71 |
72 | extension WaterFlowLayout {
73 | override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
74 | // 找出相交的那些,别全部返回
75 | // return layoutAttributeArray
76 |
77 | return layoutAttributeArray.filter { $0.frame.intersects(rect)}
78 | }
79 |
80 | override var collectionViewContentSize: CGSize {
81 | // 这里宽度不能设置为0
82 | return CGSize(width: collectionView!.bounds.width, height: maxHeight)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemo/WaterFlow/WaterFlowViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WaterFlowViewController.swift
3 | // UICollectionViewDemo
4 | //
5 | // Created by alenpaulkevin on 2017/6/20.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | private let baseCellID = "baseCellID"
12 | class WaterFlowViewController: UIViewController {
13 |
14 | var itemCount: Int = 30
15 |
16 | var collectionView: UICollectionView!
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | setUpView()
21 | }
22 |
23 | private func setUpView() {
24 | // 创建flowLayout对象
25 | let layout = WaterFlowLayout()
26 | layout.delegate = self
27 | // 设置列数
28 | layout.cols = 3
29 |
30 | let margin: CGFloat = 8
31 | layout.minimumLineSpacing = margin
32 | layout.minimumInteritemSpacing = margin
33 | layout.sectionInset = UIEdgeInsetsMake(0, margin, 0, margin)
34 | // 创建collection
35 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
36 | collectionView.backgroundColor = .white
37 | collectionView.dataSource = self
38 |
39 | // 注册cell
40 | collectionView.register(BaseCollectionViewCell.self, forCellWithReuseIdentifier: baseCellID)
41 | view.addSubview(collectionView)
42 | }
43 | }
44 |
45 | extension WaterFlowViewController: UICollectionViewDataSource {
46 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
47 | return itemCount
48 | }
49 |
50 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
51 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: baseCellID, for: indexPath) as! BaseCollectionViewCell
52 | cell.cellIndex = indexPath.item
53 | cell.backgroundColor = indexPath.item % 2 == 0 ? .purple : .lightGray
54 | if itemCount - 1 == indexPath.item {
55 | itemCount += 20
56 | collectionView.reloadData()
57 | }
58 | return cell
59 | }
60 | }
61 |
62 | extension WaterFlowViewController: WaterFlowLayoutDelegate {
63 | func waterFlowLayout(_ waterFlowLayout: WaterFlowLayout, itemHeightAt indexPath: IndexPath) -> CGFloat {
64 | return CGFloat(arc4random_uniform(150) + 50)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemoTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemoTests/UICollectionViewDemoTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionViewDemoTests.swift
3 | // UICollectionViewDemoTests
4 | //
5 | // Created by alenpaulkevin on 2017/6/19.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import UICollectionViewDemo
11 |
12 | class UICollectionViewDemoTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemoUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/UICollectionViewDemo/UICollectionViewDemoUITests/UICollectionViewDemoUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UICollectionViewDemoUITests.swift
3 | // UICollectionViewDemoUITests
4 | //
5 | // Created by alenpaulkevin on 2017/6/19.
6 | // Copyright © 2017年 alenpaulkevin. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class UICollectionViewDemoUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 |
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 |
18 | // In UI tests it is usually best to stop immediately when a failure occurs.
19 | continueAfterFailure = false
20 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
21 | XCUIApplication().launch()
22 |
23 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
24 | }
25 |
26 | override func tearDown() {
27 | // Put teardown code here. This method is called after the invocation of each test method in the class.
28 | super.tearDown()
29 | }
30 |
31 | func testExample() {
32 | // Use recording to get started writing UI tests.
33 | // Use XCTAssert and related functions to verify your tests produce the correct results.
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------