├── .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 | Kingfisher 4 | 5 |

6 | 7 |

8 | 9 | 10 | 11 | 12 | 13 | 14 | codebeat badge 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 | --------------------------------------------------------------------------------