Help us understand the problem. What is going on with this article?

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

はじめに

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

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

やりたいこと

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

実践

Storyboard上でUIStackViewを画面に追加

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


2. 最低限のAutoLayoutを設定

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away