Skip to content

Instantly share code, notes, and snippets.

@elmodos
Created November 9, 2018 19:47
Show Gist options
  • Save elmodos/97ce873009b745b7734bd3e28920b585 to your computer and use it in GitHub Desktop.
Save elmodos/97ce873009b745b7734bd3e28920b585 to your computer and use it in GitHub Desktop.
import UIKit
open class ZoomImageView: UIScrollView, UIScrollViewDelegate {
// MARK: Publics
public enum ZoomMode {
case fit
case fill
}
public var zoomMode: ZoomMode = .fit {
didSet {
self.updateImageView()
}
}
open var image: UIImage? {
get {
return self.imageView.image
}
set {
let oldImage = self.imageView.image
self.imageView.image = newValue
if oldImage?.size != newValue?.size {
self.oldSize = nil
self.updateImageView()
}
}
}
open override var intrinsicContentSize: CGSize {
return self.imageView.intrinsicContentSize
}
// MARK: Privates
private let imageView = UIImageView()
private var oldSize: CGSize?
// MARK: - Memory Management
public override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
public init(image: UIImage) {
super.init(frame: CGRect.zero)
self.image = image
self.setup()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
// MARK: - action Handlers
@objc private func onDoubleTap(_ sender: UITapGestureRecognizer) {
if sender.state == .recognized {
if self.zoomScale == 1 {
self.setZoomScale(max(1, maximumZoomScale / 3), animated: true)
} else {
self.setZoomScale(1, animated: true)
}
}
}
// MARK: - Public
open func scrollToCenter() {
let centerOffset = CGPoint(
x: (self.contentSize.width / 2) - (self.bounds.width / 2),
y: (self.contentSize.height / 2) - (self.bounds.height / 2)
)
self.contentOffset = centerOffset
}
open func setup() {
if #available(iOS 11, *) {
self.contentInsetAdjustmentBehavior = .never
}
self.backgroundColor = UIColor.clear
self.delegate = self
self.imageView.contentMode = .scaleAspectFill
self.showsVerticalScrollIndicator = false
self.showsHorizontalScrollIndicator = false
self.decelerationRate = UIScrollView.DecelerationRate.fast
self.addSubview(self.imageView)
let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(onDoubleTap(_:)))
doubleTapGesture.numberOfTapsRequired = 2
self.addGestureRecognizer(doubleTapGesture)
}
open override func layoutSubviews() {
super.layoutSubviews()
if self.imageView.image != nil && self.oldSize != self.bounds.size {
self.updateImageView()
self.oldSize = self.bounds.size
}
if self.imageView.frame.width <= self.bounds.width {
self.imageView.center.x = self.bounds.width * 0.5
}
if self.imageView.frame.height <= self.bounds.height {
self.imageView.center.y = self.bounds.height * 0.5
}
}
open override func updateConstraints() {
super.updateConstraints()
self.updateImageView()
}
// MARK: - Private
private func updateImageView() {
func fitSize(aspectRatio: CGSize, boundingSize: CGSize) -> CGSize {
let widthRatio = (boundingSize.width / aspectRatio.width)
let heightRatio = (boundingSize.height / aspectRatio.height)
var boundingSize = boundingSize
if widthRatio < heightRatio {
boundingSize.height = boundingSize.width / aspectRatio.width * aspectRatio.height
} else if heightRatio < widthRatio {
boundingSize.width = boundingSize.height / aspectRatio.height * aspectRatio.width
}
return CGSize(width: ceil(boundingSize.width), height: ceil(boundingSize.height))
}
func fillSize(aspectRatio: CGSize, minimumSize: CGSize) -> CGSize {
let widthRatio = (minimumSize.width / aspectRatio.width)
let heightRatio = (minimumSize.height / aspectRatio.height)
var minimumSize = minimumSize
if widthRatio > heightRatio {
minimumSize.height = minimumSize.width / aspectRatio.width * aspectRatio.height
} else if heightRatio > widthRatio {
minimumSize.width = minimumSize.height / aspectRatio.height * aspectRatio.width
}
return CGSize(width: ceil(minimumSize.width), height: ceil(minimumSize.height))
}
guard let image = self.imageView.image else {
return
}
var size: CGSize
switch zoomMode {
case .fit:
size = fitSize(aspectRatio: image.size, boundingSize: self.bounds.size)
case .fill:
size = fillSize(aspectRatio: image.size, minimumSize: self.bounds.size)
}
size.height = round(size.height)
size.width = round(size.width)
self.zoomScale = 1
self.maximumZoomScale = image.size.width / size.width
var imageViewBounds = imageView.bounds
imageViewBounds.size = size
self.imageView.bounds = imageViewBounds
self.contentSize = size
self.imageView.center = self.contentCenter(forBoundingSize: self.bounds.size, contentSize: self.contentSize)
}
// MARK: - UIScrollViewDelegate
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
self.imageView.center = self.contentCenter(forBoundingSize: bounds.size, contentSize: contentSize)
}
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.imageView
}
@inline(__always)
private func contentCenter(forBoundingSize boundingSize: CGSize, contentSize: CGSize) -> CGPoint {
let horizontalOffest = (boundingSize.width > contentSize.width)
? ((boundingSize.width - contentSize.width) * 0.5)
: 0.0
let verticalOffset = (boundingSize.height > contentSize.height)
? ((boundingSize.height - contentSize.height) * 0.5)
: 0.0
return CGPoint(x: contentSize.width * 0.5 + horizontalOffest, y: contentSize.height * 0.5 + verticalOffset)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment