UIStackViewにコードからViewを追加・削除する方法

アイキャッチ

はじめに

UIStackViewは複数のViewを並べるのにとても便利なUIパーツです。
UIStackViewを使えば、複雑なAutoLayoutを組まなくても、簡単に動的で綺麗なレイアウトを実現できます。

今回はそんな便利なUIStackViewを活用すべく、コードからViewの追加・削除をしてみました。

やりたいこと

  • Storyboard上でUIStackViewを画面に追加
  • コード上からViewをUIStackViewに追加・削除
  • UIStackViewにViewがない時はトリツメ表示
やりたいこと

実践

Storyboard上でUIStackViewを画面に追加

Storyboard上で、UIStackViewやButtonなどのUIパーツと、最低限のAutoLayoutを追加します。

ViewControllerにTopView、StackView、BottomViewを追加

Storyboard上でUIStackViewを画面に追加

最低限のAutoLayoutを設定

StackViewの上下がTopView、BottomViewと接するように制約を追加

StackViewのheightを0に設定

StackViewのheight制約のPriority(優先度)をLow(250)に設定 ←【超重要】

後から、height制約を定義したViewをStackViewに追加するので、StackViewにPriority1000(default)の制約を付けていると、コンフリクトを起こしてしまいます。

優先度Lowのheight制約をStackViewに追加することで、コンパイルエラーを防ぎつつ、コンフリクトを回避することができます。

最低限のAutoLayoutを設定

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ってどんな時に使えばいいんですかね?
知っている人がいれば教えてください。

参考

https://developer.apple.com/documentation/uikit/uistackview

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です