#はじめに
前回このような記事を投稿しました。親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をセグメントで切り替えます。
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()
}
}
protocol ContainerViewDelegate: AnyObject {
func input(_ text: String)
}
final class RedViewController: UIViewController {
weak var delegate: ContainerViewDelegate?
@IBAction private func inputButtonDidTapped(_ sender: Any) {
delegate?.input("AAAAA")
}
}
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