AutorÃa | Ultima modificación | Ver Log |
//// UIImageView+AlamofireImage.swift//// Copyright (c) 2015 Alamofire Software Foundation (http://alamofire.org/)//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.//import Alamofireimport Foundation#if os(iOS) || os(tvOS)import UIKitpublic typealias AnimationOptions = UIView.AnimationOptionsextension UIImageView {/// Used to wrap all `UIView` animation transition options alongside a duration.public enum ImageTransition {case noTransitioncase crossDissolve(TimeInterval)case curlDown(TimeInterval)case curlUp(TimeInterval)case flipFromBottom(TimeInterval)case flipFromLeft(TimeInterval)case flipFromRight(TimeInterval)case flipFromTop(TimeInterval)case custom(duration: TimeInterval,animationOptions: AnimationOptions,animations: (UIImageView, Image) -> Void,completion: ((Bool) -> Void)?)/// The duration of the image transition in seconds.public var duration: TimeInterval {switch self {case .noTransition:return 0.0case let .crossDissolve(duration):return durationcase let .curlDown(duration):return durationcase let .curlUp(duration):return durationcase let .flipFromBottom(duration):return durationcase let .flipFromLeft(duration):return durationcase let .flipFromRight(duration):return durationcase let .flipFromTop(duration):return durationcase let .custom(duration, _, _, _):return duration}}/// The animation options of the image transition.public var animationOptions: AnimationOptions {switch self {case .noTransition:return []case .crossDissolve:return .transitionCrossDissolvecase .curlDown:return .transitionCurlDowncase .curlUp:return .transitionCurlUpcase .flipFromBottom:return .transitionFlipFromBottomcase .flipFromLeft:return .transitionFlipFromLeftcase .flipFromRight:return .transitionFlipFromRightcase .flipFromTop:return .transitionFlipFromTopcase let .custom(_, animationOptions, _, _):return animationOptions}}/// The animation options of the image transition.public var animations: (UIImageView, Image) -> Void {switch self {case let .custom(_, _, animations, _):return animationsdefault:return { $0.image = $1 }}}/// The completion closure associated with the image transition.public var completion: ((Bool) -> Void)? {switch self {case let .custom(_, _, _, completion):return completiondefault:return nil}}}}// MARK: -extension UIImageView: AlamofireExtended {}extension AlamofireExtension where ExtendedType: UIImageView {// MARK: - Properties/// The instance image downloader used to download all images. If this property is `nil`, the `UIImageView` will/// fallback on the `sharedImageDownloader` for all downloads. The most common use case for needing to use a custom/// instance image downloader is when images are behind different basic auth credentials.public var imageDownloader: ImageDownloader? {get {objc_getAssociatedObject(type, &AssociatedKeys.imageDownloader) as? ImageDownloader}nonmutating set(downloader) {objc_setAssociatedObject(type, &AssociatedKeys.imageDownloader, downloader, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}}/// The shared image downloader used to download all images. By default, this is the default `ImageDownloader`/// instance backed with an `AutoPurgingImageCache` which automatically evicts images from the cache when the memory/// capacity is reached or memory warning notifications occur. The shared image downloader is only used if the/// `imageDownloader` is `nil`.public static var sharedImageDownloader: ImageDownloader {get {if let downloader = objc_getAssociatedObject(UIImageView.self, &AssociatedKeys.sharedImageDownloader) as? ImageDownloader {return downloader} else {return ImageDownloader.default}}set {objc_setAssociatedObject(UIImageView.self, &AssociatedKeys.sharedImageDownloader, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}}var activeRequestReceipt: RequestReceipt? {get {objc_getAssociatedObject(type, &AssociatedKeys.activeRequestReceipt) as? RequestReceipt}nonmutating set {objc_setAssociatedObject(type, &AssociatedKeys.activeRequestReceipt, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}}// MARK: - Image Download/// Asynchronously downloads an image from the specified URL, applies the specified image filter to the downloaded/// image and sets it once finished while executing the image transition.////// If the image is cached locally, the image is set immediately. Otherwise the specified placeholder image will be/// set immediately, and then the remote image will be set once the image request is finished.////// The `completion` closure is called after the image download and filtering are complete, but before the start of/// the image transition. Please note it is no longer the responsibility of the `completion` closure to set the/// image. It will be set automatically. If you require a second notification after the image transition completes,/// use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when/// the image transition is finished.////// - parameter url: The URL used for the image request./// - parameter cacheKey: An optional key used to identify the image in the cache. Defaults/// to `nil`./// - parameter placeholderImage: The image to be set initially until the image request finished. If/// `nil`, the image view will not change its image until the image/// request finishes. Defaults to `nil`./// - parameter serializer: Image response serializer used to convert the image data to `UIImage`./// Defaults to `nil` which will fall back to the/// instance `imageResponseSerializer` set on the `ImageDownloader`./// - parameter filter: The image filter applied to the image after the image request is/// finished. Defaults to `nil`./// - parameter progress: The closure to be executed periodically during the lifecycle of the/// request. Defaults to `nil`./// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the/// main queue./// - parameter imageTransition: The image transition animation applied to the image when set./// Defaults to `.None`./// - parameter runImageTransitionIfCached: Whether to run the image transition if the image is cached. Defaults/// to `false`./// - parameter completion: A closure to be executed when the image request finishes. The closure/// has no return value and takes three arguments: the original request,/// the response from the server and the result containing either the/// image or the error that occurred. If the image was returned from the/// image cache, the response will be `nil`. Defaults to `nil`.public func setImage(withURL url: URL,cacheKey: String? = nil,placeholderImage: UIImage? = nil,serializer: ImageResponseSerializer? = nil,filter: ImageFilter? = nil,progress: ImageDownloader.ProgressHandler? = nil,progressQueue: DispatchQueue = DispatchQueue.main,imageTransition: UIImageView.ImageTransition = .noTransition,runImageTransitionIfCached: Bool = false,completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {setImage(withURLRequest: urlRequest(with: url),cacheKey: cacheKey,placeholderImage: placeholderImage,serializer: serializer,filter: filter,progress: progress,progressQueue: progressQueue,imageTransition: imageTransition,runImageTransitionIfCached: runImageTransitionIfCached,completion: completion)}/// Asynchronously downloads an image from the specified URL Request, applies the specified image filter to the downloaded/// image and sets it once finished while executing the image transition.////// If the image is cached locally, the image is set immediately. Otherwise the specified placeholder image will be/// set immediately, and then the remote image will be set once the image request is finished.////// The `completion` closure is called after the image download and filtering are complete, but before the start of/// the image transition. Please note it is no longer the responsibility of the `completion` closure to set the/// image. It will be set automatically. If you require a second notification after the image transition completes,/// use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when/// the image transition is finished.////// - parameter urlRequest: The URL request./// - parameter cacheKey: An optional key used to identify the image in the cache. Defaults/// to `nil`./// - parameter placeholderImage: The image to be set initially until the image request finished. If/// `nil`, the image view will not change its image until the image/// request finishes. Defaults to `nil`./// - parameter serializer: Image response serializer used to convert the image data to `UIImage`./// Defaults to `nil` which will fall back to the/// instance `imageResponseSerializer` set on the `ImageDownloader`./// - parameter filter: The image filter applied to the image after the image request is/// finished. Defaults to `nil`./// - parameter progress: The closure to be executed periodically during the lifecycle of the/// request. Defaults to `nil`./// - parameter progressQueue: The dispatch queue to call the progress closure on. Defaults to the/// main queue./// - parameter imageTransition: The image transition animation applied to the image when set./// Defaults to `.None`./// - parameter runImageTransitionIfCached: Whether to run the image transition if the image is cached. Defaults/// to `false`./// - parameter completion: A closure to be executed when the image request finishes. The closure/// has no return value and takes three arguments: the original request,/// the response from the server and the result containing either the/// image or the error that occurred. If the image was returned from the/// image cache, the response will be `nil`. Defaults to `nil`.public func setImage(withURLRequest urlRequest: URLRequestConvertible,cacheKey: String? = nil,placeholderImage: UIImage? = nil,serializer: ImageResponseSerializer? = nil,filter: ImageFilter? = nil,progress: ImageDownloader.ProgressHandler? = nil,progressQueue: DispatchQueue = DispatchQueue.main,imageTransition: UIImageView.ImageTransition = .noTransition,runImageTransitionIfCached: Bool = false,completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {guard !isURLRequestURLEqualToActiveRequestURL(urlRequest) else {let response = AFIDataResponse<UIImage>(request: nil,response: nil,data: nil,metrics: nil,serializationDuration: 0.0,result: .failure(AFIError.requestCancelled))completion?(response)return}cancelImageRequest()let imageDownloader = self.imageDownloader ?? UIImageView.af.sharedImageDownloaderlet imageCache = imageDownloader.imageCache// Use the image from the image cache if it existsif let request = urlRequest.urlRequest {let cachedImage: Image?if let cacheKey = cacheKey {cachedImage = imageCache?.image(withIdentifier: cacheKey)} else {cachedImage = imageCache?.image(for: request, withIdentifier: filter?.identifier)}if let image = cachedImage {let response = AFIDataResponse<UIImage>(request: request,response: nil,data: nil,metrics: nil,serializationDuration: 0.0,result: .success(image))if runImageTransitionIfCached {// It's important to display the placeholder image again otherwise you have some odd disparity// between the request loading from the cache and those that download. It's important to keep// the same behavior between both, otherwise the user can actually see the difference.if let placeholderImage = placeholderImage { type.image = placeholderImage }// Need to let the runloop cycle for the placeholder image to take affectDispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {// Added this additional check to ensure another request didn't get in during the delayguard self.activeRequestReceipt == nil else { return }self.run(imageTransition, with: image)completion?(response)}} else {type.image = imagecompletion?(response)}return}}// Set the placeholder since we're going to have to downloadif let placeholderImage = placeholderImage { type.image = placeholderImage }// Generate a unique download id to check whether the active request has changed while downloadinglet downloadID = UUID().uuidString// Weakify the image view to allow it to go out-of-memory while download is running if deallocatedweak var imageView = type// Download the image, then run the image transition or completion handlerlet requestReceipt = imageDownloader.download(urlRequest,cacheKey: cacheKey,receiptID: downloadID,serializer: serializer,filter: filter,progress: progress,progressQueue: progressQueue,completion: { response inguardlet strongSelf = imageView?.af,strongSelf.isURLRequestURLEqualToActiveRequestURL(response.request) &&strongSelf.activeRequestReceipt?.receiptID == downloadIDelse {completion?(response)return}if case let .success(image) = response.result {strongSelf.run(imageTransition, with: image)}strongSelf.activeRequestReceipt = nilcompletion?(response)})activeRequestReceipt = requestReceipt}// MARK: - Image Download Cancellation/// Cancels the active download request, if one exists.public func cancelImageRequest() {guard let activeRequestReceipt = activeRequestReceipt else { return }let imageDownloader = self.imageDownloader ?? UIImageView.af.sharedImageDownloaderimageDownloader.cancelRequest(with: activeRequestReceipt)self.activeRequestReceipt = nil}// MARK: - Image Transition/// Runs the image transition on the image view with the specified image.////// - parameter imageTransition: The image transition to ran on the image view./// - parameter image: The image to use for the image transition.public func run(_ imageTransition: UIImageView.ImageTransition, with image: Image) {let imageView = typeUIView.transition(with: type,duration: imageTransition.duration,options: imageTransition.animationOptions,animations: { imageTransition.animations(imageView, image) },completion: imageTransition.completion)}// MARK: - Private - URL Request Helper Methodsprivate func urlRequest(with url: URL) -> URLRequest {var urlRequest = URLRequest(url: url)for mimeType in ImageResponseSerializer.acceptableImageContentTypes.sorted() {urlRequest.addValue(mimeType, forHTTPHeaderField: "Accept")}return urlRequest}private func isURLRequestURLEqualToActiveRequestURL(_ urlRequest: URLRequestConvertible?) -> Bool {iflet currentRequestURL = activeRequestReceipt?.request.task?.originalRequest?.url,let requestURL = urlRequest?.urlRequest?.url,currentRequestURL == requestURL {return true}return false}}// MARK: - Deprecatedextension UIImageView {@available(*, deprecated, message: "Replaced by `imageView.af.imageDownloader`")public var af_imageDownloader: ImageDownloader? {get { af.imageDownloader }set { af.imageDownloader = newValue }}@available(*, deprecated, message: "Replaced by `imageView.af.sharedImageDownloader`")public class var af_sharedImageDownloader: ImageDownloader {get { af.sharedImageDownloader }set { af.sharedImageDownloader = newValue }}@available(*, deprecated, message: "Replaced by `imageView.af.setImage(withURL: ...)`")public func af_setImage(withURL url: URL,cacheKey: String? = nil,placeholderImage: UIImage? = nil,serializer: ImageResponseSerializer? = nil,filter: ImageFilter? = nil,progress: ImageDownloader.ProgressHandler? = nil,progressQueue: DispatchQueue = DispatchQueue.main,imageTransition: ImageTransition = .noTransition,runImageTransitionIfCached: Bool = false,completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {af.setImage(withURL: url,cacheKey: cacheKey,placeholderImage: placeholderImage,serializer: serializer,filter: filter,progress: progress,progressQueue: progressQueue,imageTransition: imageTransition,runImageTransitionIfCached: runImageTransitionIfCached,completion: completion)}@available(*, deprecated, message: "Replaced by `imageView.af.setImage(withURLRequest: ...)`")public func af_setImage(withURLRequest urlRequest: URLRequestConvertible,cacheKey: String? = nil,placeholderImage: UIImage? = nil,serializer: ImageResponseSerializer? = nil,filter: ImageFilter? = nil,progress: ImageDownloader.ProgressHandler? = nil,progressQueue: DispatchQueue = DispatchQueue.main,imageTransition: ImageTransition = .noTransition,runImageTransitionIfCached: Bool = false,completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {af.setImage(withURLRequest: urlRequest,cacheKey: cacheKey,placeholderImage: placeholderImage,serializer: serializer,filter: filter,progress: progress,progressQueue: progressQueue,imageTransition: imageTransition,runImageTransitionIfCached: runImageTransitionIfCached,completion: completion)}@available(*, deprecated, message: "Replaced by `imageView.af.cancelImageRequest()`")public func af_cancelImageRequest() {af.cancelImageRequest()}@available(*, deprecated, message: "Replaced by `imageView.af.run(_:with:)`")public func run(_ imageTransition: ImageTransition, with image: Image) {af.run(imageTransition, with: image)}}// MARK: -private enum AssociatedKeys {static var imageDownloader = "UIImageView.af.imageDownloader"static var sharedImageDownloader = "UIImageView.af.sharedImageDownloader"static var activeRequestReceipt = "UIImageView.af.activeRequestReceipt"}#endif