よくある状況
たとえば以下のようなコードを書いたとします。
SwiftUI
@State private var currentSheet: SheetType = .name
@State private var isShowSheet: Bool = false
enum SheetType {
case iconImage, name
// ... ほかの画面
}
// どこかのボタンアクション
Button("アイコン画像を編集") {
currentSheet = .iconImage
isShowSheet = true
}
ところが、実行してみると currentSheet を .iconImage に設定しているのに、なぜか .name の画面が出てしまう という現象が起きることがあります。
SwiftUI では「同じアクション内で順番どおりに書いた State の変更」が 必ずしも意図した順序で反映されるとは限らない ケースがあるためです。
特に、すでにシートが表示されている状態で別のシートに切り替える、などの状況が絡むと、競合が起きやすくなります。
原因: SwiftUI の State 反映タイミング
SwiftUI は「宣言的 UI」であり、View の再描画が「命令した順」に厳密に行われるわけではありません。
通常は「1つのアクション内の State 変更」がまとめて1回の再描画サイクルになり、結果として
SwiftUI
currentSheet = .iconImage
isShowSheet = true
が同時に反映され、.iconImage のシートが開きます。
しかし何らかの事情でシートがまだ閉じきっていなかったり、システム的な割り込みが入ったりすると、
期待通りに「currentSheet を更新 → isShowSheet = true」という順番が適用されないことがあります。
解決策: .onChange(of: someValue) を使って「enum が変わったらシートを開く」
以下のように currentSheet を Optional にしておき、.onChange(of: currentSheet) で「値が入ったらシートを開く」ようにします。
SwiftUI
@State private var currentSheet: SheetType? = nil
@State private var isShowSheet: Bool = false
enum SheetType {
case iconImage, name, memory, ...
}
var body: some View {
VStack {
Button("アイコン画像") {
currentSheet = .iconImage
}
}
.onChange(of: currentSheet) { newValue in
// currentSheet が nil でなければシートを開く
isShowSheet = (newValue != nil)
}
.sheet(isPresented: $isShowSheet) {
if currentSheet == .iconImage {
IconImageEditView(...)
} else if currentSheet == .name {
NameEditView(...)
}
// 他のケースは else if ...
}
}