SwiftUI: @Stateの変数の更新内容が見えないケースがある
SwiftUIで@State
の変数が複数あるとします(例えばa
とb
)。
- ボタンタップのハンドラなどでその複数の変数(
a
・b
)を同時に更新する。 -
.onChange(of: perform:)
でそれらの変数のうちの1つ(例えばa
)を監視する。 - その際、
perform:
のクロージャ内で監視対象でない変数(この例ではb
)を参照する。
どういう条件か分かりませんが、3のタイミングで参照したb
の値が、1を行う前の値のまま、というケースがあります。
違いました。
.onChange
ではなく.sheet(item: content:)
でした。
ただ、どっちにしても小さいテストコードを書いても再現しません。
色々試した結果、値が取れるようになった方法もいくつかありますが、原因が分からないのでこれで良いのかも分からない、という状況です。
具体的な方法は以下のようなものです。
- クロージャにキャプチャリストを追加して、
b
を明示的に指定する。 -
a
の更新時にTask
を使う。
キャプチャリストを追加するというのは以下のようにします。
.sheet(item: a) { [bValue = b] aValue in
// aValueはaの新しい値。bValueはbの新しい値
}
Task
を使うのは、更新時に以下のようにします。
{
b = // 何かの値
Task {
a = // 何かの値
}
}
何かタイミング依存のような気はしているのですが…
何かタイミング依存のような気はしているのですが…
クロージャが構築される時点でキャプチャされている、というのであれば、まったく更新されてない、というような動きになっていれば納得なのです。
更新されるケースもあるのがよく分かりません。
というか、軽いサンプルを作ると更新されてしまいます。
今回は、a
とb
の更新と監視を分ける(同時に行わない)ように見直して、とりあえずは期待通りに動くようになりました。
こういうこと(同時に更新して片側を監視して、差異検出後に両方を参照)はしないほうがいいんでしょうかね。
Combine
とかで変数の更新に伴う通知が行くようになっていて、それで通知を受けてビューの更新が走るんだけども、更新といいながら再度init
されていて、@State
はinit
のあとに前のビューの値から引き継がれる?ようなことなんでしょうか。引き継ぎのタイミング次第で、見えたり見えなかったり。
この辺、裏方のやっている詳細な動きがよく分からないので、想像ですけども。