I tried your code and found the same thing. These posts talk about some similar issues.
https://forums.developer.apple.com/forums/thread/661777
https://forums.developer.apple.com/forums/thread/659660
this one has a solution that worked
https://forums.developer.apple.com/forums/thread/714912?answerId=728415022#728415022
here's your code, working (based on the solution in the last link above), but it doesn't look like your original implementation:
@Environment(\.dismiss) var dismiss
var mealName: String
var body: some View {
VStack {
Text(mealName)
Button("OK") {
dismiss()
}
}
.padding()
}
}
struct ContentView: View {
@State var meals: [Meal] = [
Meal(name: "filet steak"),
Meal(name: "pepperoni pizza"),
Meal(name: "pancakes"),
Meal(name: "full breakfast")
]
@State private var selectedMeal: Meal?
var body: some View {
NavigationStack {
List {
ForEach(meals) { meal in
HStack {
Button {
selectedMeal = meal
} label: {
Text(meal.name)
.font(.headline)
}
}
}
}
.sheet(item: $selectedMeal) { item in
SheetView(mealName: item.name)
}
}
}
}
class Meal: Identifiable {
var id: UUID = UUID()
var name: String = ""
init() {}
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
}
that is, it has buttons not just simply lines of text.
here's code that works around the issue another way, by passing a binding to the sheet and using a small delay before changing isShowingMealForm.
@Environment(\.dismiss) var dismiss
@Binding var mealName: String
var body: some View {
VStack {
Text(mealName)
Button("OK") {
dismiss()
}
}
.padding()
}
}
struct ContentView: View {
@State var meals: [Meal] = [
Meal(name: "filet steak"),
Meal(name: "pepperoni pizza"),
Meal(name: "pancakes"),
Meal(name: "full breakfast")
]
@State private var isShowingMealForm = false
@State private var selectedMeal: Meal?
@State private var mealName: String = "no meal selected"
var body: some View {
NavigationStack {
List {
ForEach(meals) { meal in
HStack {
Text(meal.name)
.font(.headline)
}
.onTapGesture {
if self.isShowingMealForm {
self.isShowingMealForm = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.selectedMeal = meal
self.mealName = meal.name
self.isShowingMealForm = true
}
} else {
self.selectedMeal = meal
self.mealName = meal.name
self.isShowingMealForm = true
}
}
}
}
}
.sheet(isPresented: $isShowingMealForm) {
SheetView(mealName: $mealName)
}
}
}
class Meal: Identifiable {
var id: UUID = UUID()
var name: String = ""
init() {}
init(id: UUID = UUID(), name: String) {
self.id = id
self.name = name
}
}
This works too, but I am a loss to clearly explain why. I put a breakpoint in your original onTapGesture
closure, and I can clearly see self.selectedMeal
obstinately staying at nil
even after the line self.selectedMeal = meal.
It only works as expected after you click on another meal line.
I'm not at all comfortable with arbitrary delays in code, and the version with .sheet(item)
has fewer lines of code than the one with .sheet( isPresented: ),
so I'd go with that.
I'd love it if someone who understands this stuff could chime in and tell us why the observed behavior differs from the expected though.