はじめに
UIStackViewは複数のViewを並べるのにとても便利なUIパーツです。
UIStackViewを使えば、複雑なAutoLayoutを組まなくても、簡単に動的で綺麗なレイアウトを実現できます。
今回はそんな便利なUIStackViewを活用すべく、コードからViewの追加・削除をしてみました。
やりたいこと
- Storyboard上でUIStackViewを画面に追加
- コード上からViewをUIStackViewに追加・削除
- UIStackViewにViewがない時はトリツメ表示

実践
Storyboard上でUIStackViewを画面に追加
Storyboard上で、UIStackViewやButtonなどのUIパーツと、最低限のAutoLayoutを追加します。
ViewControllerにTopView、StackView、BottomViewを追加

最低限のAutoLayoutを設定
StackViewの上下がTopView、BottomViewと接するように制約を追加
StackViewのheightを0に設定
StackViewのheight制約のPriority(優先度)をLow(250)に設定 ←【超重要】
後から、height制約を定義したViewをStackViewに追加するので、StackViewにPriority1000(default)の制約を付けていると、コンフリクトを起こしてしまいます。
優先度Lowのheight制約をStackViewに追加することで、コンパイルエラーを防ぎつつ、コンフリクトを回避することができます。

Viewの追加・削除の処理
「Viewを追加する」ボタンがタップされた時に、ViewをStackViewに追加し、
追加されたViewが選択されたら、対象のViewを削除するように実装します。
追加するViewを定義
適当にLabelと削除ボタンが実装されたViewを定義します。
UIStackViewからViewを取り除きたい場合は、通常通りremoveFromSuperview
を用います。
UIStackViewにはremoveArrangedSubview
と言う関数が用意されていますが、これを使ってもUIStackViewのレイアウト管理下から外れるだけで、SubViewとしては残ってしまうので、注意です。
import UIKit class StackViewCell: UIView { // ラベル let label = UILabel() init() { super.init(frame: CGRect()) // 削除ボタン let deleteButton = UIButton() deleteButton.setTitle("削除", for: .normal) deleteButton.setTitleColor(UIColor.blue, for: .normal) deleteButton.sizeToFit() // 削除ボタンがタップされた時の処理を登録 deleteButton.addTarget(self, action: #selector(tapDeleteButton(_:)), for: .touchUpInside) // ボタンをaddSubview addSubview(deleteButton) // ボタンが中心に配置されるように制約を追加 deleteButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true deleteButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true deleteButton.translatesAutoresizingMaskIntoConstraints = false // LabelをaddSubview addSubview(label) // labelが左上に配置されるように制約を追加 label.topAnchor.constraint(equalTo: topAnchor, constant: 10.0).isActive = true label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10.0).isActive = true label.translatesAutoresizingMaskIntoConstraints = false } // コードからしか呼ばないので、空実装 required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 削除ボタンが押された時に呼ばれるメソッド @objc func tapDeleteButton(_ sender: UIButton) { // superview(StackView)から自身を削除する removeFromSuperview() } }
Viewを追加する処理をViewControllerに実装
「Viewを追加する」ボタンがタップされたらStackViewに新規作成したViewが追加されるように実装します。
ここで注意するのは、下記の二点です。
- Viewにframeではなく、AutoLayoutでサイズを指定する
- stackViewには
addSubView
ではなくaddArrangedSubview
を使ってViewを追加する
addSubView
を実行しても、Viewは追加されるが、レイアウトはされない
import UIKit class ViewController: UIViewController { @IBOutlet weak var stackView: UIStackView! /// 作成したViewのカウンター var count:Int = 0 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } // 「Viewを追加する」ボタンがタップされた時に実行される処理 @IBAction func tapAddViewButton(_ sender: Any) { // 新規追加するViewを作成 let newView = StackViewCell() // 背景を緑に設定 newView.backgroundColor = UIColor.green // 枠線を設定 newView.layer.borderColor = UIColor.black.cgColor newView.layer.borderWidth = 1.0 // 追加されたViewがわかりやすいように、ナンバリング newView.label.text = "\(count)" newView.label.sizeToFit() newView.label.textColor = UIColor.black // ナンバリング用のカウンタをインクリメント count += 1 // 新規Viewに height=100 の制約を追加 ←【超重要】 newView.heightAnchor.constraint(equalToConstant: 100.0).isActive = true newView.translatesAutoresizingMaskIntoConstraints = false // これだとダメ // newView.frame = CGRect(x: 0, y: 0, width: stackView.frame.width, height: 100) // stackViewにnewViewを追加する stackView.addArrangedSubview(newView) // これだとダメ //stackView.addSubview(newView) } }
実際の動作
ビルドすると以下のように動作する。

まとめ
- 追加するViewはframeではなく、AutoLayoutを用いてサイズを定義する
addSubview
ではなく、addArrangedSubview
を用いてViewを追加する- UIStackViewの
removeArrangedSubview
ではなく、削除対象ViewのremoveFromSuperview
を用いてViewを削除する
ちなみに、addArrangedSubview(view:)
の代わりにinsertArrangedSubview(view:,at:)
を用いれば、Viewを追加するのではなく、好きな位置にViewを挿入できます。
また、removeArrangedSubview
ってどんな時に使えばいいんですかね?
知っている人がいれば教えてください。
コメントを残す