【Swift3】縦長の画像をScrollViewいっぱいに表示させる方法
縦長の画像を画面いっぱいにスクロールさせようとした時に、試行錯誤したのでこの記事では個人的にどうやったかをご紹介します。以下、手順です。
- 準備する。
- imageViewのcontentModeをscaleAspectFitにする。(そうすると画像がscrollViewにFitするために縮小される)
- 次に、倍率を計算する。
- 縮小された画像の横幅を割り出す。
- scrollViewいっぱいに画像を表示するための倍率を割り出す。
- 割り出した倍率を最小倍率に設定し、そこまで拡大する。
- ScrollViewのoffset位置を調整。
1. 準備する
1-1. まず、storyboardへ移動し、ViewControllerの中にUIScrollViewを設置します。Constraintは必要に応じて設定して問題ありません。この記事ではViewControllerいっぱいに縛ってます。
1-2. UIScrollViewの中にUIImageViewを設置します。ConstraintはUIScrollVIewの四隅に縛り付け、さらにAlign Center X と Align Center Yを0にします。
1-3. 次にViewControllerにOutletを引っ張ります。画面右上の
このボタン
を押し、下の画像のように2画面になるようにします。
1-4. ScrollViewをクリックし、「control」ボタンを押したまま右画面のViewControllerの中にoutletを引っ張ります。
*任意の名前を設定して問題ありません。この記事では「scrollView」にしています。
同様に、UIImageViewのoutletも引っ張ります。
最後に、一番大事な設定をします。
ScrollViewをクリックし、controlを押しながらView Controllerまで引っ張り、離します。
そうするとdelegateのpop upが出てくるのでScrollViewのdelegateをViewControllerに渡します。
Delegateの設定はコード上で設定しても問題ありません。
2. UIImageViewのcontentModeをscaleAspectFitにする
さて、準備が出来ましたのでいよいよコーディングに入ります。
まずはscrollViewの初期設定をviewDidLoad()の中に記述しましょう。
1 2 3 4 5 6 |
override func viewDidLoad() { super.viewDidLoad() scrollView.minimumZoomScale = 1.0 //最小倍率 scrollView.maximumZoomScale = 7.0 //最大倍率 scrollView.showsHorizontalScrollIndicator = false //横スクロールを不可 } |
次に、画像をUIImageViewのなかに入れるfunctionを書き、viewDidLoadで呼び出します。
1 2 3 4 5 |
func initialSetting(){ self.imageView.contentMode = .scaleAspectFit //アスペクト比を保ちつつUIImageViewの中に収まるように let image = UIImage(named: "sample") self.imageView.image = image } |
現在、ViewControllerの中は下のようになっているはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() scrollView.minimumZoomScale = 1.0 //最小倍率 scrollView.maximumZoomScale = 7.0 //最大倍率 scrollView.showsHorizontalScrollIndicator = false //横スクロールを不可 initialSetting() } func initialSetting(){ self.imageView.contentMode = .scaleAspectFit //アスペクト比を保ちつつUIImageViewの中に収まるように let image = UIImage(named: "sample") self.imageView.image = image } } |
この状態で一度ビルドしてみましょう。
contetModeがscaleAspectFitなので、下のように、画像が画面いっぱいに表示され余白が出来るはずです。
3.倍率を計算する
現在scrollVIewはUIImageView全体を表示している状態です。理想の状態は、下のように画像のみを表示する事です。
つまり、画像がScrollViewの画面いっぱいになるまでズームアップすれば良いと言うことです。
それではやっていきます。
まずは今の倍率を割り出しましょう。
1 2 3 4 5 6 7 |
func updateZoom(){ var zoomScale: CGFloat zoomScale = min(self.scrollView.bounds.width / (self.imageView.image?.size.width)!, self.scrollView.bounds.height / (self.imageView.image?.size.height)!) if zoomScale > 1{ zoomScale = 1 } } |
ここでは、
( scrollViewの横幅 / 画像の横幅 )
と
( scrollViewの高さ / 画像の高さ )
を比べて、小さい方をzoomScale変数の中に入れています。
縦長の画像のみを取り扱うと分かっていれば、高さの計算のみで事足りますが、一応横長の画像にも対応出来るように横幅と高さ両方を比べています。
4.縮小された画像の横幅を割り出す
倍率が分かったところで、現在縮小された画像の横幅を計算していきましょう。
1 |
let currentImageWidth = (self.imageView.image?.size.width)! * zoomScale |
5.scrollViewいっぱいに画像を表示するための倍率を割り出す
今の画像の横幅が分かったので、次にscrollViewいっぱいに画像を表示するための倍率を計算します。
1 |
zoomScale = self.scrollView.bounds.size.width / currentImageWidth |
6.割り出した倍率を最小倍率に設定し、そこまで拡大する
scrollViewいっぱいに画像を表示するための倍率が分かったので、倍率を変更します。
1 2 3 |
self.scrollView.maximumZoomScale = zoomScale + 7.0 self.scrollView.minimumZoomScale = zoomScale self.scrollView.zoomScale = zoomScale |
*viewDidLoad()の中に最大倍率を7.0に設定しているため、ここで改めて設定し直します。設定し直さなかった場合、画像がものすごい縦長だとズームイン出来なくなるためです。ですので、順番も重要です。先に最大倍率と最小倍率を設定する必要があります。
現在、ViewControllerのコードは下のようになっているはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() scrollView.minimumZoomScale = 1.0 //最小倍率 scrollView.maximumZoomScale = 7.0 //最大倍率 scrollView.showsHorizontalScrollIndicator = false //横スクロールを不可 initialSetting() } func initialSetting(){ self.imageView.contentMode = .scaleAspectFit //アスペクト比を保ちつつUIImageViewの中に収まるように let image = UIImage(named: "sample") self.imageView.image = image updateZoom() } func updateZoom(){ var zoomScale: CGFloat zoomScale = min(self.scrollView.bounds.width / (self.imageView.image?.size.width)!, (self.scrollView.bounds.height) / (self.imageView.image?.size.height)!) if zoomScale > 1{ zoomScale = 1 } let currentImageWidth = (self.imageView.image?.size.width)! * zoomScale zoomScale = self.scrollView.bounds.size.width / currentImageWidth self.scrollView.maximumZoomScale = zoomScale + 7.0 self.scrollView.minimumZoomScale = zoomScale self.scrollView.zoomScale = zoomScale } } |
一度ここでビルドしてみましょう。
変わっていません。
これは、UIScrollViewDelegateのfunctionが記述されていないためズームイン出来ない状態にあります。
では付け足しましょう。
ViewControllerにUIScrollViewDelegateを追加します。
1 |
class ViewController: UIViewController, UIScrollViewDelegate |
そして、以下を追加します
1 2 3 |
func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } |
もう一度ビルドしてみましょう。
ただ画像の中央にズームインされ、少しズレてますね。
7. ScrollViewのoffset位置を調整
両方同時に直していきましょう。
結論から言うと、なぜズレたのかは調べても分かりませんでした;;
いつか理由が分かる日が来たら後述します。
ScrollViewの画面を任意の場所に移動するには
1 |
scrollView.setContentOffset(contentOffset: CGPoint, animated: Bool) |
を使います。
色々と試した結果、scrollViewのxの位置は
1 |
self.scrollView.bounds.width - currentImageWidth - 5 |
がちょうど良かったです。(なぜだろう?)
この値は後に使うので、class 変数にして代入しておきましょう。
1 |
offsetX = self.scrollView.bounds.width - currentImageWidth - 5 |
yの位置は一番上まで移動したいので、0にします。
1 |
self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: false) |
現在、ViewControllerのコードは以下のようになっているはずです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import UIKit class ViewController: UIViewController, UIScrollViewDelegate { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var imageView: UIImageView! var offsetX: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() scrollView.minimumZoomScale = 1.0 //最小倍率 scrollView.maximumZoomScale = 7.0 //最大倍率 scrollView.showsHorizontalScrollIndicator = false //横スクロールを不可 initialSetting() } func initialSetting(){ self.imageView.contentMode = .scaleAspectFit //アスペクト比を保ちつつUIImageViewの中に収まるように let image = UIImage(named: "sample") self.imageView.image = image updateZoom() } func updateZoom(){ var zoomScale: CGFloat zoomScale = min(self.scrollView.bounds.width / (self.imageView.image?.size.width)!, (self.scrollView.bounds.height) / (self.imageView.image?.size.height)!) if zoomScale > 1{ zoomScale = 1 } let currentImageWidth = (self.imageView.image?.size.width)! * zoomScale zoomScale = self.scrollView.bounds.size.width / currentImageWidth self.scrollView.maximumZoomScale = zoomScale + 7.0 self.scrollView.minimumZoomScale = zoomScale self.scrollView.zoomScale = zoomScale offsetX = self.scrollView.bounds.width - currentImageWidth - 5 self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: false) } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } } |
では一度ビルドしましょう。
ズレが治り、画像の一番上が表示されるようになりました!
ただこの状態で画像を動かしてみると、横にも上にも動いてしまいます。
画像の部分のみ、上下にスクロール出来るようにしましょう。
UIScrollViewDelegate のfunctionの一つ、
1 |
func scrollViewDidScroll |
を使います。
このfunctionはscrollViewがスクロールされた時に呼び出されるfunctionです。
ここでやりたい事は、以下の図のようにスクロールされた時、scrollViewを画像の位置に固定する事です。
ではやっていきましょう。
1 2 3 4 5 6 7 8 |
func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.zoomScale == self.scrollView.minimumZoomScale{ scrollView.contentOffset.x = (offsetX == 0 ? scrollView.contentOffset.x : offsetX) scrollView.contentInset = UIEdgeInsets(top: 0, left: -scrollView.contentOffset.x, bottom: 0, right: -scrollView.contentOffset.x) }else{ scrollView.contentInset = UIEdgeInsets(top: 0, left: -offsetX, bottom: 0, right: -offsetX) } } |
1 |
if scrollView.zoomScale == self.scrollView.minimumZoomScale |
は画像のズームインを可能にするために必要です。上記がないと、ズームインした後に勝手に元のxの位置に戻ってしまいます。
完成形のViewControllerのコードは以下になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var imageView: UIImageView! var offsetX: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() scrollView.minimumZoomScale = 1.0 //最小倍率 scrollView.maximumZoomScale = 7.0 //最大倍率 scrollView.showsHorizontalScrollIndicator = false //横スクロールを不可 initialSetting() } func initialSetting(){ self.imageView.contentMode = .scaleAspectFit //アスペクト比を保ちつつUIImageViewの中に収まるように let image = UIImage(named: "sample") self.imageView.image = image updateZoom() } func updateZoom(){ var zoomScale: CGFloat zoomScale = min(self.scrollView.bounds.width / (self.imageView.image?.size.width)!, (self.scrollView.bounds.height) / (self.imageView.image?.size.height)!) if zoomScale > 1{ zoomScale = 1 } let currentImageWidth = (self.imageView.image?.size.width)! * zoomScale zoomScale = self.scrollView.bounds.size.width / currentImageWidth self.scrollView.maximumZoomScale = zoomScale + 7.0 self.scrollView.minimumZoomScale = zoomScale self.scrollView.zoomScale = zoomScale offsetX = self.scrollView.bounds.width - currentImageWidth - 5 self.scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: false) } } extension ViewController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } func scrollViewDidScroll(_ scrollView: UIScrollView) { if scrollView.zoomScale == self.scrollView.minimumZoomScale{ scrollView.contentOffset.x = (offsetX == 0 ? scrollView.contentOffset.x : offsetX) scrollView.contentInset = UIEdgeInsets(top: 0, left: -scrollView.contentOffset.x, bottom: 0, right: -scrollView.contentOffset.x) }else{ scrollView.contentInset = UIEdgeInsets(top: 0, left: -offsetX, bottom: 0, right: -offsetX) } } } |
よく分からないことも色々ありましたが、とりあえずは求める動作になりました!
良ければ皆さんもご参考にしてください。