SwiftUI首次点击ActivityRow弹出空白全屏Sheet问题排查
SwiftUI Sheet首次点击显示空白的问题分析
问题现象
首次点击任意ActivityRow时,会弹出空白的全屏Sheet,但第二次及之后点击都能正常显示携带指定Activity数据的Sheet(支持分档尺寸)。
涉及代码
struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query(sort: \Activity.dateOfLastCompletion, order: .reverse) private var activities: [Activity] @State private var isModalWithDetailsShown = false @State private var isNewActivitySheetShown = false @State var selectedActivity: Activity? = nil var body: some View { NavigationStack { VStack { if activities.isEmpty { ContentUnavailableView("No activities", systemImage: "mug.fill", description: Text("Use the plus button to start tracking your first activity")) } else { ScrollView { LazyVGrid(columns: [GridItem()], spacing: 16) { ForEach(activities, id: \.id) { activity in ActivityRow(activity: activity) .onTapGesture { selectedActivity = activity isModalWithDetailsShown = true } .tag(activity as Activity?) .sheet(isPresented: $isModalWithDetailsShown, onDismiss: { selectedActivity = nil }) { if let selectedActivity = selectedActivity { ModalActivityDetailView(activity: selectedActivity) .presentationDetents([.fraction(0.20), .fraction(1)]) .presentationDragIndicator(.visible) } } } } .padding(.horizontal) }.scrollIndicators(.hidden) } } .navigationTitle("Activities") .toolbar { ToolbarItem(placement: .topBarLeading) { Button(action: { print("User went to settings") }) { Text("Settings") } } ToolbarItem(placement: .topBarTrailing) { Button(action: { isNewActivitySheetShown.toggle() }, label: { Image(systemName: "plus") }) .sheet(isPresented: $isNewActivitySheetShown) { NewAcitivityView() } } }.tint(.primary) } }
问题原因
核心问题是将.sheet修饰符附加到了ForEach循环的每个ActivityRow上,且使用isPresented绑定同一个布尔状态:
- 首次点击时,
isModalWithDetailsShown变为true,此时ForEach生成的所有ActivityRow上的.sheet都会响应这个状态,SwiftUI无法确定要展示哪一个sheet,导致渲染冲突,最终显示空白。 - 第二次及后续点击时,之前的sheet已完成初始化,状态和选中的
selectedActivity形成关联,因此能正常加载对应内容。
解决方案
把.sheet从ForEach内部移到外层视图上,改用item绑定直接关联selectedActivity,让SwiftUI精准识别要展示的内容:
struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query(sort: \Activity.dateOfLastCompletion, order: .reverse) private var activities: [Activity] @State private var isNewActivitySheetShown = false @State var selectedActivity: Activity? = nil var body: some View { NavigationStack { VStack { if activities.isEmpty { ContentUnavailableView("No activities", systemImage: "mug.fill", description: Text("Use the plus button to start tracking your first activity")) } else { ScrollView { LazyVGrid(columns: [GridItem()], spacing: 16) { ForEach(activities, id: \.id) { activity in ActivityRow(activity: activity) .onTapGesture { selectedActivity = activity } } } .padding(.horizontal) } .scrollIndicators(.hidden) // 将sheet移到ScrollView外层,使用item绑定 .sheet(item: $selectedActivity) { activity in ModalActivityDetailView(activity: activity) .presentationDetents([.fraction(0.20), .fraction(1)]) .presentationDragIndicator(.visible) } } } .navigationTitle("Activities") .toolbar { ToolbarItem(placement: .topBarLeading) { Button(action: { print("User went to settings") }) { Text("Settings") } } ToolbarItem(placement: .topBarTrailing) { Button(action: { isNewActivitySheetShown.toggle() }, label: { Image(systemName: "plus") }) .sheet(isPresented: $isNewActivitySheetShown) { NewAcitivityView() } } }.tint(.primary) } }
改动说明
- 移除冗余的
isModalWithDetailsShown状态变量,无需用布尔值控制sheet显示。 - 将
.sheet移到ScrollView外层,通过item: $selectedActivity绑定,当selectedActivity不为空时自动展示sheet,为空时自动关闭。 - 点击
ActivityRow时仅需设置selectedActivity = activity,简化逻辑。
修改后,每次点击都会直接关联选中的Activity,sheet能正确加载对应内容,不会出现首次空白的问题。
内容的提问来源于stack exchange,提问作者sirekpiotr
相关产品推荐
相关产品推荐

