Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  UIImageView+AlamofireImage.swift
3
//
4
//  Copyright (c) 2015 Alamofire Software Foundation (http://alamofire.org/)
5
//
6
//  Permission is hereby granted, free of charge, to any person obtaining a copy
7
//  of this software and associated documentation files (the "Software"), to deal
8
//  in the Software without restriction, including without limitation the rights
9
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
//  copies of the Software, and to permit persons to whom the Software is
11
//  furnished to do so, subject to the following conditions:
12
//
13
//  The above copyright notice and this permission notice shall be included in
14
//  all copies or substantial portions of the Software.
15
//
16
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
//  THE SOFTWARE.
23
//
24
 
25
import Alamofire
26
import Foundation
27
 
28
#if os(iOS) || os(tvOS)
29
 
30
import UIKit
31
 
32
public typealias AnimationOptions = UIView.AnimationOptions
33
 
34
extension UIImageView {
35
    /// Used to wrap all `UIView` animation transition options alongside a duration.
36
    public enum ImageTransition {
37
        case noTransition
38
        case crossDissolve(TimeInterval)
39
        case curlDown(TimeInterval)
40
        case curlUp(TimeInterval)
41
        case flipFromBottom(TimeInterval)
42
        case flipFromLeft(TimeInterval)
43
        case flipFromRight(TimeInterval)
44
        case flipFromTop(TimeInterval)
45
        case custom(duration: TimeInterval,
46
                    animationOptions: AnimationOptions,
47
                    animations: (UIImageView, Image) -> Void,
48
                    completion: ((Bool) -> Void)?)
49
 
50
        /// The duration of the image transition in seconds.
51
        public var duration: TimeInterval {
52
            switch self {
53
            case .noTransition:
54
                return 0.0
55
            case let .crossDissolve(duration):
56
                return duration
57
            case let .curlDown(duration):
58
                return duration
59
            case let .curlUp(duration):
60
                return duration
61
            case let .flipFromBottom(duration):
62
                return duration
63
            case let .flipFromLeft(duration):
64
                return duration
65
            case let .flipFromRight(duration):
66
                return duration
67
            case let .flipFromTop(duration):
68
                return duration
69
            case let .custom(duration, _, _, _):
70
                return duration
71
            }
72
        }
73
 
74
        /// The animation options of the image transition.
75
        public var animationOptions: AnimationOptions {
76
            switch self {
77
            case .noTransition:
78
                return []
79
            case .crossDissolve:
80
                return .transitionCrossDissolve
81
            case .curlDown:
82
                return .transitionCurlDown
83
            case .curlUp:
84
                return .transitionCurlUp
85
            case .flipFromBottom:
86
                return .transitionFlipFromBottom
87
            case .flipFromLeft:
88
                return .transitionFlipFromLeft
89
            case .flipFromRight:
90
                return .transitionFlipFromRight
91
            case .flipFromTop:
92
                return .transitionFlipFromTop
93
            case let .custom(_, animationOptions, _, _):
94
                return animationOptions
95
            }
96
        }
97
 
98
        /// The animation options of the image transition.
99
        public var animations: (UIImageView, Image) -> Void {
100
            switch self {
101
            case let .custom(_, _, animations, _):
102
                return animations
103
            default:
104
                return { $0.image = $1 }
105
            }
106
        }
107
 
108
        /// The completion closure associated with the image transition.
109
        public var completion: ((Bool) -> Void)? {
110
            switch self {
111
            case let .custom(_, _, _, completion):
112
                return completion
113
            default:
114
                return nil
115
            }
116
        }
117
    }
118
}
119
 
120
// MARK: -
121
 
122
extension UIImageView: AlamofireExtended {}
123
extension AlamofireExtension where ExtendedType: UIImageView {
124
    // MARK: - Properties
125
 
126
    /// The instance image downloader used to download all images. If this property is `nil`, the `UIImageView` will
127
    /// fallback on the `sharedImageDownloader` for all downloads. The most common use case for needing to use a custom
128
    /// instance image downloader is when images are behind different basic auth credentials.
129
    public var imageDownloader: ImageDownloader? {
130
        get {
131
            objc_getAssociatedObject(type, &AssociatedKeys.imageDownloader) as? ImageDownloader
132
        }
133
        nonmutating set(downloader) {
134
            objc_setAssociatedObject(type, &AssociatedKeys.imageDownloader, downloader, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
135
        }
136
    }
137
 
138
    /// The shared image downloader used to download all images. By default, this is the default `ImageDownloader`
139
    /// instance backed with an `AutoPurgingImageCache` which automatically evicts images from the cache when the memory
140
    /// capacity is reached or memory warning notifications occur. The shared image downloader is only used if the
141
    /// `imageDownloader` is `nil`.
142
    public static var sharedImageDownloader: ImageDownloader {
143
        get {
144
            if let downloader = objc_getAssociatedObject(UIImageView.self, &AssociatedKeys.sharedImageDownloader) as? ImageDownloader {
145
                return downloader
146
            } else {
147
                return ImageDownloader.default
148
            }
149
        }
150
        set {
151
            objc_setAssociatedObject(UIImageView.self, &AssociatedKeys.sharedImageDownloader, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
152
        }
153
    }
154
 
155
    var activeRequestReceipt: RequestReceipt? {
156
        get {
157
            objc_getAssociatedObject(type, &AssociatedKeys.activeRequestReceipt) as? RequestReceipt
158
        }
159
        nonmutating set {
160
            objc_setAssociatedObject(type, &AssociatedKeys.activeRequestReceipt, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
161
        }
162
    }
163
 
164
    // MARK: - Image Download
165
 
166
    /// Asynchronously downloads an image from the specified URL, applies the specified image filter to the downloaded
167
    /// image and sets it once finished while executing the image transition.
168
    ///
169
    /// If the image is cached locally, the image is set immediately. Otherwise the specified placeholder image will be
170
    /// set immediately, and then the remote image will be set once the image request is finished.
171
    ///
172
    /// The `completion` closure is called after the image download and filtering are complete, but before the start of
173
    /// the image transition. Please note it is no longer the responsibility of the `completion` closure to set the
174
    /// image. It will be set automatically. If you require a second notification after the image transition completes,
175
    /// use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when
176
    /// the image transition is finished.
177
    ///
178
    /// - parameter url:                        The URL used for the image request.
179
    /// - parameter cacheKey:                   An optional key used to identify the image in the cache. Defaults
180
    ///                                         to `nil`.
181
    /// - parameter placeholderImage:           The image to be set initially until the image request finished. If
182
    ///                                         `nil`, the image view will not change its image until the image
183
    ///                                         request finishes. Defaults to `nil`.
184
    /// - parameter serializer:                 Image response serializer used to convert the image data to `UIImage`.
185
    ///                                         Defaults to `nil` which will fall back to the
186
    ///                                         instance `imageResponseSerializer` set on the `ImageDownloader`.
187
    /// - parameter filter:                     The image filter applied to the image after the image request is
188
    ///                                         finished. Defaults to `nil`.
189
    /// - parameter progress:                   The closure to be executed periodically during the lifecycle of the
190
    ///                                         request. Defaults to `nil`.
191
    /// - parameter progressQueue:              The dispatch queue to call the progress closure on. Defaults to the
192
    ///                                         main queue.
193
    /// - parameter imageTransition:            The image transition animation applied to the image when set.
194
    ///                                         Defaults to `.None`.
195
    /// - parameter runImageTransitionIfCached: Whether to run the image transition if the image is cached. Defaults
196
    ///                                         to `false`.
197
    /// - parameter completion:                 A closure to be executed when the image request finishes. The closure
198
    ///                                         has no return value and takes three arguments: the original request,
199
    ///                                         the response from the server and the result containing either the
200
    ///                                         image or the error that occurred. If the image was returned from the
201
    ///                                         image cache, the response will be `nil`. Defaults to `nil`.
202
    public func setImage(withURL url: URL,
203
                         cacheKey: String? = nil,
204
                         placeholderImage: UIImage? = nil,
205
                         serializer: ImageResponseSerializer? = nil,
206
                         filter: ImageFilter? = nil,
207
                         progress: ImageDownloader.ProgressHandler? = nil,
208
                         progressQueue: DispatchQueue = DispatchQueue.main,
209
                         imageTransition: UIImageView.ImageTransition = .noTransition,
210
                         runImageTransitionIfCached: Bool = false,
211
                         completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {
212
        setImage(withURLRequest: urlRequest(with: url),
213
                 cacheKey: cacheKey,
214
                 placeholderImage: placeholderImage,
215
                 serializer: serializer,
216
                 filter: filter,
217
                 progress: progress,
218
                 progressQueue: progressQueue,
219
                 imageTransition: imageTransition,
220
                 runImageTransitionIfCached: runImageTransitionIfCached,
221
                 completion: completion)
222
    }
223
 
224
    /// Asynchronously downloads an image from the specified URL Request, applies the specified image filter to the downloaded
225
    /// image and sets it once finished while executing the image transition.
226
    ///
227
    /// If the image is cached locally, the image is set immediately. Otherwise the specified placeholder image will be
228
    /// set immediately, and then the remote image will be set once the image request is finished.
229
    ///
230
    /// The `completion` closure is called after the image download and filtering are complete, but before the start of
231
    /// the image transition. Please note it is no longer the responsibility of the `completion` closure to set the
232
    /// image. It will be set automatically. If you require a second notification after the image transition completes,
233
    /// use a `.Custom` image transition with a `completion` closure. The `.Custom` `completion` closure is called when
234
    /// the image transition is finished.
235
    ///
236
    /// - parameter urlRequest:                 The URL request.
237
    /// - parameter cacheKey:                   An optional key used to identify the image in the cache. Defaults
238
    ///                                         to `nil`.
239
    /// - parameter placeholderImage:           The image to be set initially until the image request finished. If
240
    ///                                         `nil`, the image view will not change its image until the image
241
    ///                                         request finishes. Defaults to `nil`.
242
    /// - parameter serializer:                 Image response serializer used to convert the image data to `UIImage`.
243
    ///                                         Defaults to `nil` which will fall back to the
244
    ///                                         instance `imageResponseSerializer` set on the `ImageDownloader`.
245
    /// - parameter filter:                     The image filter applied to the image after the image request is
246
    ///                                         finished. Defaults to `nil`.
247
    /// - parameter progress:                   The closure to be executed periodically during the lifecycle of the
248
    ///                                         request. Defaults to `nil`.
249
    /// - parameter progressQueue:              The dispatch queue to call the progress closure on. Defaults to the
250
    ///                                         main queue.
251
    /// - parameter imageTransition:            The image transition animation applied to the image when set.
252
    ///                                         Defaults to `.None`.
253
    /// - parameter runImageTransitionIfCached: Whether to run the image transition if the image is cached. Defaults
254
    ///                                         to `false`.
255
    /// - parameter completion:                 A closure to be executed when the image request finishes. The closure
256
    ///                                         has no return value and takes three arguments: the original request,
257
    ///                                         the response from the server and the result containing either the
258
    ///                                         image or the error that occurred. If the image was returned from the
259
    ///                                         image cache, the response will be `nil`. Defaults to `nil`.
260
    public func setImage(withURLRequest urlRequest: URLRequestConvertible,
261
                         cacheKey: String? = nil,
262
                         placeholderImage: UIImage? = nil,
263
                         serializer: ImageResponseSerializer? = nil,
264
                         filter: ImageFilter? = nil,
265
                         progress: ImageDownloader.ProgressHandler? = nil,
266
                         progressQueue: DispatchQueue = DispatchQueue.main,
267
                         imageTransition: UIImageView.ImageTransition = .noTransition,
268
                         runImageTransitionIfCached: Bool = false,
269
                         completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {
270
        guard !isURLRequestURLEqualToActiveRequestURL(urlRequest) else {
271
            let response = AFIDataResponse<UIImage>(request: nil,
272
                                                    response: nil,
273
                                                    data: nil,
274
                                                    metrics: nil,
275
                                                    serializationDuration: 0.0,
276
                                                    result: .failure(AFIError.requestCancelled))
277
 
278
            completion?(response)
279
 
280
            return
281
        }
282
 
283
        cancelImageRequest()
284
 
285
        let imageDownloader = self.imageDownloader ?? UIImageView.af.sharedImageDownloader
286
        let imageCache = imageDownloader.imageCache
287
 
288
        // Use the image from the image cache if it exists
289
        if let request = urlRequest.urlRequest {
290
            let cachedImage: Image?
291
 
292
            if let cacheKey = cacheKey {
293
                cachedImage = imageCache?.image(withIdentifier: cacheKey)
294
            } else {
295
                cachedImage = imageCache?.image(for: request, withIdentifier: filter?.identifier)
296
            }
297
 
298
            if let image = cachedImage {
299
                let response = AFIDataResponse<UIImage>(request: request,
300
                                                        response: nil,
301
                                                        data: nil,
302
                                                        metrics: nil,
303
                                                        serializationDuration: 0.0,
304
                                                        result: .success(image))
305
 
306
                if runImageTransitionIfCached {
307
                    // It's important to display the placeholder image again otherwise you have some odd disparity
308
                    // between the request loading from the cache and those that download. It's important to keep
309
                    // the same behavior between both, otherwise the user can actually see the difference.
310
                    if let placeholderImage = placeholderImage { type.image = placeholderImage }
311
 
312
                    // Need to let the runloop cycle for the placeholder image to take affect
313
                    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1)) {
314
                        // Added this additional check to ensure another request didn't get in during the delay
315
                        guard self.activeRequestReceipt == nil else { return }
316
 
317
                        self.run(imageTransition, with: image)
318
                        completion?(response)
319
                    }
320
                } else {
321
                    type.image = image
322
                    completion?(response)
323
                }
324
 
325
                return
326
            }
327
        }
328
 
329
        // Set the placeholder since we're going to have to download
330
        if let placeholderImage = placeholderImage { type.image = placeholderImage }
331
 
332
        // Generate a unique download id to check whether the active request has changed while downloading
333
        let downloadID = UUID().uuidString
334
 
335
        // Weakify the image view to allow it to go out-of-memory while download is running if deallocated
336
        weak var imageView = type
337
 
338
        // Download the image, then run the image transition or completion handler
339
        let requestReceipt = imageDownloader.download(urlRequest,
340
                                                      cacheKey: cacheKey,
341
                                                      receiptID: downloadID,
342
                                                      serializer: serializer,
343
                                                      filter: filter,
344
                                                      progress: progress,
345
                                                      progressQueue: progressQueue,
346
                                                      completion: { response in
347
                                                          guard
348
                                                              let strongSelf = imageView?.af,
349
                                                              strongSelf.isURLRequestURLEqualToActiveRequestURL(response.request) &&
350
                                                              strongSelf.activeRequestReceipt?.receiptID == downloadID
351
                                                          else {
352
                                                              completion?(response)
353
                                                              return
354
                                                          }
355
 
356
                                                          if case let .success(image) = response.result {
357
                                                              strongSelf.run(imageTransition, with: image)
358
                                                          }
359
 
360
                                                          strongSelf.activeRequestReceipt = nil
361
 
362
                                                          completion?(response)
363
                                                      })
364
 
365
        activeRequestReceipt = requestReceipt
366
    }
367
 
368
    // MARK: - Image Download Cancellation
369
 
370
    /// Cancels the active download request, if one exists.
371
    public func cancelImageRequest() {
372
        guard let activeRequestReceipt = activeRequestReceipt else { return }
373
 
374
        let imageDownloader = self.imageDownloader ?? UIImageView.af.sharedImageDownloader
375
        imageDownloader.cancelRequest(with: activeRequestReceipt)
376
 
377
        self.activeRequestReceipt = nil
378
    }
379
 
380
    // MARK: - Image Transition
381
 
382
    /// Runs the image transition on the image view with the specified image.
383
    ///
384
    /// - parameter imageTransition: The image transition to ran on the image view.
385
    /// - parameter image:           The image to use for the image transition.
386
    public func run(_ imageTransition: UIImageView.ImageTransition, with image: Image) {
387
        let imageView = type
388
 
389
        UIView.transition(with: type,
390
                          duration: imageTransition.duration,
391
                          options: imageTransition.animationOptions,
392
                          animations: { imageTransition.animations(imageView, image) },
393
                          completion: imageTransition.completion)
394
    }
395
 
396
    // MARK: - Private - URL Request Helper Methods
397
 
398
    private func urlRequest(with url: URL) -> URLRequest {
399
        var urlRequest = URLRequest(url: url)
400
 
401
        for mimeType in ImageResponseSerializer.acceptableImageContentTypes.sorted() {
402
            urlRequest.addValue(mimeType, forHTTPHeaderField: "Accept")
403
        }
404
 
405
        return urlRequest
406
    }
407
 
408
    private func isURLRequestURLEqualToActiveRequestURL(_ urlRequest: URLRequestConvertible?) -> Bool {
409
        if
410
            let currentRequestURL = activeRequestReceipt?.request.task?.originalRequest?.url,
411
            let requestURL = urlRequest?.urlRequest?.url,
412
            currentRequestURL == requestURL {
413
            return true
414
        }
415
 
416
        return false
417
    }
418
}
419
 
420
// MARK: - Deprecated
421
 
422
extension UIImageView {
423
    @available(*, deprecated, message: "Replaced by `imageView.af.imageDownloader`")
424
    public var af_imageDownloader: ImageDownloader? {
425
        get { af.imageDownloader }
426
        set { af.imageDownloader = newValue }
427
    }
428
 
429
    @available(*, deprecated, message: "Replaced by `imageView.af.sharedImageDownloader`")
430
    public class var af_sharedImageDownloader: ImageDownloader {
431
        get { af.sharedImageDownloader }
432
        set { af.sharedImageDownloader = newValue }
433
    }
434
 
435
    @available(*, deprecated, message: "Replaced by `imageView.af.setImage(withURL: ...)`")
436
    public func af_setImage(withURL url: URL,
437
                            cacheKey: String? = nil,
438
                            placeholderImage: UIImage? = nil,
439
                            serializer: ImageResponseSerializer? = nil,
440
                            filter: ImageFilter? = nil,
441
                            progress: ImageDownloader.ProgressHandler? = nil,
442
                            progressQueue: DispatchQueue = DispatchQueue.main,
443
                            imageTransition: ImageTransition = .noTransition,
444
                            runImageTransitionIfCached: Bool = false,
445
                            completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {
446
        af.setImage(withURL: url,
447
                    cacheKey: cacheKey,
448
                    placeholderImage: placeholderImage,
449
                    serializer: serializer,
450
                    filter: filter,
451
                    progress: progress,
452
                    progressQueue: progressQueue,
453
                    imageTransition: imageTransition,
454
                    runImageTransitionIfCached: runImageTransitionIfCached,
455
                    completion: completion)
456
    }
457
 
458
    @available(*, deprecated, message: "Replaced by `imageView.af.setImage(withURLRequest: ...)`")
459
    public func af_setImage(withURLRequest urlRequest: URLRequestConvertible,
460
                            cacheKey: String? = nil,
461
                            placeholderImage: UIImage? = nil,
462
                            serializer: ImageResponseSerializer? = nil,
463
                            filter: ImageFilter? = nil,
464
                            progress: ImageDownloader.ProgressHandler? = nil,
465
                            progressQueue: DispatchQueue = DispatchQueue.main,
466
                            imageTransition: ImageTransition = .noTransition,
467
                            runImageTransitionIfCached: Bool = false,
468
                            completion: ((AFIDataResponse<UIImage>) -> Void)? = nil) {
469
        af.setImage(withURLRequest: urlRequest,
470
                    cacheKey: cacheKey,
471
                    placeholderImage: placeholderImage,
472
                    serializer: serializer,
473
                    filter: filter,
474
                    progress: progress,
475
                    progressQueue: progressQueue,
476
                    imageTransition: imageTransition,
477
                    runImageTransitionIfCached: runImageTransitionIfCached,
478
                    completion: completion)
479
    }
480
 
481
    @available(*, deprecated, message: "Replaced by `imageView.af.cancelImageRequest()`")
482
    public func af_cancelImageRequest() {
483
        af.cancelImageRequest()
484
    }
485
 
486
    @available(*, deprecated, message: "Replaced by `imageView.af.run(_:with:)`")
487
    public func run(_ imageTransition: ImageTransition, with image: Image) {
488
        af.run(imageTransition, with: image)
489
    }
490
}
491
 
492
// MARK: -
493
 
494
private enum AssociatedKeys {
495
    static var imageDownloader = "UIImageView.af.imageDownloader"
496
    static var sharedImageDownloader = "UIImageView.af.sharedImageDownloader"
497
    static var activeRequestReceipt = "UIImageView.af.activeRequestReceipt"
498
}
499
 
500
#endif