UIScrollViewのAutoLayout制御はいろいろな人がハマるらしくていくつかの記事が見つかるが、自分が欲しい感じで解説しているものがなかったので書いてみる。
#前提
- コードで書く。ストーリーボードは使わない。
#概念的なものの解説
UIScrollViewは重要な長方形を2つ持っている。
- 画面上で占める領域、俗に言うUIScrollView.frame
- スクロールすることによって見ることができる領域、俗に言うUIScrollView.contentSize
まあこれらはおなじみの知識である。んで、
UIScrollViewをAutoLayout制御するときは
- 外側のUIViewと関連つけたときはUIScrollView.frameのほうに結びつく
- 内側の(UIScrollViewにaddされている)UIViewと関連つけたときはUIScrollView.contentSizeのほうに結びつく
という特殊なルールがある。
また、結構くせものなのが
- UIScrollView.contentSizeは内側の部品のサイズをもとにして決まる。
ということ。なので内部部品のサイズを先に決めてやる。これは文の説明が難しいのでコードを見て下さい。
以上をまとめてコードで書きました。外、スクロールビュー、内、の3つを、それぞれ50づつ間を開けています。
class ViewController: UIViewController {
let outView = UIView()
let scrollView = UIScrollView()
let inView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
outView.backgroundColor = UIColor.red
scrollView.backgroundColor = UIColor.orange
inView.backgroundColor = UIColor.yellow
scrollView.addSubview(inView)
outView.addSubview(scrollView)
view.addSubview(outView)
outView.translatesAutoresizingMaskIntoConstraints = false
outView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 50.0).isActive = true
outView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -50.0).isActive = true
outView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
outView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50.0).isActive = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.leftAnchor.constraint(equalTo: outView.leftAnchor, constant: 50.0).isActive = true
scrollView.rightAnchor.constraint(equalTo: outView.rightAnchor, constant: -50.0).isActive = true
scrollView.topAnchor.constraint(equalTo: outView.topAnchor, constant: 50.0).isActive = true
scrollView.bottomAnchor.constraint(equalTo: outView.bottomAnchor, constant: -50.0).isActive = true
inView.translatesAutoresizingMaskIntoConstraints = false
inView.widthAnchor.constraint(equalToConstant: 1000.0).isActive = true
inView.heightAnchor.constraint(equalToConstant: 1000.0).isActive = true
inView.leftAnchor.constraint(equalTo: scrollView.leftAnchor, constant: 50.0).isActive = true
inView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: -50.0).isActive = true
inView.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 50.0).isActive = true
inView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -50.0).isActive = true
}
}
UIScrollViewに外側から制約をつけたときと、内側から制約をつけたときで、挙動が変わっているのがわかるかと思います。inViewにはwidth、heightと上下左右の6個の制約がついているが、これは上で書いたようにinViewのサイズを決定させて、それをもとにscrollViewのcontentSizeを決定させるため。(書く順番は前後しても大丈夫だと思いますが)
また、どうしても内部部品のサイズをUIScrollViewのframeのwidth(or height)に合わせたいときはあるよね、というのもわかる。そういうときは、UIScrollViewの下(奥?)に同サイズのUIViewを敷いてそれのwidth(or height)に合わせる手法をとる。コードは以下のとおり。
class ViewController: UIViewController {
let outView = UIView()
let scrollView = UIScrollView()
let inView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
outView.backgroundColor = UIColor.red
scrollView.backgroundColor = UIColor.orange
inView.backgroundColor = UIColor.yellow
scrollView.addSubview(inView)
outView.addSubview(scrollView)
view.addSubview(outView)
outView.translatesAutoresizingMaskIntoConstraints = false
outView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 50.0).isActive = true
outView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -50.0).isActive = true
outView.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
outView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50.0).isActive = true
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.leftAnchor.constraint(equalTo: outView.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: outView.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: outView.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: outView.bottomAnchor).isActive = true
inView.translatesAutoresizingMaskIntoConstraints = false
//↓↓↓これが味噌↓↓↓
inView.widthAnchor.constraint(equalTo: outView.widthAnchor).isActive = true
inView.heightAnchor.constraint(equalToConstant: 1000.0).isActive = true
inView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
inView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
inView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
inView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}
}
#最後に
簡単な仕様の自分のアプリでしか検証していないコードなので、別のパターンのときには通用しなかったり、ここに書いてある説明が不適切だったりするかもしれません。