Created
November 9, 2018 19:47
-
-
Save elmodos/97ce873009b745b7734bd3e28920b585 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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