🐡
UIScrollViewとUIStackViewで快適なスクロールを(Swift)
はじめに
未だに UIScrollView を使うときに制約どうつけるんだ?と迷うのでまとめました。
こういうのができるようになります。
レイアウトガイド
iOS 11 から Contetnt Layout Guide と Frame Layout Guide が登場し ScrollView のレイアウトはかなり楽になりました。
それぞれ下記を表しています。
- Contetnt Layout Guide:ScrollView の中身
- Frame Layout Guide:ScrollView 自体
縦スクロール
Storyboard を使って縦スクロールができる画面をつくっていきます。
- Storyboard で View に UIScrollView をのせる
- UIScrollView の上下左右を Safe Area(もしくは super view)に合わせて制約をつける
- UIScrollView に UIStackView(Vertical)をのせる
- UIStackView の上下左右を Contetnt Layout Guide に合わせる
- UIStackView の幅を Frame Layout Guide に合わせる
- UIStackView の Instrinsic Size を Placeholder にする
こんな感じです。
6 は UIStackView の高さが確定しておらず警告が出るのでそれの回避策として Placeholder を設定しています。
あとは UIStackView を ViewController に紐づけてコードでコンテンツをのせて完成。
final class ViewController: UIViewController {
@IBOutlet private weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
let v1 = UIView()
v1.backgroundColor = .systemRed
stackView.addArrangedSubview(v1)
v1.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v1.heightAnchor.constraint(equalToConstant: 100)])
let v2 = UIView()
v2.backgroundColor = .systemYellow
stackView.addArrangedSubview(v2)
v2.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v2.heightAnchor.constraint(equalToConstant: 200)])
let v3 = UIView()
v3.backgroundColor = .systemOrange
stackView.addArrangedSubview(v3)
v3.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v3.heightAnchor.constraint(equalToConstant: 900)])
let v4 = UIView()
v4.backgroundColor = .systemBlue
stackView.addArrangedSubview(v4)
v4.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v4.heightAnchor.constraint(equalToConstant: 300)])
let v5 = UIView()
v5.backgroundColor = .systemGreen
stackView.addArrangedSubview(v5)
v5.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v5.heightAnchor.constraint(equalToConstant: 100)])
}
}
横スクロール
次は横スクロールができる画面をつくっていきます。
- Storyboard で View に UIScrollView をのせる
- UIScrollView の上下左右を Safe Area(もしくは super view)に合わせて制約をつける
- UIScrollView に UIStackView(Horizontal)をのせる
- UIStackView の上下左右を Contetnt Layout Guide に合わせる
- UIStackView の高さを Frame Layout Guide に合わせる
- UIStackView の Instrinsic Size を Placeholder にする
こんな感じです。
あとは UIStackView を ViewController に紐づけてコードでコンテンツをのせて完成。
final class ViewController: UIViewController {
@IBOutlet private weak var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
let v1 = UIView()
v1.backgroundColor = .systemRed
stackView.addArrangedSubview(v1)
v1.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v1.widthAnchor.constraint(equalToConstant: 100)])
let v2 = UIView()
v2.backgroundColor = .systemYellow
stackView.addArrangedSubview(v2)
v2.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v2.widthAnchor.constraint(equalToConstant: 200)])
let v3 = UIView()
v3.backgroundColor = .systemOrange
stackView.addArrangedSubview(v3)
v3.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v3.widthAnchor.constraint(equalToConstant: 900)])
let v4 = UIView()
v4.backgroundColor = .systemBlue
stackView.addArrangedSubview(v4)
v4.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v4.widthAnchor.constraint(equalToConstant: 300)])
let v5 = UIView()
v5.backgroundColor = .systemGreen
stackView.addArrangedSubview(v5)
v5.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v5.widthAnchor.constraint(equalToConstant: 100)])
}
}
UIScrollViewのサブクラスを作ってやってみる
UIScrollView のサブクラスを作って汎用的にしてみます。
下記のように縦スクロール用と横スクロール用の 2 つのクラスを作成します。
final class VerticalScrollView: UIScrollView {
private var stackView: UIStackView = {
let stack = UIStackView()
stack.axis = .vertical
return stack
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: contentLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentLayoutGuide.trailingAnchor),
stackView.widthAnchor.constraint(equalTo: frameLayoutGuide.widthAnchor)
])
}
func addContent(_ view: UIView) {
stackView.addArrangedSubview(view)
}
}
final class HorizontalScrollView: UIScrollView {
private var stackView: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
return stack
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: contentLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: contentLayoutGuide.bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: contentLayoutGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: contentLayoutGuide.trailingAnchor),
stackView.heightAnchor.constraint(equalTo: frameLayoutGuide.heightAnchor)
])
}
func addContent(_ view: UIView) {
stackView.addArrangedSubview(view)
}
}
Storyboard でこんな感じで配置します。
コードでコンテンツをのせると完成!
final class ViewController: UIViewController {
@IBOutlet private weak var verticalScrollView: VerticalScrollView!
@IBOutlet private weak var horizontalScrollView: HorizontalScrollView!
override func viewDidLoad() {
super.viewDidLoad()
setupVertical()
setupHorizontal()
}
private func setupVertical() {
let v1 = UIView()
v1.backgroundColor = .systemRed
verticalScrollView.addContent(v1)
v1.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v1.heightAnchor.constraint(equalToConstant: 100)])
let v2 = UIView()
v2.backgroundColor = .systemYellow
verticalScrollView.addContent(v2)
v2.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v2.heightAnchor.constraint(equalToConstant: 200)])
let v3 = UIView()
v3.backgroundColor = .systemOrange
verticalScrollView.addContent(v3)
v3.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v3.heightAnchor.constraint(equalToConstant: 900)])
let v4 = UIView()
v4.backgroundColor = .systemBlue
verticalScrollView.addContent(v4)
v4.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v4.heightAnchor.constraint(equalToConstant: 300)])
let v5 = UIView()
v5.backgroundColor = .systemGreen
verticalScrollView.addContent(v5)
v5.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v5.heightAnchor.constraint(equalToConstant: 100)])
}
private func setupHorizontal() {
let v1 = UIView()
v1.backgroundColor = .systemPurple
horizontalScrollView.addContent(v1)
v1.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v1.widthAnchor.constraint(equalToConstant: 100)])
let v2 = UIView()
v2.backgroundColor = .systemMint
horizontalScrollView.addContent(v2)
v2.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v2.widthAnchor.constraint(equalToConstant: 200)])
let v3 = UIView()
v3.backgroundColor = .systemPink
horizontalScrollView.addContent(v3)
v3.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v3.widthAnchor.constraint(equalToConstant: 900)])
let v4 = UIView()
v4.backgroundColor = .systemCyan
horizontalScrollView.addContent(v4)
v4.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v4.widthAnchor.constraint(equalToConstant: 300)])
let v5 = UIView()
v5.backgroundColor = .systemBrown
horizontalScrollView.addContent(v5)
v5.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([v5.widthAnchor.constraint(equalToConstant: 100)])
}
}
おわりに
これでさくさくスクロールできるようになりました。
VerticalScrollView
と HorizontalScrollView
はコードでしかコンテンツをのせられないので使いやすいかはわかりません(ちなみに私はこういう書き方をしたことはないです🤥)。
まだまだ様々な理由で UIKit をはがせないプロジェクトもあるかと思いますががんばりましょう💪
Discussion