【Swift】別のViewControllerのViewを使いたい!(ContainerView編)


はじめに

前回このような記事を投稿しました。親Viewに子Viewを追加したり消去したりして別のViewControllerのViewを使うというやり方です。
しかし、このやり方だと表示したいViewControllerの高さを変えたいとなった時にレイアウトがうまくいきません。(レイアウトを張っていないからですが、、、)
以下の緑のViewに赤いView Controllerの赤いView、青いView Controllerの青いViewを表示させようとすると、真ん中のlabelが中央に表示されません。これは、表示したいViewControllerのトップと緑のViewのトップが同じ設定になってるからですね。


無理矢理やる方法もありますが、もう少し上手い方法があるので、今回はそれを紹介します。
(僕の名誉のために、レイアウトをしっかり張って対応したものは以下に載せておきます(そもそも名誉なんかない))

今回はcenterXAnchor必要ないですけどね。
では、本題に入りましょう。

今回作るもの

赤いところと青いところがContainerViewです。ボタンを押したらテーブルビューにそれぞれ値を追加していくというシンプルなアプリを考えていきましょう。

GitHub

実装

Storyboardの作成

階層は以下のようになっています。(オートレイアウトなどはGitHubを参考にしてください)

ソースコード

ContainerViewを切り替えたいのでContainerAとContainerBの親Viewを用意して、ContainerAとContainerをセグメントで切り替えます。

TopViewController
import UIKit

final class TopViewController: UIViewController {

    @IBOutlet private weak var containerView: UIView!
    @IBOutlet private weak var containerA: UIView!
    @IBOutlet private weak var containerB: UIView!
    @IBOutlet private weak var tableView: UITableView!

    private var containers = [UIView]()
    private var texts = [String]()

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.dataSource = self
        containers.append(containerA)
        containers.append(containerB)
        containerView.bringSubviewToFront(containerA)

    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        switch segue.identifier {
            case "ASegueID":
                let redVC = segue.destination as! RedViewController
                redVC.delegate = self
            case "BSegueID":
                let blueVC = segue.destination as! BlueViewController
                blueVC.delegate = self
            default:
                fatalError()
        }
    }

    @IBAction private func segmentDidTapped(_ sender: UISegmentedControl) {
        let currentContainerView = containers[sender.selectedSegmentIndex]
        containerView.bringSubviewToFront(currentContainerView)
    }

}

extension TopViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        texts.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
        let text = texts[indexPath.row]
        cell.textLabel?.text = text
        return cell
    }

}

extension TopViewController: ContainerViewDelegate {
    func input(_ text: String) {
        texts.append(text)
        tableView.reloadData()
    }
}
RedViewController
protocol ContainerViewDelegate: AnyObject {
    func input(_ text: String)
}

final class RedViewController: UIViewController {

    weak var delegate: ContainerViewDelegate?

    @IBAction private func inputButtonDidTapped(_ sender: Any) {
        delegate?.input("AAAAA")
    }

}
BlueViewController
final class BlueViewController: UIViewController {

    weak var delegate: ContainerViewDelegate?

    @IBAction private func inputButtonDidTapped(_ sender: Any) {
        delegate?.input("BBBBB")
    }

}

解説

テーブルビューやデリゲート周りの解説は省きます。
Containerを切り替えたいので、それを格納する配列を用意します。

    private var containers = [UIView]()

先ほど用意した配列にContainerを格納し、初期起動した時はcontainerViewにcontainerAを一番前に表示させます。(bringSubviewToFront)

    override func viewDidLoad() {
        super.viewDidLoad()

        containers.append(containerA)
        containers.append(containerB)
        containerView.bringSubviewToFront(containerA)

    }

そして、セグメントが選択された時に表示するcontainerを配列から持ってきて先ほどの(bringSubviewToFrontを使って一番前に表示させます。

    @IBAction private func segmentDidTapped(_ sender: UISegmentedControl) {
        let currentContainerView = containers[sender.selectedSegmentIndex]
        containerView.bringSubviewToFront(currentContainerView)
    }

あとはデリゲートやらプロトコルやらを使ってテーブルビューを更新してあげれば完成です。
ちなみに、ビューヒエラルキーはこのようになっています。

伝えたかったこと

今回、ContainerViewを使ってViewControllerの切り替えを行いましたが、addChildを使ったものよりも簡単ではなかったでしょうか。addChildは追加する前にremoveする必要があったり(addSubViewせずにinsertSubViewを使えばいいだけですが)、オートレイアウトを考えなければいけませんでした。ContainerViewを使うことで、簡単にViewControllerの切り替えができることが伝えたかったことです。(コードでやるならaddChildを使うのが良さそうですね)

おわりに

ドキュメントは目を通しておいてください。
ContainerView