Skip to content

Instantly share code, notes, and snippets.

@timusus
Last active May 16, 2017 00:53
Show Gist options
  • Select an option

  • Save timusus/ac3af8baf4ecdd2ad41a2c6a0eecf9df to your computer and use it in GitHub Desktop.

Select an option

Save timusus/ac3af8baf4ecdd2ad41a2c6a0eecf9df to your computer and use it in GitHub Desktop.
Library-agnostic image loading (iOS/Swift)
import Foundation
class HanekeImageLoader: ImageLoader {
/**
Haneke requires that we register our 'formats' before they can be used.
**/
func setup() {
ImageTransformationSet.imageTransformationSets.forEach { imageTransformationSet in
if let format = createFormat(for: imageTransformationSet) {
Shared.imageCache.addFormat(format)
}
}
HanekeGlobals.UIKit.SetImageAnimationDuration = 0.4
}
func loadImageFromURL(imageView: UIImageView, url: URL, placeholder: UIImage? = nil, imageTransformationSet: ImageTransformationSet? = nil) {
imageView.hnk_setImageFromURL(url, placeholder: placeholder, format: getFormat(for: imageTransformationSet), failure: nil, success: nil)
}
func cancelImageLoad(imageView: UIImageView) {
imageView.hnk_cancelSetImage()
}
func createFormat(for imageTransitionSet: ImageTransformationSet) -> Format<UIImage>? {
return Format<UIImage>(name: imageTransitionSet.name, diskCapacity: UInt64(imageTransitionSet.cacheSize * 1024 * 1024)) { image in
return self.applyTransformations(for: imageTransitionSet, image: image)
}
}
func getFormat(for imageTransformationSet: ImageTransformationSet?) -> Format<UIImage>? {
guard let imageTransformationSet = imageTransformationSet else {
return nil
}
if let (format, _, _) = Shared.imageCache.formats[imageTransformationSet.name] {
return format
}
return nil
}
func applyTransformations(for imageTransformationSet: ImageTransformationSet, image: UIImage) -> UIImage {
var transformedImage = image
imageTransformationSet.imageTransformations.forEach { transformation in
switch transformation {
case .crop:
if transformedImage.size.width != transformedImage.size.height {
let smallestLength = min(transformedImage.size.width, transformedImage.size.height)
transformedImage = transformedImage.imageCropped(to: CGRect(x: 0, y: 0, width: smallestLength, height: smallestLength))!
}
break
case .scale(let size):
transformedImage = transformedImage.imageScaled(toMaxSize: size)
break
case .blur(let radius):
transformedImage = UIImageEffects.blurImage(transformedImage, radius: radius)
break
case .round(let radius):
transformedImage = transformedImage.imageRounded(radius: radius)
break
}
}
return transformedImage
}
}
protocol ImageLoader {
/**
Opportunity to do any initial setup for the ImageLoader.
Typically, this would be called in `AppDelegate.didFinishLaunchingWithOptions()`
**/
func setup()
func loadImageFromURL(imageView: UIImageView, url: URL, placeholder: UIImage?, imageTransformationSet: ImageTransformationSet?)
func cancelImageLoad(imageView: UIImageView)
}
enum ImageTransformation {
case blur(level: CGFloat)
case scale(size: CGSize)
case crop()
case round(radius: CGFloat)
}
class ImageTransformationSet {
let name: String
let imageTransformations: [ImageTransformation]
let cacheSize: Int
init(name: String, imageTransformations: [ImageTransformation], cacheSize: Int) {
self.name = name
self.imageTransformations = imageTransformations
self.cacheSize = cacheSize
}
static let avatarSmall = ImageTransformationSet(name: "avatarSmall", imageTransformations: [
// ImageTransformation.crop(),
// ImageTransformation.scale(size: CGSize(width: 36, height: 36)),
// ImageTransformation.round(radius: AVATAR_SMALL_CORNER_RADIUS)
], cacheSize: 4)
static let avatarMedium = ImageTransformationSet(name: "avatarMedium", imageTransformations: [
// ImageTransformation.crop(),
// ImageTransformation.scale(size: CGSize(width: 65, height: 65)),
// ImageTransformation.round(radius: AVATAR_MEDIUM_CORNER_RADIUS)
], cacheSize: 6)
static let inlineImages = ImageTransformationSet(name: "inlineImages", imageTransformations: [
ImageTransformation.scale(size: CGSize(width: UIScreen.main.nativeBounds.width, height: UIScreen.main.nativeBounds.width)),
], cacheSize: 60)
static let blur = ImageTransformationSet(name: "blurImages", imageTransformations: [
ImageTransformation.blur(level: UIImageEffects.BlurLevel.ultraLow.rawValue)
], cacheSize: 6)
static let imageTransformationSets: [ImageTransformationSet] = [
ImageTransformationSet.avatarSmall,
ImageTransformationSet.avatarMedium,
ImageTransformationSet.inlineImages,
ImageTransformationSet.blur
]
}
import Kingfisher
class KingfisherImageLoader: ImageLoader {
func setup() {
//Set maximum cache size of 60MB
ImageCache.default.maxDiskCacheSize = 60 * 1024 * 1024
}
func loadImageFromURL(imageView: UIImageView, url: URL, placeholder: UIImage?, imageTransformationSet: ImageTransformationSet?) {
imageView.kf.setImage(with: url, placeholder: placeholder, options: getOptions(for: imageTransformationSet))
}
func cancelImageLoad(imageView: UIImageView) {
imageView.kf.cancelDownloadTask()
}
func getOptions(for imageTransformationSet: ImageTransformationSet?) -> KingfisherOptionsInfo? {
var options: KingfisherOptionsInfo = [
.transition(.fade(0.4)),
.backgroundDecode,
.callbackDispatchQueue(DispatchQueue.global(qos: DispatchQoS.QoSClass.utility))
]
guard let imageTransformationSet = imageTransformationSet else {
return options
}
var processor: ImageProcessor? = nil
imageTransformationSet.imageTransformations.forEach { transformation in
var newProcessor: ImageProcessor? = nil
switch transformation {
case .crop():
newProcessor = SquareCropProcessor()
break
case .scale(let size):
newProcessor = ResizingImageProcessor(referenceSize: size)
break
case .blur(let radius):
newProcessor = BlurImageProcessor(blurRadius: radius)
break
case .round(let radius):
newProcessor = RoundCornerImageProcessor(cornerRadius: radius)
break
}
if (newProcessor != nil) {
if (processor != nil) {
processor = processor!.append(another: newProcessor!)
} else {
processor = newProcessor
}
}
}
if let processor = processor {
options += [.processor(processor)]//, .scaleFactor(UIScreen.main.scale)]
}
return options
}
class SquareCropProcessor: ImageProcessor {
let identifier: String = "squarecropprocessor"
func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> Image? {
switch item {
case .image(let image):
if image.size.width != image.size.height {
let smallestLength = min(image.size.width, image.size.height)
return image.imageCropped(to: CGRect(x: 0, y: 0, width: smallestLength, height: smallestLength))!
}
return image
default:
return (DefaultImageProcessor.default >> self).process(item: item, options: options)
}
}
}
}
extension UIImageView {
func setImageFromURL(url: URL, placeholder: UIImage? = nil, imageTransformationSet: ImageTransformationSet? = nil) {
ImageLoaderManager.sharedInstance().imageLoader.loadImageFromURL(imageView: self, url: url, placeholder: placeholder, imageTransformationSet: imageTransformationSet)
}
func prepareForReuse() {
ImageLoaderManager.sharedInstance().imageLoader.cancelImageLoad(imageView: self)
self.image = nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment