Skip to content

Instantly share code, notes, and snippets.

@nunogoncalves
Last active January 17, 2019 12:24
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nunogoncalves/f30cc4cd297bf549ffef91db23193b0b to your computer and use it in GitHub Desktop.
Save nunogoncalves/f30cc4cd297bf549ffef91db23193b0b to your computer and use it in GitHub Desktop.
Basic zoomable image view
//
// ZoomableImageView.swift
// ZoomableImage
//
// Created by Nuno Gonçalves on 01/04/17.
// Copyright © 2017 Nuno Gonçalves. All rights reserved.
//
import UIKit
@IBDesignable
class ZoomableImageView : UIView {
fileprivate var imageView: UIImageView!
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView(frame: self.bounds)
scrollView.maximumZoomScale = self.maxZoomScale
scrollView.delegate = self
scrollView.contentMode = .center
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
return scrollView
}()
private var tapGesture: UITapGestureRecognizer!
@IBInspectable
var image: UIImage? {
get { return imageView.image }
set {
if let image = newValue {
setMaxZoomScale(basedOn: image)
}
imageView.image = newValue
setNeedsLayout()
}
}
@IBInspectable var maxZoomScale: CGFloat = 1 {
didSet {
scrollView.maximumZoomScale = maxZoomScale
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
addSubview(scrollView)
imageView = UIImageView(frame: bounds)
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = .black
tapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapped(with:)))
tapGesture.numberOfTapsRequired = 2
imageView.addGestureRecognizer(tapGesture)
imageView.isUserInteractionEnabled = true
scrollView.addSubview(imageView)
}
private func setMaxZoomScale(basedOn image: UIImage) {
let imageSize = image.size
let size = frame.size
let heightRatio = imageSize.height / size.height
let widthRatio = imageSize.width / size.width
maxZoomScale = min(max(heightRatio, widthRatio), 1)
}
@objc private func doubleTapped(with gestureRecognizer: UITapGestureRecognizer) {
if scrollView.zoomScale == 1 {
scrollView.zoom(to: zoomRect(for: scrollView.maximumZoomScale,
center: gestureRecognizer.location(in: gestureRecognizer.view)),
animated: true)
} else {
scrollView.setZoomScale(1, animated: true)
}
}
func zoomRect(for scale: CGFloat, center: CGPoint) -> CGRect {
var zoomRect = CGRect.zero
zoomRect.size.height = imageView.frame.height / scale
zoomRect.size.width = imageView.frame.width / scale
let newCenter = imageView.convert(center, from: self)
zoomRect.origin.x = newCenter.x - (zoomRect.width / 2.0)
zoomRect.origin.y = newCenter.y - (zoomRect.height / 2.0)
return zoomRect
}
override func layoutSubviews() {
super.layoutSubviews()
scrollView.frame = bounds
let size: CGSize
if let image = imageView.image {
size = CGSize(width: bounds.width, height: bounds.width * image.size.height / image.size.width )
} else {
size = bounds.size
}
imageView.frame = CGRect(origin: .zero, size: size)
scrollView.contentSize = size
centerIfNeeded()
}
func centerIfNeeded() {
var inset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
if scrollView.contentSize.height < scrollView.bounds.height - 0 {
let insetV = (scrollView.bounds.height - 0 - scrollView.contentSize.height) / 2
inset.top += insetV
inset.bottom = insetV
}
if scrollView.contentSize.width < scrollView.bounds.width {
let insetV = (scrollView.bounds.width - scrollView.contentSize.width) / 2
inset.left = insetV
inset.right = insetV
}
scrollView.contentInset = inset
}
}
extension ZoomableImageView : UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
centerIfNeeded()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment