検証環境:
Xcode 11.1
Swift 5.1
前回の続き。
今回は画像をダブルタップしたときに拡大・縮小するように実装を追加する。
ScrollView のタップ箇所をズームさせる方法は Apple のプログラミングガイドを参考に実装する。
https://developer.apple.com/library/archive/documentation/WindowsViews/Conceptual/UIScrollView_pg/ZoomZoom/ZoomZoom.html#//apple_ref/doc/uid/TP40008179-CH102-SW7developer.apple.com
まず、画像が乗っている ScrollView に対してダブルタップのジェスチャを追加する。
// ScrollView にダブルタップのジェスチャを追加 let gesture = UITapGestureRecognizer(target: self, action: #selector(scrollViewDoubleTapped(_:))) gesture.numberOfTapsRequired = 2 scrollView.addGestureRecognizer(gesture)
次にタブルタップ時に呼ばれるアクション部分を実装する。
ScrollView の zoomScale
が最小値(1倍)だった場合は最大値(4倍)に拡大させ、最小値(1倍)より大きい場合には最小値(1倍)に戻るように縮小する。
private let minZoomScale: CGFloat = 1.0 private let maxZoomScale: CGFloat = 4.0 // ScrollView がダブルタップされた時 @objc private func scrollViewDoubleTapped(_ gesture: UITapGestureRecognizer) { guard let scrollView = gesture.view as? UIScrollView else { return } if scrollView.zoomScale == minZoomScale { // タップされた場所を中心に最大に拡大する let location = gesture.location(in: scrollView) let rect = zoomRect(for: scrollView, scale: maxZoomScale, center: location) scrollView.zoom(to: rect, animated: true) } else { // 最小に戻す scrollView.setZoomScale(minZoomScale, animated: true) } } private func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect { let size = CGSize( width: scrollView.frame.width / scale, height: scrollView.frame.height / scale ) let rect = CGRect( origin: CGPoint( x: center.x - size.width / 2.0, y: center.y - size.height / 2.0 ), size: size ) return rect }
拡大時には UIScrollView の zoom(to:animated:)
を使い、縮小時には setZoomScale(_:animated:)
を使う。
全コード
class ImageViewerViewController: UIViewController, UIScrollViewDelegate { @IBOutlet private weak var scrollView: UIScrollView! private let minZoomScale: CGFloat = 1.0 private let maxZoomScale: CGFloat = 4.0 private var imageView: UIImageView = { let image = UIImage(named: "dog")! let imageView = UIImageView(image: image) // アスペクト比固定でフィットさせる imageView.contentMode = .scaleAspectFit return imageView }() override func viewDidLoad() { super.viewDidLoad() setupScrollView() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() adjustImageViewSize() updateContentInset() } private func setupScrollView() { scrollView.delegate = self scrollView.backgroundColor = .black scrollView.minimumZoomScale = minZoomScale scrollView.maximumZoomScale = maxZoomScale scrollView.showsVerticalScrollIndicator = false scrollView.showsHorizontalScrollIndicator = false scrollView.addSubview(imageView) // ダブルタップのジェスチャを追加 let gesture = UITapGestureRecognizer(target: self, action: #selector(scrollViewDoubleTapped(_:))) gesture.numberOfTapsRequired = 2 scrollView.addGestureRecognizer(gesture) } // 画像のアスペクト比を維持したまま ScrollView の表示範囲にぴったり収まるように ImageView のサイズを調整する private func adjustImageViewSize() { guard let size = imageView.image?.size, imageView.frame.isEmpty else { return } let wRate = scrollView.bounds.width / size.width let hRate = scrollView.bounds.height / size.height let rate = min(wRate, hRate, 1) imageView.frame.size = CGSize(width: size.width * rate, height: size.height * rate) // contentSize を画像サイズと同じにする scrollView.contentSize = imageView.frame.size } // ImageView のサイズが ScrollView の表示範囲より小さい場合に画面中央に配置されるように contentInset を設定する private func updateContentInset() { scrollView.contentInset = UIEdgeInsets( top: max((scrollView.frame.height - imageView.frame.height) / 2, 0), left: max((scrollView.frame.width - imageView.frame.width) / 2, 0), bottom: 0, right: 0 ) } // ズーム対象のビューを返す func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } // ズームのタイミングで contentInset を更新する func scrollViewDidZoom(_ scrollView: UIScrollView) { updateContentInset() } // ScrollView がダブルタップされた時 @objc private func scrollViewDoubleTapped(_ gesture: UITapGestureRecognizer) { guard let scrollView = gesture.view as? UIScrollView else { return } if scrollView.zoomScale == minZoomScale { // タップされた場所を中心に最大に拡大する let location = gesture.location(in: scrollView) let rect = zoomRect(for: scrollView, scale: maxZoomScale, center: location) scrollView.zoom(to: rect, animated: true) } else { // 最小に戻す scrollView.setZoomScale(minZoomScale, animated: true) } } private func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect { let size = CGSize( width: scrollView.frame.width / scale, height: scrollView.frame.height / scale ) let rect = CGRect( origin: CGPoint( x: center.x - size.width / 2.0, y: center.y - size.height / 2.0 ), size: size ) return rect } }
参考: