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

UITableViewのデリゲートからサブのビューを呼んでデータも渡す

More than 3 years have passed since last update.

要旨

  • ビューコントローラー間のデータの受け渡しは、ストーリーボードを分けてinstantiateInitialViewControllerを使うと簡単
  • .storyboardを新規作成したら is Initial View Controllerの設定を忘れない!

内容

 UITableViewにタッチすると、その情報を次のビューに送るサンプルです。
 こちらのツイートを見たので書きました。

 たぶん、こういうモノだと思います (違ったらごめんなさい)。

 名称未設定.png

 もし違ったとしても、これは前にすごいハマってひどい目にあったので、備忘録も兼ねて書きます。更にやろうと思えば色々できますので、その雛形までに。
 GitHubに置いておきます

※ 私の最近の趣味は「swift」でツイート検索してニヨニヨすることです。

細かい話

ストーリーボードを分けると楽

 データを渡す方法はいろいろあるのでしょうが、テーブルビューから次のビューを呼ぶなら「専用コントローラー&専用ストーリーボード」を用意するのが分かりやすくて楽だと思います (今回は関係ありませんが、セル専用xibを作ったりするのも楽ですね。どうもプログラムを書いてUIを作るのは苦手です)。
 まず最初にサブのビューコントローラーを用意して、データを渡すために変数を準備します。

class SubViewController: UIViewController {
    var targetText: String!
    @IBOutlet weak var label: UILabel! {
        didSet {
            label.text = targetText
        }
    }
}

 ちなみにdidSetの部分はこれを見て爆速でパクりました。

 これは本当に便利です。viewDidLoadを占拠する連中をほぼ追放できます。

 ストーリーボードの設定は特に難しくないと思いますが、"is Initial View Controller" だけは本当に忘れがちなので注意したいところです。これを忘れると辛い目に遭います。
 そうして専用コントローラーを専用ストーリーボードにセットしたなら、いよいよ呼ぶ側の処理をデリゲートにて設定します。

extension ViewController {
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        // 渡すデータ
        let text = "\(contents[indexPath.section].title) \(contents[indexPath.section].items[indexPath.row])"
        // 次のビューコントローラーを用意
        let sb = UIStoryboard(name: "Sub", bundle: NSBundle.mainBundle())
        let vc = sb.instantiateInitialViewController() as SubViewController
        vc.targetText = text
        // プッシュ:この時点でIBOutletが用意され、didSetも働く
        navigationController?.pushViewController(vc, animated: true)
    }
}

テーブルの中身をクラスにすると楽

「UITableViewの正しい使い方」というのが存在するのか知りませんし、おそらく人それぞれだと思いますが、最近の我が家では「データソース機能を事実上もっているクラス」を作るのがブームです。
(何となくこれが一番落ち着くというか… 根拠のない気分的なものです。)

 サンプルに入っているTableContentクラスのインスタンスは、セクション単位で情報を保持します。具体的には、セクションタイトルとセクション内部の文字群を持ちます。もちろんもっと大量の情報を持たせることもできますが、そういう時は var items: [HogeClass]! とでも書き換えて使うことになるかと思います。そっちの方が楽そうだからです。
 TableContentクラスには、プロパティリストから情報を持ってくるクラス関数と、セルを作って返すメソッドがあります。

class TableContent {
    var title: String!
    var items: [String]!

    class func readPropertyList() -> [TableContent] {
        var contents = [TableContent]()

        let filePath = NSBundle.mainBundle().pathForResource("tableContents", ofType: "plist")!
        let rawDic = NSDictionary(contentsOfFile: filePath) as [String: [String]]

        for title in rawDic["title"]! {
            let c = TableContent()
            c.title = title
            c.items = rawDic[title]
            contents.append(c)
        }
        return contents
    }
    func makeTableCell(tableview: UITableView, _ indexPath: NSIndexPath) -> UITableViewCell {
        let cellID = "tableViewCell"
        let cell = tableview.dequeueReusableCellWithIdentifier(cellID) as? UITableViewCell ?? UITableViewCell()
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
}

 .plistを使っているのでローカライズもできます。

 ここまで書いておけば、テーブルビューの実装はかなり簡単です。
 まずdidLoadの段階でプロパティリストを読んでおきます。

class ViewController: UITableViewController {
    // テーブルのコンテンツ
    var contents: [TableContent]!

    override func viewDidLoad() {
        super.viewDidLoad()
        // コンテンツの取得
        contents = TableContent.readPropertyList()
    }
}

 データソースの設定が必要ですが、これは機械的に書くだけですね。
 ビューコントローラーにはなるべく細かいことを書きたくありませんが、しかしデータソースを別クラスに分離する程でもないので、こういう構造にしてみました。

extension ViewController {
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return contents.count
    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return contents[section].items.count
    }
    override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return contents[section].title
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        return contents[indexPath.section].makeTableCell(tableView, indexPath)
    }
}

 なお言うまでもありませんが、この方法が最高で他はダメという趣旨ではありません。

ViewControllerをエクステンションで細切れにすると楽

 今回のように短いサンプルだと読みにくいだけですが、1つのクラスでデリゲートの継承を5つも6つもやるときには別ファイルに分けたほうが楽だと思います。
 これも何方かの方法をパクりました (思い出せず)。

Why do not you register as a user and use Qiita more conveniently?
  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