import Foundation import UIKit final class CropScrollView: UIScrollView, UIScrollViewDelegate { private var contentView: UIView? public var updated: (CGPoint, CGFloat) -> Void = { _, _ in } override init(frame: CGRect) { super.init(frame: frame) self.backgroundColor = .clear self.showsVerticalScrollIndicator = false self.showsHorizontalScrollIndicator = false self.contentInsetAdjustmentBehavior = .never self.clipsToBounds = false self.bouncesZoom = true self.delegate = self self.decelerationRate = .fast let transparentView = UIView(frame: bounds) transparentView.backgroundColor = .clear transparentView.isUserInteractionEnabled = false self.addSubview(transparentView) self.contentView = transparentView self.minimumZoomScale = 1.0 self.maximumZoomScale = 4.0 } required init?(coder: NSCoder) { preconditionFailure() } override func layoutSubviews() { super.layoutSubviews() guard let contentView = self.contentView else { return } let boundsSize = bounds.size var frameToCenter = contentView.frame if frameToCenter.size.width < boundsSize.width { frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2 } else { frameToCenter.origin.x = 0 } if frameToCenter.size.height < boundsSize.height { frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2 } else { frameToCenter.origin.y = 0 } contentView.frame = frameToCenter } func setContentSize(_ size: CGSize) { self.contentView?.frame = CGRect(origin: .zero, size: size) self.contentSize = size self.zoom(to: CGRect(origin: CGPoint(x: floor((size.width - self.bounds.width) / 2.0), y: floor((size.height - self.bounds.height) / 2.0)), size: self.bounds.size), animated: false) } private func notify() { let currentScale = self.zoomScale let contentOffset = self.contentOffset let centerOffset = CGPoint( x: -1.0 * (contentOffset.x + self.bounds.width / 2.0 - self.contentSize.width / 2.0), y: -1.0 * (contentOffset.y + self.bounds.height / 2.0 - self.contentSize.height / 2.0) ) self.updated(centerOffset, currentScale) } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return self.contentView } func scrollViewDidZoom(_ scrollView: UIScrollView) { self.setNeedsLayout() self.layoutIfNeeded() self.notify() } func scrollViewDidScroll(_ scrollView: UIScrollView) { self.notify() } func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { self.notify() } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { self.notify() } } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.notify() } }