Swift で 画像ビューワを実装する その1

検証環境:
Xcode 11.1
Swift 5.1

画像1枚を表示するシンプルな画像ビューワを実装してみる。
画像はピンチで拡大縮小ができるようにする。

f:id:xyk:20191011201756g:plain

実装方法としては UIScrollView 上に UIImageView を配置して実現する。
ただ乗せるだけだと画像の起点が左上になるが UIScrollView の contentInset を設定することで画面の中央に配置できる。

画像のズームは

  • スクロールビュー上のズームさせるビューを返すデリゲート viewForZoomingInScrollView
  • スクロールビューの minimumZoomScale、maximumZoomScale を設定

の2つを実装する必要がある。

コード

Storyboard側で UIScrollView を画面いっぱいに貼り付けてある。

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()
        
        updateImageView()
        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)
    }

    // 画像のアスペクト比を維持したまま ScrollView にぴったり収まるように ImageView のサイズを調整する
    private func updateImageView() {
        guard let size = imageView.image?.size 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 が常に画面中央に配置されるように 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()
    }
}

続き:
xyk.hatenablog.com

参考: UIScrollViewの中央にUIImageViewを配置しつつズーム可能にする - Qiita
https://qiita.com/wmoai/items/52b1901e62d28dae9f91