はじめに
iOSアプリで画像Viewerを作るとき、UIScrollView上にUIImageViewを配置するのが一般的かと思います。
しかし単純に乗せただけでは、画像の起点が左上になったり、ズームすると画像外の場所までスクロールしてしまって、しっくりこない場合があります。
そこで、画像をUIScrollViewの中央に表示しつつ、正しくズーム/スクロールできる方法を考えました。
方針
簡単に方針としては
- UIImageViewの位置は(0,0)
- UIImageViewのサイズは、UIImageのサイズに合わせる(要調整)
- UIScrollView.contentSizeは、UIImageViewのサイズに合わせる
- UIScrollView.contentInsetを操作して、画像を常に中央に配置
要するにスクロールビューの余白を調整して常に中央に表示する感じです。
コード
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となるでしょう。