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

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

新規登録して質問してみよう
ただいま回答率
85.48%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

1056閲覧

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

unagimochimochi

総合スコア7

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2020/07/15 00:10

編集2020/07/15 03:35

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

前提・実現したいこと

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

#####HomeViewController.swift
ホーム画面

Swift

1import UIKit 2 3class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 4 5 @IBOutlet weak var PlanTable: UITableView! 6 var DateAndTimes = [String]() 7 8 /* 9 10 省略 11 DateAndTimesに文字列をセットする処理 12 13 */ 14 15 override func viewDidLoad() { 16 super.viewDidLoad() 17 18 /* 19 20 省略 21 22 */ 23 } 24 25 override func didReceiveMemoryWarning() { 26 super.didReceiveMemoryWarning() 27 } 28 29 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 30 31 let cell = tableView.dequeueReusableCell(withIdentifier: "PlanCell", for: indexPath) 32 33 let DateAndTimeLabel = cell.viewWithTag(1) as! UILabel 34 DateAndTimeLabel.text = self.DateAndTimes[indexPath.row] 35 36 return cell 37 } 38 39 /* 40 41 省略 42 TableViewのその他の処理 43 44 */ 45 46 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 47 48 guard let identifier = segue.identifier else { 49 return 50 } 51 if identifier == "toPlanDetails" { 52 let PlanDetailsVC = segue.destination as! PlanDetailsViewController 53 PlanDetailsVC.DateAndTime = self.DateAndTimes[(self.PlanTable.indexPathForSelectedRow?.row)!] 54 } 55 56}

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

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
予定詳細画面

Swift

1import UIKit 2 3class PlanDetailsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 4 5 let planItem = ["参加者","場所","共有開始","通知"] 6 7 var PlanTitle: String? 8 var DateAndTime: String? 9 10 @IBOutlet weak var DateAndTimeLabel: UILabel! 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 if let DateAndTime = self.DateAndTime { 16 DateAndTimeLabel.text = DateAndTime 17 } 18 19 if let PlanTitle = self.PlanTitle { 20 self.navigationItem.title = PlanTitle 21 } 22 } 23 24 /* 25 26 省略 27 28 */ 29 30 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 31 32 guard let identifier = segue.identifier else { 33 return 34 } 35 36 if identifier == "editPlan" { 37 let addPlanVC = segue.destination as! AddPlanViewController 38 addPlanVC.PlanTitle = self.PlanTitle 39 40 let indexPath = IndexPath(row: 0, section: 0) 41 if let cell = addPlanVC.addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell { // ここにエラー 42 cell.DateAndTime = self.DateAndTime 43 } else { 44 print("nil") 45 } 46 } 47 } 48 49}

#####DateAndTimeCell.swift
編集画面(AddPlanViewController)の日時セル

Swift

1import UIKit 2 3class DateAndTimeCell: UITableViewCell { 4 5 @IBOutlet weak var displayDateAndTimeTextField: UITextField! 6 7 var datePicker: UIDatePicker = UIDatePicker() 8 var DateAndTime: String? 9 10 override func awakeFromNib() { 11 super.awakeFromNib() 12 13 if let DateAndTime = self.DateAndTime { 14 print("(DateAndTime) - DateAndTimeCell.swift") 15 self.displayDateAndTimeTextField.text = DateAndTime 16 } 17 18 /* 19 20 省略 21 22 */ 23 } 24 25 override func setSelected(_ selected: Bool, animated: Bool) { 26 super.setSelected(selected, animated: animated) 27 } 28} 29

試したこと

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

#####PlanDetailsViewController.Swift
予定詳細画面 prepare部分

Swift

1override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 2 3 guard let identifier = segue.identifier else { 4 return 5 } 6 7 if identifier == "editPlan" { 8 let AddPlanVC = segue.destination as! AddPlanViewController 9 AddPlanVC.PlanTitle = self.PlanTitle 10 AddPlanVC.DateAndTime = self.DateAndTime 11 } 12}

#####AddPlanViewController.swift
編集画面

Swift

1import UIKit 2 3class AddPlanViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 4 5 var PlanTitle: String? 6 var DateAndTime: String? 7 8 @IBOutlet weak var addPlanTable: UITableView! 9 @IBOutlet weak var PlanTitleTextField: UITextField! 10 11 // キャンセルボタン 12 @IBAction func cancelButton(_ sender: Any) { 13 self.dismiss(animated: true, completion: nil) 14 } 15 16 // 保存ボタン 17 @IBOutlet weak var saveButton: UIBarButtonItem! 18 19 var planItem = ["日時","参加者","場所","共有開始","通知"] 20 21 override func viewDidLoad() { 22 super.viewDidLoad() 23 24 if let PlanTitle = self.PlanTitle { 25 self.PlanTitleTextField.text = PlanTitle 26 } 27 28 if let DateAndTime = self.DateAndTime { 29 print("(DateAndTime) - 1段階目") 30 31 let indexPath = IndexPath(row: 0, section: 0) 32 if let cell = addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell { 33 print("(DateAndTime) - 2段階目") 34 cell.displayDateAndTimeTextField.text = DateAndTime 35 } else { 36 return 37 } 38 } 39 } 40 41 func tableView(_ tableView: UITableView, cellForRowAt indexPath:IndexPath) -> UITableViewCell { 42 var lineupCell: UITableViewCell 43 if indexPath.row == 0 { 44 lineupCell = tableView.dequeueReusableCell(withIdentifier: "DateAndTimeCell", for:indexPath) as UITableViewCell 45 } else { 46 lineupCell = tableView.dequeueReusableCell(withIdentifier: "ParticipantCell", for:indexPath) as UITableViewCell 47 } 48 lineupCell.textLabel?.text = planItem[indexPath.row] 49 return lineupCell 50 } 51 52 /* 53 54 省略 55 56 */ 57}

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

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

Xcode 11.5
Swift 4.2

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

TsukubaDepot

2020/07/15 01:11

全体的な動きが追えないのですが、 「Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value」 という実行時エラーが起きるのは、ソースコードのどの部分が特定、あるいは推測することは可能でしょうか。 もしくは、「ここでこのような操作をすると落ちる」という情報でもあれば違います。 また、ソースコードはそれなりに提示してあるのですが、冒頭のスクリーンショットとの対応付けが分かりにくい感じがします。 スクリーンショットの先頭あたりにでも、対応するクラス名(カスタムのviewControllerの名前)を差し込んでいただければと思います。
guest

回答1

0

ベストアンサー

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

Swift

1if identifier == "editPlan" { 2 let addPlanVC = segue.destination as! AddPlanViewController 3 addPlanVC.PlanTitle = self.PlanTitle 4 5 let indexPath = IndexPath(row: 0, section: 0) 6 if let cell = addPlanVC.addPlanTable.cellForRow(at: indexPath) as? DateAndTimeCell { // ここにエラー 7 cell.DateAndTime = self.DateAndTime 8 } else { 9 print("nil") 10 } 11 }

遷移先では、おそらく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:オプショナルバインディングについて

エラーが出た行ですが

Swift

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

と記述されています。

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

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

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

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

Swift

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

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

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

Swift

1@IBOutlet weak var addPlanTable: UITableView!

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

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

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

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

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

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

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

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

Swift

1import UIKit 2 3class ViewController: UIViewController { 4 5 override func viewDidLoad() { 6 super.viewDidLoad() 7 // Do any additional setup after loading the view. 8 } 9 10 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 11 let vc = segue.destination as! TableViewController 12 vc.item = "渡したい情報" 13 } 14 15} 16 17class TableViewController: UIViewController, UITableViewDataSource { 18 19 @IBOutlet weak var tableView: UITableView! 20 // MARK: 受け取りたい情報 21 var item: String! 22 23 // MARK: ここでは表示させない。 24 override func viewDidLoad() { 25 super.viewDidLoad() 26 // Do any additional setup after loading the view. 27 tableView.dataSource = self 28 29 // ここでは tableView を直接操作しない 30 } 31 32 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 33 return 5 34 } 35 36 // MARK: セルを表示する段階で受け取った情報を表示させる 37 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 38 var cell: UITableViewCell! 39 40 if indexPath.row == 0 { 41 cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) 42 // 受け取った情報はここで表示させる 43 cell.textLabel?.text = item 44 } else { 45 cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath) 46 cell.textLabel?.text = "それ以外のセル" 47 } 48 49 return cell 50 } 51}

#追記4

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

Swift

1import UIKit 2 3class ViewController: UIViewController { 4 5 override func viewDidLoad() { 6 super.viewDidLoad() 7 // Do any additional setup after loading the view. 8 } 9 10 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 11 let vc = segue.destination as! TableViewController 12 vc.item = "渡したい情報" 13 } 14 15} 16 17class TableViewController: UIViewController, UITableViewDataSource { 18 19 @IBOutlet weak var tableView: UITableView! 20 // MARK: 受け取りたい情報 21 var item: String! 22 23 // MARK: ここでは表示させない。 24 override func viewDidLoad() { 25 super.viewDidLoad() 26 // Do any additional setup after loading the view. 27 tableView.dataSource = self 28 29 // ここでは tableView を直接操作しない 30 } 31 32 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 33 return 5 34 } 35 36 // MARK: セルを表示する段階で受け取った情報を表示させる 37 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 38 //var cell: UITableViewCell! 39 40 if indexPath.row == 0 { 41 let cell = tableView.dequeueReusableCell(withIdentifier: "Cell1", for: indexPath) as! CustomTableViewCell 42 // 受け取った情報はここで表示させる 43 cell.textFieldInCustomCell.text = item 44 45 return cell 46 } else { 47 let cell = tableView.dequeueReusableCell(withIdentifier: "Cell2", for: indexPath) 48 cell.textLabel?.text = "それ以外のセル" 49 50 return cell 51 } 52 } 53} 54 55// MARK: Cell1 だけカスタムクラスで設定 56// Cell1 に対応するカスタムクラスを CustomTableViewCell に設定 57class CustomTableViewCell: UITableViewCell { 58 // カスタムセル内のアウトレット 59 @IBOutlet weak var textFieldInCustomCell: UITextField! 60 61 override func awakeFromNib() { 62 super.awakeFromNib() 63 // Initialization code 64 } 65 66 override func setSelected(_ selected: Bool, animated: Bool) { 67 super.setSelected(selected, animated: animated) 68 69 // Configure the view for the selected state 70 } 71}

投稿2020/07/15 04:01

編集2020/07/15 11:13
TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

unagimochimochi

2020/07/15 10: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内に渡したデータが表示されることはありませんでした。
TsukubaDepot

2020/07/15 10: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も含まれているはずです。
TsukubaDepot

2020/07/15 11:13

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

2020/07/15 11:48

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問