2017年1月9日月曜日

TableViewとNavigationControllerで画面遷移

TableViewでデータを選択 -> 画面遷移 というケースがよく使われるとの事なので試してみました。

画面1 : tableViewでリストを表示 (DataTableViewController.swift / User List Scene)
画面2 : tableViewで選択されたデータを受け取り、画面遷移 (ViewController.swift / View Controller Scene)

画面1は新規に作成、画面2はプロジェクト作成時に生成されたものです。
NavigationControllerを配置して、画面遷移を実施します。

1.    SingleViewでprojectを作成
2.    Main.Storyboardを選択し、Navigation Controllerを最初からあるViewControllerの左にドラッグドロップ
   Navigation ControllerとRoot View Controller (Table View)の繋がった箱がコピーされる
   (最初のView Controller含めて3つの箱が表示されている)
3.    ↑で追加したNavigation Controllerを選択し、右ペインのAttributes(右ペイン上部の"-↓-“みたいなアイコンを選択しておく)のViewControllerの項目にある”Is Initial View Controller”をチェックする
  最初のViewControllerの左にあった矢印が消えて、追加したNavigation Controllerの左に矢印が追加される (= アプリが起動した時に最初に表示するシーンになるという意味)
4.    (左2番目ペインの)Root View Controller Scene - Root View Controller - Root View Controllerを選択し、タイトルを書き換える
    “***リスト”など (オブジェクトはRootViewController - Table Viewになっている)
5.    (      〃              )Root View Controller - Table View - Table View Cell を選択
  1.    右ペインのStyleをSubtitle とする (title + subtitleの表示になる)
  2.         〃       Identifer(一つ下にある)を”Cell”と入力する (Cellでアクセス出来る?)
  3.         〃       Accosoryを"Dsiclosure Indicator”を選択する (画面遷移の”>”表示が出る)
segueで接続
6.    (左2番目のペイン)TableView - Cell を選択した状態で、Cellをcontrol押しのIBAction接続で”最初からあった”Viewに接続する
   この時にダイアログが表示されるので”Selection Segue - show”を選択する
   テーブルViewコントローラから最初のViewに矢印がひかれる
7.    ↑でひっぱったsegueを選択し、identiferを入力する
   showCellDataとか…これは後ほどswiftコードの識別子として使用する
Table Viewのカスタムクラス(***.swift)を作成
8.    TableViewControllerのカスタムクラスを作成する
  1.    File -> New -> FileでiOS-Sourceを選択、Cocoa Touch Classを選択し、Nextを押す
  2.    Class名をつける (DataTableViewControllerとか)、subClassを”UITableViewContoller”を選択してNext
  3.    置き場所はdefault(ViewController.swiftなどが置かれている場所)でよい
9.    ↑で作ったカスタムclassをview(今回はリスト)と関連づける
  1.    storyboardを選択し、TableViewを選択、(GUIの)上アイコン左(RootViewContlller)を選択
  2.    右ペインの左から2番目のアイコン(propertyっぽいやつ)を選択してClassから”DataTableViewController”を選択する  << ****.swiftのclass名を選択するという意味
10.    カスタムクラスにTableViewに表示させるデータを記載する
                DataTableTableViewController.swiftを選択
  1. tableに表示されるリストを書く (class直下で良い)
    // セルに表示するデータ
    let modelList = [
        (company: "aaa", model1: "a01", model2: "a02"),
        (company: "bbb", model1: "b01", model2: "b02"),
        (company: "ccc", model1: "c01", model2: "c02"),
        (company: "ddd", model1: "d01", model2: "d02")
    ]
    2.    コメントアウトされているtableViewメソッドを有効化して、rawのデータを返すコードを記載する
     // tableViewのセルに表示する部分
     override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
     // 元からあるコードのCellの識別子を書き換える
        // Configure the cell… // 以下の部分は手書きで記載
        let cellData = modelList[indexPath.row]
      
        // cellのプロパティに設定
        cell.textLabel?.text = cellData.company
        cell.detailTextLabel?.text = cellData.model1
      
        return cell    // ここは元のコードのまま
    }
11. データを渡す部分を有効化して記載
    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
        if segue.identifier == "showCellData” {    // segueの識別子に合わせる事!
            if let indexPath = self.tableView.indexPathForSelectedRow{
                let modelData = modelList[indexPath.row]
                (segue.destinationViewController as! ViewController).data = modelData
                // 移動先にメンバ”data”を作る事(この時点で存在しない場合、メンバが無いerrorが出ている)
            }
        }
    }

12. 受け渡しの個数、及びtable数を返している箇所を実装する
  numberOfSectionsInTableView と tableView メソッドがreturn 0になっているので以下を返すように変更する
  numberOfSectionsInTableView    :    return 1
  tableView:                    return list.count
  (これをやらないとtableに表示されない)
  具体的には
    // セクションの個数
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 0
    }

    // セクション内の行数
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        // return 0
       
        return modelList.count

13. 表示するViewController.swift (tableViewから遷移される側)に渡されたデータを表示するコードを実装する
   実際は、tableのデータを処理する部分。今回はサンプルなので、ラベルに渡されたデータを表示するだけのコードを記載
表示するViewControllerに、渡されたデータを表示するコードを記載する

    // 渡されるデータを定義する (これが書かれていな買ったので、"12"ではエラーが出ていた)
    var data:(company:String, model1:String, model2:String)?
  
    // ラベルに接続するコード
    func dispLabel(){
        if let dispData = data {
            Label1.text = dispData.company
            Label2.text = dispData.model1
            Label3.text = dispData.model2
        }
    }
  
    @IBOutlet weak var Label1: UILabel!
    @IBOutlet weak var Label2: UILabel!
    @IBOutlet weak var Label3: UILabel!

(結構長いが...)とりあえず、上記実装で
- TableViewで選択したデータを、ViewControllerで表示
という動作が出来るようになりました。

注意点は
- 遷移先画面へのデータの受け渡しでは、受け側でデータを定義していないとエラーが出る(後で受け側に定義すれば出なくなる)
- TableViewの画面表示させるlistの定義と、仮実装・コメントされているスタブへの記述
 Tableに適切なデータが表示されていない場合は、これが原因だったりしました
- swiftの記述で、identifierを参照するので、GUIに設定した内容を忘れないようにする
- DataTableViewController.swiftを生成するので、main.storyboardの画面にGUIで紐付けする
- NavigationControllerをGUIで配置したり、初期画面を設定したりするのを忘れない
辺りでしょうか

swiftのコードを記載します。
DataTableViewController.swift (DataTableViewControllerとして追加したクラス)
//
//  DataTableTableViewController.swift
//  TableView-Navigation01//

import UIKit

class DataTableTableViewController: UITableViewController {

    // リストを追加
    let modelList = [
        (company: "aaa", model1: "a01", model2: "a02"),
        (company: "bbb", model1: "b01", model2: "b02"),
        (company: "ccc", model1: "c01", model2: "c02"),
        (company: "ddd", model1: "d01", model2: "d02")
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    // セクションの個数を返すように中身を変更する
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        // return 0
        return 1        // 変更
    }

    // セクション内の行数を返すコードに中身を変更する
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        // return 0
        return modelList.count      // 変更
    }

    // 元々コメントアウトされているコードを有効化して中に処理を書く
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) // param1をGUIで設定したIDに合わせる

        // Configure the cell… // 以下の部分は手書きで記載
        let cellData = modelList[indexPath.row]
        // cellのプロパティに設定
        cell.textLabel?.text = cellData.company
        cell.detailTextLabel?.text = cellData.model1
        // ここまで

        return cell
    }


    /*
    // Override to support conditional editing of the table view.
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return false if you do not want the specified item to be editable.
        return true
    }
    */

    /*
    // Override to support editing the table view.
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            // Delete the row from the data source
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }   
    }
    */

    /*
    // Override to support rearranging the table view.
    override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {

    }
    */

    /*
    // Override to support conditional rearranging of the table view.
    override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        // Return false if you do not want the item to be re-orderable.
        return true
    }
    */

    // MARK: - Navigation

    // 以下を有効化して中身を記載(移動する前にデータを受け渡す)
    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
       
        // 以下のコードを記述する
        if segue.identifier == "showCellData" {
            // 行を取り出す
            if let indexPath = self.tableView.indexPathForSelectedRow{
                let modelData = modelList[indexPath.row]    // 上で作ってある配列から行をインデックスとして取り出し
                // (segue.destinationViewController as! ViewController).data = modelData
                (segue.destinationViewController as! ViewController).data = modelData   // 移動先にメンバ"data"を作る事
            }
        }
    }
}

---
ViewController.swift (渡される側、そして元々あった画面に紐付けされている)
//
//  ViewController.swift
//  TableView-Navigation01
//
//

import UIKit

class ViewController: UIViewController {

    // 渡す側のviewcontrollerから (segue.destinationViewController as! ViewController).dataとしてアクセスされる
    var data:(company:String, model1:String, model2:String)?    // 渡されるデータを定義する
   
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        // 表示する関数を呼び出す
        dispLabel()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // 暫定表示UI (GUIからIBOutletで紐付け)
    @IBOutlet weak var label1: UILabel!
    @IBOutlet weak var label2: UILabel!
    @IBOutlet weak var label3: UILabel!
   
    // ボタン押下で遷移元に戻る
    @IBAction func returnButtonClick(sender: AnyObject) {
        _ = navigationController?.popViewControllerAnimated(true)
    } 
    // ラベルに表示するコード
    func dispLabel(){
        if let dispData = data{
            label1.text = dispData.company
            label2.text = dispData.model1
            label3.text = dispData.model2
        }
    }
}





0 件のコメント:

コメントを投稿