質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.92%

セル内の TextField にあらかじめ遷移元の Label の文字列を表示したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 166

Swift初心者です。
質問の仕方に至らぬ点があるかもしれませんが、どうぞよろしくお願いいたします。

前提・実現したいこと

予定詳細画面(↓PlanDetailsViewController)のLabelに表示されている日時(2020年7月15日 水曜日 15:00)を、
予定詳細画面
右上の鉛筆アイコンをクリックして編集画面(↓AddPlanViewController)に遷移した際、
日時セル(DateAndTimeCell)のTextFieldに最初から表示したいです。
編集画面
ちなみに、予定詳細画面のLabelに表示されている日時は、
ホーム画面(↓HomeViewController)のTableViewのLabelから以下のコードで渡しています。
ホーム画面

HomeViewController.swift

ホーム画面

import UIKit

class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var PlanTable: UITableView!
    var DateAndTimes = [String]()

    /*

    省略
    DateAndTimesに文字列をセットする処理

    */

    override func viewDidLoad() {
        super.viewDidLoad()

        /*

        省略

        */
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "PlanCell", for: indexPath)

        let DateAndTimeLabel = cell.viewWithTag(1) as! UILabel
        DateAndTimeLabel.text = self.DateAndTimes[indexPath.row]

        return cell
    }

    /*

    省略
    TableViewのその他の処理

    */

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        guard let identifier = segue.identifier else {
            return
        }
        if identifier == "toPlanDetails" {
            let PlanDetailsVC = segue.destination as! PlanDetailsViewController
            PlanDetailsVC.DateAndTime = self.DateAndTimes[(self.PlanTable.indexPathForSelectedRow?.row)!]
    }

}

発生している問題・エラーメッセージ

Buildできますが、
予定詳細画面の右上の鉛筆アイコンをクリックして編集画面に遷移しようとすると
以下のエラーメッセージが出て先に進めません。

Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

エラーの位置は予定詳細画面(PlanDetailsController.swift)のprepare内、
オプショナルバインディングの行です。

if identifier == "editPlan" {
            let addPlanVC = segue.destination as! AddPlanViewController
            addPlanVC.PlanTitle = self.PlanTitle

            let indexPath = IndexPath(row: 0, section: 0)            
            if let cell = addPlanVC.addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell {     // ここにエラー
                cell.DateAndTime = self.DateAndTime
            } else {
                print("nil")
            } 
        }

書籍やドットインストールを参考に記述していますが、
セルを別ファイルに書いている例がなく、躓きました。

該当のソースコード

PlanDetailsViewController.swift

予定詳細画面

import UIKit

class PlanDetailsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    let planItem = ["参加者","場所","共有開始","通知"]

    var PlanTitle: String?
    var DateAndTime: String?

    @IBOutlet weak var DateAndTimeLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        if let DateAndTime = self.DateAndTime {
            DateAndTimeLabel.text = DateAndTime
        }

        if let PlanTitle = self.PlanTitle {
            self.navigationItem.title = PlanTitle
        }
    }

    /*

    省略

    */

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        guard let identifier = segue.identifier else {
            return
        }

        if identifier == "editPlan" {
            let addPlanVC = segue.destination as! AddPlanViewController
            addPlanVC.PlanTitle = self.PlanTitle

            let indexPath = IndexPath(row: 0, section: 0)            
            if let cell = addPlanVC.addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell {     // ここにエラー
                cell.DateAndTime = self.DateAndTime
            } else {
                print("nil")
            } 
        }
    }

}
DateAndTimeCell.swift

編集画面(AddPlanViewController)の日時セル

import UIKit

class DateAndTimeCell: UITableViewCell {

    @IBOutlet weak var displayDateAndTimeTextField: UITextField!

    var datePicker: UIDatePicker = UIDatePicker()
    var DateAndTime: String?

    override func awakeFromNib() {
        super.awakeFromNib()

        if let DateAndTime = self.DateAndTime {
            print("\(DateAndTime) - DateAndTimeCell.swift")
            self.displayDateAndTimeTextField.text = DateAndTime
        }

        /*

        省略

        */
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
}

試したこと

事をややこしくするかもしれませんが……
AddPlanViewController.swiftのほうで処理を書けないか試しました。
エラーは出ませんが、TextFieldに最初から日時が出力されることはありませんでした。

PlanDetailsViewController.Swift

予定詳細画面 prepare部分

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    guard let identifier = segue.identifier else {
        return
    }

    if identifier == "editPlan" {
        let AddPlanVC = segue.destination as! AddPlanViewController
        AddPlanVC.PlanTitle = self.PlanTitle
        AddPlanVC.DateAndTime = self.DateAndTime
    } 
}
AddPlanViewController.swift

編集画面

import UIKit

class AddPlanViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    var PlanTitle: String?
    var DateAndTime: String?

    @IBOutlet weak var addPlanTable: UITableView!
    @IBOutlet weak var PlanTitleTextField: UITextField!

    // キャンセルボタン
    @IBAction func cancelButton(_ sender: Any) {
        self.dismiss(animated: true, completion: nil)
    }

    // 保存ボタン
    @IBOutlet weak var saveButton: UIBarButtonItem!

    var planItem = ["日時","参加者","場所","共有開始","通知"]

    override func viewDidLoad() {
        super.viewDidLoad()

        if let PlanTitle = self.PlanTitle {
            self.PlanTitleTextField.text = PlanTitle
        }

        if let DateAndTime = self.DateAndTime {
            print("\(DateAndTime) - 1段階目")

            let indexPath = IndexPath(row: 0, section: 0)
            if let cell = addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell {
                print("\(DateAndTime) - 2段階目")
                cell.displayDateAndTimeTextField.text = DateAndTime
            } else {
                return
            }
        }
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell {
        var lineupCell: UITableViewCell
        if indexPath.row == 0 {
            lineupCell = tableView.dequeueReusableCell(withIdentifier: "DateAndTimeCell", for:indexPath) as UITableViewCell
        } else {
            lineupCell = tableView.dequeueReusableCell(withIdentifier: "ParticipantCell", for:indexPath) as UITableViewCell
        }
        lineupCell.textLabel?.text = planItem[indexPath.row]
        return lineupCell
    }

    /*

    省略

    */
}


("\(DateAndTime) - 1段階目") はprintされますが
("\(DateAndTime) - 2段階目") はprintされませんでした。

補足情報(FW/ツールのバージョンなど)

Xcode 11.5
Swift 4.2

ここまで読んでくださり、ありがとうございます。
コードをかなり省略してしまったので、全体像がわかるほうが回答しやすいのであればお申し付けください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • TsukubaDepot

    2020/07/15 10:11

    全体的な動きが追えないのですが、
    「Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value」
    という実行時エラーが起きるのは、ソースコードのどの部分が特定、あるいは推測することは可能でしょうか。
    もしくは、「ここでこのような操作をすると落ちる」という情報でもあれば違います。

    また、ソースコードはそれなりに提示してあるのですが、冒頭のスクリーンショットとの対応付けが分かりにくい感じがします。
    スクリーンショットの先頭あたりにでも、対応するクラス名(カスタムのviewControllerの名前)を差し込んでいただければと思います。

    キャンセル

回答 1

checkベストアンサー

+1

ソースコードとエラーが発生した場所だけで判断しますが、遷移先にあるまだ表示されていないセルに対してアクセスしようとしているため問題が発生しているように思えます。

if identifier == "editPlan" {
            let addPlanVC = segue.destination as! AddPlanViewController
            addPlanVC.PlanTitle = self.PlanTitle

            let indexPath = IndexPath(row: 0, section: 0)            
            if let cell = addPlanVC.addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell {     // ここにエラー
                cell.DateAndTime = self.DateAndTime
            } else {
                print("nil")
            } 
        }

遷移先では、おそらくaddPlanTableという名前で UITableView のインスタンス(変数)を作っているとおもますが、その変数は通常オプショナル型で宣言されています。

遷移先のaddPlanTableに具体的な値、つまり UITableView のインスタンスが代入されるのは、遷移先の画面 (view)のviewDidLoad()が実行された段階です。

遷移元でperformSegue()を実行すると、遷移先のインスタンスが作られる(画面を表示するための準備をする)のですが、この時点ではまだUI部品の作成までは行われていません。

なので、遷移先のプロパティ(変数)であり、UITableView のインスタンスである addPlanTableにアクセスしようとした時点で実行時エラーとなってしまうのだと推測されます。

いわゆる「値渡し」と呼ばれる、遷移先の画面(インスタンス)を生成し、そのクラスのプロパティ(値)を代入しようとしている操作そのものは誤っているわけではありませんが、UI部品のプロパティに対してはこの時点では直接代入できません。

なので、一旦何らかのプロパティを準備しておき、一旦それに代入し、遷移先で viewDidLoad()が実行される段階で UI 部品への値の代入を行う、とう手順を踏まなければなりません。

かなりややこしい話ではありますが、原因と解決方法としてはこのような感じとなります。もしよくわからない点があればコメントいただければと思います。
(追伸:末尾に簡単なサンプルを挙げておきました)

追伸:遷移先の UITableViewCell への値代入について

しかし、上記の手順を守ったとしても、UITableViewCell への値代入は行えない可能性があります。
理由は簡単で、たとえ UITableView が生成されたとしても、その時点では Cell そのものが生成されていたいためです。

UITableView のセルが生成されるのは、既にご存知の通り

    func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell {
        var lineupCell: UITableViewCell
        if indexPath.row == 0 {
            lineupCell = tableView.dequeueReusableCell(withIdentifier: "DateAndTimeCell", for:indexPath) as UITableViewCell
        } else {
            lineupCell = tableView.dequeueReusableCell(withIdentifier: "ParticipantCell", for:indexPath) as UITableViewCell
        }
        lineupCell.textLabel?.text = planItem[indexPath.row]
        return lineupCell
    }

といった処理(つまり、tableView(_:cellForRowAt:))が呼び出された瞬間となります。

なので、単に値渡しの方法を変更するだけではなく、セルに代入するためのロジックとしては一度考え直す必要があるかとおもます。

追伸2:オプショナルバインディングについて

エラーが出た行ですが

if let cell = addPlanVC.addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell { 

と記述されています。

たしかに、if-let文を使っていますので、nilの場合には処理されないという流れになっていることに間違いはありません。

ここでnilになり得る一つの原因はcellForRow(at:)で、たしかにこのメソッドはat:で指定されたindexPathに相当するセルがない、あるいは表示されていなければnilを返します。

しかし、これだけだとオプショナルバインディングのif文で弾かれるだけなので、実行時エラーにはならないと思います。

では、どこで実行時エラーが出ているかと言うと

if let cell = addPlanVC.addPlanTable.cellForRow(at: indexPath)

でいうところの addPlanTable が原因と推測されます。

このプロパティは、遷移先のクラスでは

@IBOutlet weak var addPlanTable: UITableView!

という形で宣言されていると思います。

ここで注意していただきたいのは、UITableView! と、最後が!で終わるオプショナル型で宣言されている点です。

!で宣言されたオプショナル型は、その変数が使われる前に必ず何らかの値を代入する必要があります。もし、代入しないまま!で宣言されたオプショナル型に値を代入しようとすると実行時エラーが発生する仕組みとなっています。

基本的に、オプショナル型で宣言された変数にアクセスする場合には、変数の末尾に?をつけることによって「この変数はオプショナル型である」と意識できるようになっています。

しかし、必ず値を代入することを「プログラマが」責任をもつオプショナル型については、特例で?を付けなくてもアクセスできるようになっています。

なので、使い方に気をつけない限り、今回のようにオプショナル型へのアクセスと気づかないままアクセスし、思わぬ実行時エラーが発生すると理解していただければと思います。

追記3:ごく簡単な検証用サンプル

検証用のサンプルです。
遷移先の viewDidLoad() でややこしいことをしなくても、tableView(_:cellForRowAt:)でセルを作る際にデータを表示させれば良いかと思います。

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let vc = segue.destination as! TableViewController
        vc.item = "渡したい情報"
    }

}

class TableViewController: UIViewController, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    // MARK: 受け取りたい情報
    var item: String!

    // MARK: ここでは表示させない。
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        tableView.dataSource = self

        // ここでは tableView を直接操作しない
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    // MARK: セルを表示する段階で受け取った情報を表示させる
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell: UITableViewCell!

        if indexPath.row == 0 {
            cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath)
            // 受け取った情報はここで表示させる
            cell.textLabel?.text = item
        } else {
            cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath)
            cell.textLabel?.text = "それ以外のセル"
        }

        return cell
    }
}

追記4

カスタムセル内にアウトレットがあっても難しく考える必要はありません。

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let vc = segue.destination as! TableViewController
        vc.item = "渡したい情報"
    }

}

class TableViewController: UIViewController, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!
    // MARK: 受け取りたい情報
    var item: String!

    // MARK: ここでは表示させない。
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        tableView.dataSource = self

        // ここでは tableView を直接操作しない
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }

    // MARK: セルを表示する段階で受け取った情報を表示させる
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //var cell: UITableViewCell!

        if indexPath.row == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as! CustomTableViewCell
            // 受け取った情報はここで表示させる
            cell.textFieldInCustomCell.text = item

            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath)
            cell.textLabel?.text = "それ以外のセル"

            return cell
        }
    }
}

// MARK: Cell1 だけカスタムクラスで設定
// Cell1 に対応するカスタムクラスを CustomTableViewCell に設定
class CustomTableViewCell: UITableViewCell {
    // カスタムセル内のアウトレット
    @IBOutlet weak var textFieldInCustomCell: UITextField!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/15 19:47

    とても丁寧なご回答、本当にありがとうございます。
    遷移元のSwiftファイルでTableViewやCellを指定してはいけない理由を理解することができました。
    オプショナル型についてはまだ理解しきれない部分も多いので、
    書籍を読み返すなどして鍛錬します。

    また、検証用サンプルのおかげで、TableViewの項目に日時(2020年7月15日 水曜日 15:00)を表示させることができました。

    ただ、よろしければもう少しお付き合いください。
    表示させたいのはセル内にあるTextFieldで、こちらはDateAndTimeCell.swiftにOutlet接続しています。
    この場合、どのように書けばよろしいでしょうか。

    見よう見真似で、
    cell.textLabel?.text = item

    (tableView.cellForRow(at: indexPath) as? Cell1)?.TextFieldのOutlet接続名?.text = item
    にしてみたのですが、エラーは起こらずともTextField内に渡したデータが表示されることはありませんでした。

    キャンセル

  • 2020/07/15 19:53

    実装方法次第なので当てずっぽうになりますが、まず確実に間違っているといえそうなのは、cellForRow(at: indexPath)  を使っているという点かと思います。

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

    では、indexPath に対応したセルを作ればいいだけなので、その中で改めて cellForRowAt(at:)を使う必要がありません。

    そうではなくて、ただ単純に
    cell.extFieldのOutlet接続名.text = item
    とやれば良いのではないかと思いますが、いかがでしょうか。

    つまり、
    tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath)
    を実行した時点で、Identifier に対応したカスタムセルができていますから、ここで得られたインスタンス(つまり、セルそのものであり、DateAndTimeCell のインスタンス)にはTextFieldも含まれているはずです。

    キャンセル

  • 2020/07/15 20:13

    もうひとつサンプルを追加しました。ご参照ください。

    キャンセル

  • 2020/07/15 20:48

    うまくいきました! ありがとうございます。
    確かに二重にセルを指定した書き方になっていて不自然でしたよね。
    修正方法がわからず闇雲にコードを書いてしまっていました。本当に助かりました。

    ここまでお付き合いくださり、本当にありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る