🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Delegates

Delegatesとは、オブジェクト指向型プログラミングにおいて、あるオブジェクトの操作を一部の他のオブジェクトに代替させる手法のこと。オブジェクトは他のデリゲートに頼って関数を実行することができます。

Swift

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

Q&A

1回答

1083閲覧

delegate先のクラスで定義したプロパティの値がnilになってしまう

sta_sato

総合スコア2

Delegates

Delegatesとは、オブジェクト指向型プログラミングにおいて、あるオブジェクトの操作を一部の他のオブジェクトに代替させる手法のこと。オブジェクトは他のデリゲートに頼って関数を実行することができます。

Swift

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

0グッド

0クリップ

投稿2021/02/18 13:02

編集2021/02/18 15:07

前提・実現したいこと

delegateを使って下記の処理を実現したいです。

「Top画面」という画面の上に、containerViewを配置し、
containerViewの上にViewを配置しています。

Top画面に対して、クラス:ViewControllerを割り当てています。
イメージ説明

Top画面の配下のViewの、さらに配下のCotainerAに、
下記の「冷蔵庫画面」を配置していて、クラス:cabinViewControllerを設定しています。
イメージ説明

※delegateについての私の理解(間違いありましたらご指摘ください)
delegateとは、自分のクラスの処理内容を他のクラスに実行してもらう(移譲する)こと。
下記の実装が必要。
①protcolの定義
②protcolに準拠した、処理内容を記載するクラス(今回やりたいことだとviewControllerB)
③処理の移譲先を指定するクラス(今回やりたいことだとviewControllerA)

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

下記のとおりコードを記載しているのですが、myTableView.reloaDate()の処理で、
myaTableViewの値がnilになってしまうエラーが発生してしまい、解決方法がわかりません。

該当のソースコード

①protocol

swift

1protocol ReloadDataDelegate { 2 func reloadTabeleDate() 3} 4

②CabinviewController(冷蔵庫画面)

swift

1class CabinViewController:UIViewController, UITableViewDelegate, UITableViewDataSource, UINavigationControllerDelegate,ReloadDataDelegate { 2 3 @IBOutlet weak var myTableView: UITableView! 4 5 override func viewDidLoad() { 6 super.viewDidLoad() 7 myTableView.delegate = self 8 myTableView.dataSource = self 9 } 10 11 //MARK:- デリゲート 12 func reloadTabeleDate(){ 13 print("test") 14 myTableView.reloadData() 15 } 16}

③ViewController(TOP画面)

swift

1class ViewController: UIViewController, UINavigationControllerDelegate { 2 3 var delegate:ReloadDataDelegate! 4 5 // MARK: - ContainerView 6 var containers:Array<UIView>=[] 7 @IBOutlet weak var containerA: UIView! 8 @IBOutlet weak var containerB: UIView! 9 @IBOutlet weak var containerC: UIView! 10 11 12 override func viewDidLoad() { 13 super.viewDidLoad() 14 15 let cabinVC = CabinViewController() 16 self.delegate = cabinVC 17 18 containers=[containerA,containerB,containerC] 19 containerView.bringSubviewToFront(containers[containerIndex]) 20} 21 22~(中略)~ 23 //MARK:- segmentBarの値に応じて、containerViewの表示を切り替え 24 @IBAction func SegmentAction(_ sender: UISegmentedControl) { 25 26 let currentContainerView=containers[sender.selectedSegmentIndex] 27 containerView.bringSubviewToFront(currentContainerView) 28 } 29 30~(中略)~ 31 //NavigationBarのボタン 32 @IBAction func templateButton(_ sender: Any) { 33 reload() 34} 35 36 func reload() { 37 self.delegate?.reloadTabeleDate() 38 } 39} 40

試したこと

実行すると、reloadTabeleDate()内の、"test"の出力がされることは確認しています。

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

Swift 5.X

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

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

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

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

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

hoshi-takanori

2021/02/18 13:26 編集

画面 A と B はどうやって遷移しますか? または同時に表示されますか? A の viewDidLoad で B のインスタンスを生成してますが、これは実際に表示されるやつですか? また、delegate プロパティの宣言が A ではなく B にあるのは何故でしょうか。
TsukubaDepot

2021/02/18 13:24

ご提示いただいているコードですが、コンパイルできない間違いが多く残っているようです。 たとえば、ViewControllerA で self.delegate = viewControllerB というコードを書かれていますが、self(つまり、ViewControllerA)には delegate というプロパティがありません。 中略されて提示されるのは致し方がないと思いますが、間違いがあると何が根本的な理由なのか指摘できないため、動かしているコードをそのままコピーし、不要なところを削除してもらえませんでしょうか。
TsukubaDepot

2021/02/18 13:31

あと、ViewControllerA が親で、ViewControllerB のインスタンスを持っているのであれば、delegate モデルを使う必要はないといもいます。 ViewControllerAは ViewControllerB のインスタンスを知っているので、当然 ViewControllerB が持つメソッドやプロパティは直接見えます。 また、delegate としての作り方も間違っているようです(protocol をクラスのみに準拠させる、!ではなく?で参照させるなど)。
sta_sato

2021/02/18 13:33

@hoshi-takanoriさん >>画面 A と B はどうやって遷移しますか? または同時に表示されますか? 同時に表示されます。A画面の上にB画面を載っけています。 >>A の viewDidLoad で B のインスタンスを生成してますが、これは実際に表示されるやつですか? すみません、「実際に表示される」とはどういった意味になりますでしょうか? >>また、delegate プロパティの宣言が A ではなく B にあるのは何故でしょうか。 申し訳ありませんが、記載誤りのため、質問を修正いたします。 @TsukubaDepotさん 申し訳ありません、コードを追記します。また、誤って記載している箇所もあるので、追記します (コンパイルエラーにはなっていません。)
TsukubaDepot

2021/02/18 14:06

細かいところを突くようで申し訳ないのですが、正確に説明していただかないことにはこちらも理解できませんので、正確な記述となるようにご質問を変更していただけますでしょうか。 >(追記) >A画面の上に、Viewがあり、このViewの下に、ContentViewBがあります。 A画面とは、具体的にどの画面のことでしょうか。 「Top画面 scene」のことでしょうか。 また、この「Top画面 scene」に関連づけられているViewControllerのクラス名は何になりますでしょうか。 加えて、「ContentViewB」というのがありますが、これは「ContainerB」のことでしょうか。 > ContentViewBに対して、ViewControllerBを設定しています。 ViewControllerB というクラスは実際には「CabinViewController」なのでしょうか。 そうすると、ViewControllerA でインスタンスにしている次のコード > let viewControllerB = ViewControllerB() > self.delegate = viewControllerB の ViewControllerB() は、実際には CabinViewController() のことなのでしょうか。 このあたりを書き換えてしまうと、もはやご質問の意図を掴むことができないので、正確に記述していただけますでしょうか。 > (名前がわかりづらくなってしまっていますが、ContainerA~Cのうち、 > ContainerAに設定しているクラスをviewControllerBとして質問を書いています) ViewControllerB(実際はCabinViewController() )は、実際には Container View に入っているということでしょうか。
sta_sato

2021/02/18 14:50

@TsukubaDepot 申し訳ありません、省略化して表現しようと結果、正しくないかつ混乱させてしまう記載になってしまいました。そのままのコードで一度質問内容を更新いたします。
guest

回答1

0

コメントを受けて修正します。

~~とりあえず、ご質問の内容から確実に言えることだけご指摘します。
~~
結論としては、はやりインスタンスの関係を正しく把握しない状態でコーディングされていることが原因です。

Swift

1 let let cabinVC = CabinViewController()

という行があり、たしかに CabinViewController はインスタンス化されます。

しかし、StoryBoard 状に配置した Container View にはCabinViewController が紐づけられていいるはずで(embeded segueとして関連づけられているはず)で、ここで改めてインスタンス化した CabinViewController とは全く無関係になっています。

コード内でインスタンス化された CabinViewController は単にインスタンスが出来るるだけであり、viewDidLoad()などのライフサイクルに関するメソッドや、

Swift

1 @IBOutlet weak var myTableView: UITableView!

など、StoryBoard と関連づける UI部品のインスタンス化も行われません。

したがって、直接インスタンス化しただけでは、UITableViewは作成されず、myTableView の値は初期値の nil のままとなってしまいます。

したがって、

Swift

1 func reloadTabeleDate(){ 2 print("test") 3 myTableView.reloadData() 4 }

この行にあるmyTableView.reloadData()のメソッドチェインに失敗し、実行時エラーとなっています。

以上が、今回のエラーの原因です。

###対策

さて、追記していただいたコードや StoryBoard 上の階層から想像すると、ContainverView を使い、そこで TableView を表示を(つまり、CabinViewController の表示を)されています。

そうすると、必要な作業は、Container View に入れられた CabinViewController のインスタンス(より具体的に言うと、メモリ上に展開されたアドレス)を調べ、そこから直接 CabinViewController のメソッドを操作することになると思われます。

Container View に追加された (一般的に言うところの)View Controllerのインスタンスは、children という [UIViewController]の配列に入ますので、そこから CabinViewController のインスタンスを探し出す必要があります。

全てコードベースで Container View に追加するのであれば、それは自明なのですが、StoryBoard で追加したのであれば、自分で探す必要があります(ちなみに、Container View というのは、実際には UIView に過ぎず、StoryBoard で紐づければ一連の厄介な手続きが自動化されるだけの話です。逆にいうと、自動化されるために手間がかかる作業も増えるということです)。

Container View が一つだけであれば、配列のインデックスを決め打ちで調べることも可能ですが、安全性や、あるいは今回みたいに複数の Container View を使うのであれば、なんらかの方法で調べる必要があります。

たとえば、viewDidLoad()

Swift

1 if let index = self.children.firstIndex(where: { vc -> Bool in 2 return vc is CabinViewController 3 }) { 4 print("Cabin:", index) 5 }

みたいな方法を使い、children に入っているインスタンスとのパターンマッチを行うことでインデックスを調べる必要があります。

正確に言うと、is 演算子は CabinViewController クラスを元にして作られたインスタンスとのマッチングであり、仮に children の内部に複数のCabinViewControllerから作られたインスタンスが存在するのであれば、それはこの方法で調べることはできませんし、全体構成を根本的に見直す必要があると思います

それはさておき、上記の方法で index がわかれば、

Swift

1 // クラスのプロパティとして宣言 2 var cabinVC: CabinViewController? 3 4 // viewDidLoad() 内部などで 5 if let index = self.children.firstIndex(where: { vc -> Bool in 6 return vc is CabinViewController 7 }) { 8   cabinVC = self.chidren[index] as! CabinViewController 9 }

のような方法で、vcCabinViewController のインスタンス(具体的なメモリ上のアドレス)を得ることができますので、あとは

Swift

1 cabinVC?.reloadTabeleDate()

のような感じで、直接 CabinViewController のメソッドを操作することになります。

delegate の適用

今回のようなパターンであれば、無理に delegate パターンに持ち込む必要はないと思いますし、上記のようになんらかの方法で対象となるインスタンスを探すのであれば、delegate としてインスタンスを保持する理由もあまりないとおもます(結果として、行っていることは同じなので)。

もちろん、CabinViewController を汎用的なクラスとして使いたいということであれば、delegate パターンを適用することになりますが、その場合には CabinViewController のインスタンス化の流れから根本的に見直す必要があると思います。

たとえば、StoryBoard で Container View を使うのではなく、UIView を配置し、instantinateViewController で従属させたいView Controllerを正しく初期化させ、addChild で child として追加し、その view を UIView のサブビューとして登録し、必要に応じて画面全面に持ってくるという流れを全て手動で行えば、また話は違ってくると思います(それでも、delegate パターンを使う必然性はあまりないと思います)。

全体の構成

もう一つ考えていただきたいのは、全体の構成です。

ViewController 内部に多くの Container View を取り込み、複数の View Controller を扱っている時点で、もはや質問者さんご自身が管理不能な状態に陥っているように思えます。

全体像が見えないのでなんとも言えないのですが、一つの画面に対してこれほどたくさんの View Controller を適用する必要があるのか、もう一度設計をよく見直された方がいいかもしれません。

ざっとみた感じであれば、一つの View Controller 内部に Table View を配置し、適切に制約(constraint)などを設定すれば、それで住むだけの話のようにも思えます。

ただ、これは期待される構成としてどのような構成を考えていらっしゃるのかよく聞かない限り判断できませんが、ただこの内容のご質問だと、聞いている方も把握するのは難しいのではないかと感じています。

今後の方針として

それでもやはり Container View を使って作りたい、ということであれば、

ひとつの View Controller に一つの Container View だけを持つサンプルを作り、そこから Container View 内部に配置した View Controller のインスタンスを特定し、操作する

という、ごく単純で明快なサンプルを作り、そこで納得のゆく結果を得てから拡張されるのが良いかと思います。

別件のご質問も拝見しましたが、そこで指摘されているように View と View Controller の違い、特に役割の違いについて誤解を受けているように私も感じました。

もしかしたら、なんらかのサンプルを元にオリジナルのアプリを作ろうとされているのかもしれません。

でも、結局はごく最小限の構成を理解しないことにはそれより大きい構成を作ることは難しい話なので、まずはごく簡単なサンプル作りから始め、着実に積み重ねられた方が良いのではないでしょうか。

投稿2021/02/18 13:37

編集2021/02/19 05:11
TsukubaDepot

総合スコア5086

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問