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

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

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

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

Xcode

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

Swift

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

iPhone

iPhoneとは、アップル社が開発・販売しているスマートフォンです。 同社のデジタルオーディオプレーヤーiPodの機能、電話機能、インターネットやメールなどのWeb通信機能の3つをドッキングした機器です。

Q&A

1回答

1825閲覧

【iOS/Swift】@Publishedなプロパティがあるのですが、そのプロパティの変更通知がViewにされない

bbdd

総合スコア43

iOS

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

Xcode

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

Swift

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

iPhone

iPhoneとは、アップル社が開発・販売しているスマートフォンです。 同社のデジタルオーディオプレーヤーiPodの機能、電話機能、インターネットやメールなどのWeb通信機能の3つをドッキングした機器です。

0グッド

0クリップ

投稿2021/03/26 17:34

環境

Xcode: 12.4
UI: SwiftUI

質問内容

ViewからViewModelを@ObservedObjectとして参照し、そのViewModelにて@Publishedなプロパティがあるのですが、そのプロパティの変更通知がViewにされないです。

以下に詳細を記載します。

(関連クラスなど)

  • PresentAlert:

Viewです。

  • PresentAlertViewModel

PresentAlertへバインドするViewModelです。

  • AlertProvider

アラートダイアログの表示データ、表示可否情報をもつ管理するクラスです。

(状況の説明)
PresentAlertがPresentAlertViewModelをもち、
さらにPresentAlertViewModelがAlertProviderを保持しています。

swift

1 2import SwiftUI 3 4struct PresentAlertBase: View { 5 var body: some View { 6 7 VStack { 8 PresentAlert() 9 } 10 } 11} 12 13struct PresentAlert: View { 14 15 @ObservedObject var presentAlertVM = PresentAlertViewModel() 16 17 var body: some View { 18 19 VStack{ 20 Button("Plese Tap") { 21 presentAlertVM.showAlert() 22 } 23 .alert(isPresented: $presentAlertVM.alertProvider.shouldShowAlert) { 24 guard let alert = presentAlertVM.alertProvider.alert else { fatalError("????: Alert not available") } 25 return Alert(alert) 26 } 27 } 28 } 29} 30 31final class PresentAlertViewModel: ObservableObject { 32 @Published var alertProvider = AlertProvider() 33 34 func tapButton() { 35 showAlert() 36 } 37 38 func showAlert() { 39 alertProvider.alert = AlertProvider.Alert( 40 title: "demo", 41 message: "demo-message", 42 primaryButtomText: "OK", 43 primaryButtonAction: {}, 44 secondaryButtonText: "" 45 ) 46 } 47} 48 49final class AlertProvider: ObservableObject { 50 struct Alert { 51 var title: String 52 let message: String 53 let primaryButtomText: String 54 let primaryButtonAction: (() -> Void)? 55 let secondaryButtonText: String 56 } 57 58 /// アラートを表示するかどうかを示すフラグ。Viewが監視する。 59 @Published var shouldShowAlert = false 60 61 var alert: Alert? = nil { 62 didSet { 63 shouldShowAlert = alert != nil 64 } 65 } 66} 67

このとき、期待する動作は
「ボタンタップした際に、ViewModelのalertProviderへAlertが格納され、shouldShowAlertがtrueになりそれがViewに伝搬されてダイアログが表示される」
ということです。
が、表示されません。

shouldShowAlertがtrueになる、というところまでは確認できているのですが、その後なぜViewまで伝搬されていないのかがわかっていません。
また、例えばAertProviderをViewのプロパティとして保持した場合には問題なく動作することも確認できています。

ボタンをタップしてダイアログを表示する、ということ自体は実装方針を変更すれば実現は問題ないのですが、なぜこのようなことが生じているかが気になり質問させて頂きました。

質問対象の全コード

問題が発生する全体のコードです。ご参考頂けると幸いです。

swift

1 2import SwiftUI 3 4struct PresentAlertBase: View { 5 var body: some View { 6 7 VStack { 8 PresentAlert() 9 } 10 } 11} 12 13struct PresentAlert: View { 14 15 @ObservedObject var presentAlertVM = PresentAlertViewModel() 16 17 var body: some View { 18 19 VStack{ 20 Button("Plese Tap") { 21 presentAlertVM.showAlert() 22 } 23 .alert(isPresented: $presentAlertVM.alertProvider.shouldShowAlert) { 24 guard let alert = presentAlertVM.alertProvider.alert else { fatalError("????: Alert not available") } 25 return Alert(alert) 26 } 27 } 28 } 29} 30 31final class PresentAlertViewModel: ObservableObject { 32 @Published var alertProvider = AlertProvider() 33 34 func tapButton() { 35 showAlert() 36 } 37 38 func showAlert() { 39 alertProvider.alert = AlertProvider.Alert( 40 title: "demo", 41 message: "demo-message", 42 primaryButtomText: "OK", 43 primaryButtonAction: {}, 44 secondaryButtonText: "" 45 ) 46 } 47} 48 49final class AlertProvider: ObservableObject { 50 struct Alert { 51 var title: String 52 let message: String 53 let primaryButtomText: String 54 let primaryButtonAction: (() -> Void)? 55 let secondaryButtonText: String 56 } 57 58 /// アラートを表示するかどうかを示すフラグ。Viewが監視する。 59 @Published var shouldShowAlert = false 60 61 var alert: Alert? = nil { 62 didSet { 63 shouldShowAlert = alert != nil 64 } 65 } 66} 67 68extension Alert { 69 init(_ alert: AlertProvider.Alert) { 70 71 if !alert.primaryButtomText.isEmpty && !alert.secondaryButtonText.isEmpty { 72 self.init(title: Text(alert.title), 73 message: Text(alert.message), 74 primaryButton: .default(Text(alert.primaryButtomText), 75 action: alert.primaryButtonAction), 76 secondaryButton: .cancel(Text(alert.secondaryButtonText))) 77 } else { 78 79 self.init( 80 title: Text(alert.title), 81 message: Text(alert.message), 82 dismissButton: .default(Text(alert.primaryButtomText)) 83 ) 84 } 85 } 86} 87 88#if DEBUG 89struct PresentAlert_Previews: PreviewProvider { 90 static var previews: some View { 91 PresentAlertBase() 92 } 93} 94#endif

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

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

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

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

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

guest

回答1

0

Combine を使えば良いようです。
参考: ios - How to tell SwiftUI views to bind to nested ObservableObjects - Stack Overflow

swift

1import SwiftUI 2import Combine 3 4final class PresentAlertViewModel: ObservableObject { 5 @Published var alertProvider = AlertProvider() 6 7 var anyCancellable: AnyCancellable? = nil 8 9 init() { 10 anyCancellable = alertProvider.objectWillChange.sink { [weak self] (_) in 11 self?.objectWillChange.send() 12 } 13 } 14 15 // 以下略 16}

Combine の説明は、Combineを軽く触ってみて - Qiita が分かりやすいかな…。

投稿2021/03/26 19:09

hoshi-takanori

総合スコア7901

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

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

bbdd

2021/09/12 05:20 編集

ご返答ありがとうございます! Combineについては明記してませんでしたが、プロジェクトで利用しております。 そうなんですよね、ObservableObjectのネストでうまくいかないんですよ ね。。 明示的に呼び出すのは試してみます!ありがとうございます。 --- (追記) sendで明示的に呼び出すと発火する。 では、なぜネストした場合そのような対応が必要になるのかという本質問の回答はまだわからなかったので保留中です。(仕様上そういうものなのか、もしそうならどのような仕様なのか、それともバグなのか。)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

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

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

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問