(UITableView上に)ポップアップを表示したい!

投稿者: Anonymous

TableViewController に PopupViewController のポップアップを表示したいのですがうまく行かないです。 何故なのでしょうか。どうしたら良いですか。

画像の説明をここに入力
** この右側のポップアップを表示したい**

画像の説明をここに入力

↓↓こうなります。
画像の説明をここに入力

TableViewのクラス


import UIKit

class TableViewController: UITableViewController {

    var array : Array = [1, 2, 3, 4, 5]

    override func viewDidLoad() {
        super.viewDidLoad()


    }


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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "customTableViewCell", for: indexPath) as! costomViewCell

        cell.skillName.text = "〇〇〇〇〇〇(array[indexPath.row])"
        cell.goalCountLabel.text = "20 : 00"

        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            array.remove(at: indexPath.row)
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
    }


    let popupViewController = PopupViewController()
    @IBAction func addSkillButton(_ sender: UIBarButtonItem) {
        view.addSubview(popupViewController.view)
    }


    @IBAction func secret(_ sender: UIBarButtonItem) {
    }


}


Popupクラス


import UIKit

class PopupViewController: UIViewController {

    @IBOutlet weak var newSkillView: UIView!
    @IBOutlet weak var newSkillLabel: UILabel!
    @IBOutlet weak var newSkillText: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = UIColor(
            red: 150/255,
            green: 150/255,
            blue: 150/255,
            alpha: 0.6
        )
     // newSkillView.layer.cornerRadius = 25.0 この処理を行うとThread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value というエラーが出る。。。何故???

    }

    @IBAction func newSkillButton(_ sender: UIButton) {
    }

    @IBAction func tapGestureRecogButton(_ sender: UITapGestureRecognizer) {
        self.view.removeFromSuperview()

    }

}

解決

こうなります の画像を見ると、table viewの部分がグレーになっていますよね? これはPopupViewController表示はされているけど、中身がない 状態になっているからです。

この事象でいちばんの元凶はこの行です。

    let popupViewController = PopupViewController()

view controllerの引数なしのイニシャライザを呼んでいますが、これではiOSは、storyboardの中身を読みに行ってくれません。

従って、

  • IBOutletは接続してくれない

    newSkillViewはnilのままなんで、実行時にUnexpectedly found nilになる

  • IBActionは接続してくれない

  • そもそもstoryboardエディタ上で作成したデザインは一切反映してくれない

    UIViewUILabelUITextFieldも、一切画面に現れないことになります。


今回の事象の直接の原因にはなっていませんが、あなたのコードにはもう一つ致命的にまずい点があります。

        view.addSubview(popupViewController.view)

view controllerのviewだけを別のview controllerの支配下にあるviewaddSubview(_:)で追加しています。

iOSでは、

  • 全てのUIViewは一つのview controllerの支配下にある

  • UIViewController用のメソッドには「現在表示を担当している」か「現在アクティブなview controllerチェーンの一部である」かで無いと動かないものがある

と言うことで、現在のあなたのコードではPopupViewControllerは表示されています(先ほど書いたように中身は無いですが)が、今後PopupViewControllerにいろんなコードを足していって、上記のようなチェックに引っかかるメソッドを使うと、実行時エラーになってしまいます。


と言うわけで、iOSで「ポップアップを表示」する場合、一般的(storyboardを使う場合)には、

  • 普通に表示されるポップアップをstoryboardでデザインする

    背景色の一部はポップアップっぽく半透明にしますが、これはstoryboard上で設定できます

  • storyboard上でポップアップのView Controller属性 PresentationOver Current Context にしてやる

    Over Current Context

  • 必ずstoryboardが使われるようにインスタンス化する

  • 後は普通にpresentなどで表示してやる

と言う手順になります。

コードの方の修正は、例えばこんな感じです。

TableViewのクラス

    //インスタンスプロパティとしての`popupViewController`は不要
    @IBAction func addSkillButton(_ sender: UIBarButtonItem) {
        //元(`TableViewController`)と同じstoryboardは`self.storyboard`で取得できる
        let storyboard = self.storyboard!
        //storyboardからインスタンス化すると、iOSがいろいろと面倒を見てくれる
        let popupViewController = storyboard.instantiateViewController(withIdentifier: "PopupViewController") as! PopupViewController

        //普通のview controllerとして、presentで表示する
        self.present(popupViewController, animated: true, completion: nil)
    }

セグエを使ってもstoryboardは読み込まれるんですが、説明が書きにくいので今回はinstantiateViewController(withIdentifier:)を使っています。(storyboard上でポップアップの Storyboard ID をコードに合わせてPopupViewControllerにしてやる必要があります。)

Popupクラス

    override func viewDidLoad() {
        super.viewDidLoad()

        //...
        //Storyboardを通じてインスタンス化するとIBOutletを接続してくれるので実行時エラーにならない
        newSkillView.layer.cornerRadius = 25.0

    }

    //...

    @IBAction func tapGestureRecogButton(_ sender: UITapGestureRecognizer) {
        //普通のview controllerとして、presentで表示するので、dismissで元に戻れる
        self.dismiss(animated: true, completion: nil)
    }

と、ここまででポップアップ表示はできるはずなんですが、ポップアップでOKを押すとtable viewの方に結果が反映されないといけませんよね。以前の別質問に対する回答の応用(と言うよりコアな部分は殆どそのまんま)なんで、説明軽めにコードだけ示しておきます。

Popupクラス
デリゲート用のプロトコル、デリゲートプロパティ、デリゲートの呼び出しを追加。

//delegate用のプロトコルを宣言する
protocol PopupViewControllerDelegate: class {
    //`label`は、今は`Int`にしておく
    func popupViewController(_ popupVC: PopupViewController, didEnterLabel label: Int)
}
class PopupViewController: UIViewController {
    //`delegate`変数をweakで宣言する
    weak var delegate: PopupViewControllerDelegate?

    //...

    @IBAction func newSkillButton(_ sender: UIButton) {
        //`label`は、今は`Int`にしてある
        let label = Int(newSkillText.text ?? "") ?? 0
        //その`label`の値を使ってdelegateメソッドを呼び出す
        delegate?.popupViewController(self, didEnterLabel: label)
    }

    //...
}

TableViewのクラス
PopupViewControllerのデリゲートは自分であることを宣言してやる

class TableViewController: UITableViewController {

    //...

    //↓`###`のある行を追加
    //インスタンスプロパティとしての`popupViewController`は不要
    @IBAction func addSkillButton(_ sender: UIBarButtonItem) {
        //元(`TableViewController`)と同じstoryboardは`self.storyboard`で取得できる
        let storyboard = self.storyboard!
        //storyboardからインスタンス化すると、iOSがいろいろと面倒を見てくれる
        let popupViewController = storyboard.instantiateViewController(withIdentifier: "PopupViewController") as! PopupViewController
        popupViewController.delegate = self //### `PopupViewController`のdelegateは自分(`self`)が引き受ける
        //普通のview controllerとして、presentで表示する
        self.present(popupViewController, animated: true, completion: nil)
    }

    //...
}

//`PopupViewControllerDelegate`としてのお仕事を定義する
extension TableViewController: PopupViewControllerDelegate {

    func popupViewController(_ popupVC: PopupViewController, didEnterLabel label: Int) {
        let indexPath = IndexPath(row: array.count, section: 0)
        array.append(label)
        //↑↓辻褄を合わせる
        tableView.insertRows(at: [indexPath], with: .automatic)
        popupVC.dismiss(animated: true, completion: nil)
    }

}
回答者: Anonymous

Leave a Reply

Your email address will not be published. Required fields are marked *