コードでUIScrollView(とその子どもたち)をAutoLayout配置

UIScrollViewのAutoLayout制御はいろいろな人がハマるらしくていくつかの記事が見つかるが、自分が欲しい感じで解説しているものがなかったので書いてみる。

#前提
- コードで書く。ストーリーボードは使わない。

#概念的なものの解説

UIScrollViewは重要な長方形を2つ持っている。

- 画面上で占める領域、俗に言うUIScrollView.frame
- スクロールすることによって見ることができる領域、俗に言うUIScrollView.contentSize

まあこれらはおなじみの知識である。んで、

UIScrollViewをAutoLayout制御するときは

- 外側のUIViewと関連つけたときはUIScrollView.frameのほうに結びつく
- 内側の（UIScrollViewにaddされている）UIViewと関連つけたときはUIScrollView.contentSizeのほうに結びつく

という特殊なルールがある。
また、結構くせものなのが

- UIScrollView.contentSizeは内側の部品のサイズをもとにして決まる。

ということ。なので内部部品のサイズを先に決めてやる。これは文の説明が難しいのでコードを見て下さい。

以上をまとめてコードで書きました。外、スクロールビュー、内、の3つを、それぞれ50づつ間を開けています。

```swift
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
	}
}
```
![Simulator Screen Shot - iPhone 11 - 2019-10-16 at 09.12.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46631/a8cd3321-ba28-abfd-fac7-f94e8374f496.png)

UIScrollViewに外側から制約をつけたときと、内側から制約をつけたときで、挙動が変わっているのがわかるかと思います。inViewにはwidth、heightと上下左右の6個の制約がついているが、これは上で書いたようにinViewのサイズを決定させて、それをもとにscrollViewのcontentSizeを決定させるため。（書く順番は前後しても大丈夫だと思いますが）

また、どうしても内部部品のサイズをUIScrollViewのframeのwidth(or height)に合わせたいときはあるよね、というのもわかる。そういうときは、UIScrollViewの下(奥？)に同サイズのUIViewを敷いてそれのwidth（or height）に合わせる手法をとる。コードは以下のとおり。

```swift
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
	}
}
```
![Simulator Screen Shot - iPhone 11 - 2019-10-16 at 09.14.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46631/53ab3e27-2c55-f098-186e-273ae23a42e4.png)

#最後に
簡単な仕様の自分のアプリでしか検証していないコードなので、別のパターンのときには通用しなかったり、ここに書いてある説明が不適切だったりするかもしれません。