ボタンのアクションで@State変数の値を書き換えフラグをトグルして.sheetで画面遷移するとき、その変数が書き変わらない時があった。
sheetを一回閉じて他のViewをアクションさせてからだと値は書き変わった。
@EnvironmentObject にして対応、以下 はそのことをGPTに聞いたときの回答。
✅ その現象の正体
.sheet は 表示開始時に引数をキャプチャして保持し続ける ため、
- sheet を開く前に書き換えた @State が
- sheet の初期化時点の古い値のまま使われてしまう
という SwiftUI 特有の仕様です。
つまり:
|
1 2 3 4 5 6 7 |
@State var value = 0 Button("change") { value = 1 } .sheet(item: $value) { v in // ここで使われる v は sheet が最新値を拾わない! } |
sheet を閉じるまで “初期値の v” を使い続けてしまう
✅ @EnvironmentObject にしたら直る理由
EnvironmentObject や ObservableObject なら:
- sheet の View が オブジェクト参照そのもの を持つ
- プロパティが変更されると Publisher が通知
- sheet 内の値が 自動で更新される
つまり sheet が「値のコピー」ではなく「参照」を共有するようになります。
📌 他の解決方法(EnvironmentObject の他にもいくつかある)
① sheet の view を “id 付き” でリフレッシュする(裏技)
|
1 2 3 4 |
.sheet(isPresented: $showSheet) { SheetView(value: value) .id(value) // ← 実質再作成される } |
value が変わった時に sheet 内部の View が再生成され、
キャプチャ問題を回避できます。
② Binding を渡す
シンプルで効果的。
|
1 2 3 |
.sheet(isPresented: $showSheet) { SheetView(value: $value) } |
こうすれば sheet は 常に最新の $value を参照できます。
・EnvironmentObject ほど大げさじゃない
・NavigationStack の動作とも相性が良い
これは最も “SwiftUI らしい” 解決法。
③ sheet の presentation detent を一度閉じて強制再生成
推奨はしませんが、技術的には可能。
|
1 2 3 4 |
showSheet = false DispatchQueue.main.async { showSheet = true } |
④ sheet(item:) を使わずに sheet(isPresented:) を使う
.sheet(item:) は特にキャプチャが強いので、.sheet(isPresented:) の方が素直です。
|
1 2 3 |
.sheet(isPresented: $show) { SheetView(value: value) } |
✔️ まとめ(何が一番正しい?)
| 方法 | メリット | デメリット |
|---|---|---|
| EnvironmentObject(採用済) | 状態共有が強い。大規模向け | オブジェクトが大きくなりがち |
| Binding を渡す(推奨) | 最新状態を確実に反映。シンプル | 渡す変数が多いと煩雑 |
| id(value) を使う | コード量が少ない | sheet が毎回再生成される |
| isPresented を使う | キャプチャ問題が軽い | 引数を埋め込みたかった場合に工夫が必要 |

コメント