Edited at

UIScrollViewの中央にUIImageViewを配置しつつズーム可能にする

More than 3 years have passed since last update.


はじめに

iOSアプリで画像Viewerを作るとき、UIScrollView上にUIImageViewを配置するのが一般的かと思います。

しかし単純に乗せただけでは、画像の起点が左上になったり、ズームすると画像外の場所までスクロールしてしまって、しっくりこない場合があります。

そこで、画像をUIScrollViewの中央に表示しつつ、正しくズーム/スクロールできる方法を考えました。

fox.gif


方針

簡単に方針としては


  • UIImageViewの位置は(0,0)

  • UIImageViewのサイズは、UIImageのサイズに合わせる(要調整)

  • UIScrollView.contentSizeは、UIImageViewのサイズに合わせる


  • UIScrollView.contentInsetを操作して、画像を常に中央に配置

要するにスクロールビューの余白を調整して常に中央に表示する感じです。


コード


ViewController.swift

class ViewController: UIViewController, UIScrollViewDelegate {

@IBOutlet var scrollView: UIScrollView!
var imageView: UIImageView!

override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self

imageView = UIImageView(image: UIImage(named: "test.png"))
scrollView.addSubview(imageView)
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let size = imageView.image?.size {
// imageViewのサイズがscrollView内に収まるように調整
let wrate = scrollView.frame.width / size.width
let hrate = scrollView.frame.height / size.height
let rate = min(wrate, hrate, 1)
imageView.frame.size = CGSizeMake(size.width * rate, size.height * rate)

// contentSizeを画像サイズに設定
scrollView.contentSize = imageView.frame.size
// 初期表示のためcontentInsetを更新
updateScrollInset()
}
}

func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
// ズームのために要指定
return imageView
}

func scrollViewDidZoom(scrollView: UIScrollView) {
// ズームのタイミングでcontentInsetを更新
updateScrollInset()
}

private func updateScrollInset() {
// imageViewの大きさからcontentInsetを再計算
// なお、0を下回らないようにする
scrollView.contentInset = UIEdgeInsetsMake(
max((scrollView.frame.height - imageView.frame.height)/2, 0),
max((scrollView.frame.width - imageView.frame.width)/2, 0),
0,
0
);
}
}


なお、UIScrollViewはStoryBoardで設置、UIImageViewはコード内でaddSubview()しています。

これは、UIImageViewの設置をStoryBoard側で行うと、画像のframeとcontentSizeの設定が入り混じり複雑になってしまうことを避けるためです。

StoryBoard側で、UIScrollViewのzoom max/min値を指定することを忘れずに。

これを忘れるとどうあがいてもズームしません。


補足

ナビゲーションバーなどを考慮する場合は、contentInsetの再計算に別途調整が必要です。

例えばステータスバー+ナビゲーションバーであればcontentInset.topの最低値は0→64となるでしょう。