Project Setup
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
//
|
||||
// Request+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 Alamofire
|
||||
import Foundation
|
||||
|
||||
#if os(iOS) || os(tvOS)
|
||||
import UIKit
|
||||
#elseif os(watchOS)
|
||||
import UIKit
|
||||
import WatchKit
|
||||
#elseif os(macOS)
|
||||
import Cocoa
|
||||
#endif
|
||||
|
||||
public final class ImageResponseSerializer: ResponseSerializer {
|
||||
// MARK: Properties
|
||||
|
||||
public static var deviceScreenScale: CGFloat { DataRequest.imageScale }
|
||||
|
||||
public let imageScale: CGFloat
|
||||
public let inflateResponseImage: Bool
|
||||
public let emptyResponseCodes: Set<Int>
|
||||
public let emptyRequestMethods: Set<HTTPMethod>
|
||||
|
||||
static var acceptableImageContentTypes: Set<String> = {
|
||||
var contentTypes: Set<String> = ["application/octet-stream",
|
||||
"image/tiff",
|
||||
"image/jpg",
|
||||
"image/jpeg",
|
||||
"image/jp2",
|
||||
"image/gif",
|
||||
"image/png",
|
||||
"image/ico",
|
||||
"image/x-icon",
|
||||
"image/bmp",
|
||||
"image/x-bmp",
|
||||
"image/x-xbitmap",
|
||||
"image/x-ms-bmp",
|
||||
"image/x-win-bitmap"]
|
||||
|
||||
#if os(macOS) || os(iOS) // No WebP support on tvOS or watchOS.
|
||||
if #available(macOS 11, iOS 14, *) {
|
||||
contentTypes.formUnion(["image/webp"])
|
||||
}
|
||||
#endif
|
||||
|
||||
if #available(macOS 10.13, iOS 11, tvOS 11, watchOS 4, *) {
|
||||
contentTypes.formUnion(["image/heic", "image/heif"])
|
||||
}
|
||||
|
||||
return contentTypes
|
||||
}()
|
||||
|
||||
static let streamImageInitialBytePattern = Data([255, 216]) // 0xffd8
|
||||
|
||||
// MARK: Initialization
|
||||
|
||||
public init(imageScale: CGFloat = ImageResponseSerializer.deviceScreenScale,
|
||||
inflateResponseImage: Bool = true,
|
||||
emptyResponseCodes: Set<Int> = ImageResponseSerializer.defaultEmptyResponseCodes,
|
||||
emptyRequestMethods: Set<HTTPMethod> = ImageResponseSerializer.defaultEmptyRequestMethods) {
|
||||
self.imageScale = imageScale
|
||||
self.inflateResponseImage = inflateResponseImage
|
||||
self.emptyResponseCodes = emptyResponseCodes
|
||||
self.emptyRequestMethods = emptyRequestMethods
|
||||
}
|
||||
|
||||
// MARK: Serialization
|
||||
|
||||
public func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> Image {
|
||||
guard error == nil else { throw error! }
|
||||
|
||||
guard let data = data, !data.isEmpty else {
|
||||
guard emptyResponseAllowed(forRequest: request, response: response) else {
|
||||
throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
|
||||
}
|
||||
|
||||
print("Returning empty image!")
|
||||
return Image()
|
||||
}
|
||||
|
||||
try validateContentType(for: request, response: response)
|
||||
let image = try serializeImage(from: data)
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
public func serializeImage(from data: Data) throws -> Image {
|
||||
guard !data.isEmpty else {
|
||||
throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength)
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
guard let image = UIImage.af.threadSafeImage(with: data, scale: imageScale) else {
|
||||
throw AFIError.imageSerializationFailed
|
||||
}
|
||||
|
||||
if inflateResponseImage { image.af.inflate() }
|
||||
#elseif os(macOS)
|
||||
guard let bitmapImage = NSBitmapImageRep(data: data) else {
|
||||
throw AFIError.imageSerializationFailed
|
||||
}
|
||||
|
||||
let image = NSImage(size: NSSize(width: bitmapImage.pixelsWide, height: bitmapImage.pixelsHigh))
|
||||
image.addRepresentation(bitmapImage)
|
||||
#endif
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
// MARK: Content Type Validation
|
||||
|
||||
/// Adds the content types specified to the list of acceptable images content types for validation.
|
||||
///
|
||||
/// - parameter contentTypes: The additional content types.
|
||||
public class func addAcceptableImageContentTypes(_ contentTypes: Set<String>) {
|
||||
ImageResponseSerializer.acceptableImageContentTypes.formUnion(contentTypes)
|
||||
}
|
||||
|
||||
public func validateContentType(for request: URLRequest?, response: HTTPURLResponse?) throws {
|
||||
if let url = request?.url, url.isFileURL { return }
|
||||
|
||||
guard let mimeType = response?.mimeType else {
|
||||
let contentTypes = Array(ImageResponseSerializer.acceptableImageContentTypes)
|
||||
throw AFError.responseValidationFailed(reason: .missingContentType(acceptableContentTypes: contentTypes))
|
||||
}
|
||||
|
||||
guard ImageResponseSerializer.acceptableImageContentTypes.contains(mimeType) else {
|
||||
let contentTypes = Array(ImageResponseSerializer.acceptableImageContentTypes)
|
||||
|
||||
throw AFError.responseValidationFailed(
|
||||
reason: .unacceptableContentType(acceptableContentTypes: contentTypes, responseContentType: mimeType)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Image Scale
|
||||
|
||||
extension DataRequest {
|
||||
public class var imageScale: CGFloat {
|
||||
#if os(iOS) || os(tvOS)
|
||||
return UIScreen.main.scale
|
||||
#elseif os(watchOS)
|
||||
return WKInterfaceDevice.current().screenScale
|
||||
#elseif os(macOS)
|
||||
return 1.0
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - iOS, tvOS, and watchOS
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
|
||||
extension DataRequest {
|
||||
/// Adds a response handler to be called once the request has finished.
|
||||
///
|
||||
/// - parameter imageScale: The scale factor used when interpreting the image data to construct
|
||||
/// `responseImage`. Specifying a scale factor of 1.0 results in an image whose
|
||||
/// size matches the pixel-based dimensions of the image. Applying a different
|
||||
/// scale factor changes the size of the image as reported by the size property.
|
||||
/// This is set to the value of scale of the main screen by default, which
|
||||
/// automatically scales images for retina displays, for instance.
|
||||
/// `Screen.scale` by default.
|
||||
/// - parameter inflateResponseImage: Whether to automatically inflate response image data for compressed formats
|
||||
/// (such as PNG or JPEG). Enabling this can significantly improve drawing
|
||||
/// performance as it allows a bitmap representation to be constructed in the
|
||||
/// background rather than on the main thread. `true` by default.
|
||||
/// - parameter queue: The queue on which the completion handler is dispatched. `.main` by default.
|
||||
/// - parameter completionHandler: A closure to be executed once the request has finished. The closure takes 4
|
||||
/// arguments: the URL request, the URL response, if one was received, the image,
|
||||
/// if one could be created from the URL response and data, and any error produced
|
||||
/// while creating the image.
|
||||
///
|
||||
/// - returns: The request.
|
||||
@discardableResult
|
||||
public func responseImage(imageScale: CGFloat = DataRequest.imageScale,
|
||||
inflateResponseImage: Bool = true,
|
||||
queue: DispatchQueue = .main,
|
||||
completionHandler: @escaping (AFDataResponse<Image>) -> Void)
|
||||
-> Self {
|
||||
response(queue: queue,
|
||||
responseSerializer: ImageResponseSerializer(imageScale: imageScale,
|
||||
inflateResponseImage: inflateResponseImage),
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - macOS
|
||||
|
||||
#elseif os(macOS)
|
||||
|
||||
extension DataRequest {
|
||||
/// Adds a response handler to be called once the request has finished.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - queue: The queue on which the completion handler is dispatched. `.main` by default.
|
||||
/// - completionHandler: A closure to be executed once the request has finished. The closure takes 4 arguments:
|
||||
/// the URL request, the URL response, if one was received, the image, if one could be
|
||||
/// created from the URL response and data, and any error produced while creating the image.
|
||||
///
|
||||
/// - returns: The request.
|
||||
@discardableResult
|
||||
public func responseImage(queue: DispatchQueue = .main,
|
||||
completionHandler: @escaping (AFDataResponse<Image>) -> Void)
|
||||
-> Self {
|
||||
response(queue: queue,
|
||||
responseSerializer: ImageResponseSerializer(inflateResponseImage: false),
|
||||
completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user