You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)
    }
}

改动说明

  1. 移除冗余的isModalWithDetailsShown状态变量,无需用布尔值控制sheet显示。
  2. 将.sheet移到ScrollView外层,通过item: $selectedActivity绑定,当selectedActivity不为空时自动展示sheet,为空时自动关闭。
  3. 点击ActivityRow时仅需设置selectedActivity = activity,简化逻辑。

修改后,每次点击都会直接关联选中的Activity,sheet能正确加载对应内容,不会出现首次空白的问题。

内容的提问来源于stack exchange,提问作者sirekpiotr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 09:33:32