【SwiftUI】sheet(item:onDismiss...)のonDismissではitemに指定した値を使えない
SwiftUI でモーダルを表示して編集する目的で sheet(item:onDismiss:content:)
を使ったら盛大にハマって時間を溶かしたのでメモ。
sheet(item:onDismiss:content:)
の onDismiss
の closure 内では item
に指定した値は使えないので注意が必要という話です。
やったこと
以下の動画のような操作で、モーダルを閉じたときに save する、みたいな動作を実現します。
実現するために以下のようなコードを書きました。
class Item: ObservableObject, Identifiable {
var id = ""
@Published var name = ""
}
struct ContentView: View {
@ObservedObject var item = Item(id: "xxx", name: "Foo")
@State var editingItem: Item?
var body: some View {
List {
ForEach([item]) { item in
Button(action: {
// 編集中の item を保持
self.editingItem = item
}) {
Text(item.name)
}
}
// 編集対象を指定してモーダルを起動
.sheet(item: self.$editingItem, onDismiss: {
// 戻ってきたら保存する
self.editingItem.map { item in item.save() } // ①
}) { item in
EditView(item: item)
}
}
}
}
セルをタップ時にそのセルのデータを @State
で指定した変数(editingItem
)に入れておき、sheet
でそれを指定してモーダルを立ち上げています。
モーダルを閉じたら onDismiss
が呼ばれるので、その中で保存します。
これで良さそうに思いました。
onDismiss の時点で item: に指定した変数は nil になっている
実際に動かしてみると保存処理(item.save()
)が実行されません。
デバッガを使ってみると冒頭のコードで ① の item:
に指定している変数(editingItem
)が nil
になっていました。
公式のドキュメントを見てもそのようなことは書かれていません。
おそらく、引数 item
の型は Binding<Item?>
で、ここが nil
ではない値になったらモーダルを立ち上げる、という挙動なので、閉じたら逆に nil
にする、という仕様なのかなと思いました。(ということに気づくまで1日かかった)
以下のサイトでもモーダルを閉じたら item
に指定した変数は nil になるよ!と言っています。
Using alert() and sheet() with optionals
どうすればいいの?
立ち上げた先のビューで処理します。
struct EditView: View {
@ObservedObject var item: Item
var body: some View {
NavigationView {
...略
}
.onDisappear { // 閉じたときの処理
item.save()
}
}
}
編集した画面側で保存しているのでむしろこちらのほうが直感的ですね。
代案としては呼び出し側のビューで sheet
に渡す値とは別に変数を保持すればいいですがあまりスマートではなさそうです。
イメージとしては以下のような感じです。
struct ContentView: View {
@ObservedObject var item = Item(id: "xxx", name: "Foo")
@State var editingItem: Item?
@State var editingItemToBeSaved: Item?
var body: some View {
List {
ForEach([item]) { item in
Button(action: {
self.editingItem = item
// sheet の引数に渡すのとは別の変数で保持する
self.editingItemToBeSaved = item
}) {
Text(item.name)
}
}
.sheet(item: self.$editingItem, onDismiss: {
// sheet の引数に渡すのとは別の変数に入っているインスタンスで保存する
self.editingItemToBeSaved.map { item in item.save() }
}) { item in
EditView(item: item)
}
}
}
}
まとめ
以上、sheet(item:onDismiss:content:)
の onDismiss
の closure 内では item
に指定した値は使えないという話でした。
Xcode で補完すると onDismiss
が候補に現れるので、閉じたときの処理はここでやればいいや、と思ってしまったのが敗因です。
SwiftUI についてはまだ勘で書いているところがあるので、たくさん書いて理解しておきたいところです。
Discussion