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

SwiftUI中@Query导致.sheet(item:content:)被重复调用两次的问题

SwiftUI中@Query导致.sheet(item:)的content闭包重复调用的解决办法

在使用SwiftUI的@Query获取Core Data数据时,点击按钮设置editingToDo后,.sheet(item:content:)的content闭包会被调用两次,日志显示第一次editingToDo为nil,第二次才是预期的对象。已确认用FetchDescriptor替代@Query可解决问题,但不想引入副作用,以下是其他可行方案:

方案1:改用isPresented绑定替代item绑定

将基于Identifiable对象的item绑定,改为布尔值的isPresented绑定,结合单独存储选中对象的变量,避免因@Query刷新导致的闭包重复执行:

struct TodayPage: View {
    @Environment(\.modelContext) private var modelContext
    @FocusState private var focusedToDo: ToDoModel?
    @State private var isEditing = false
    @State private var currentEditingToDo: ToDoModel?
    
    @Query(
        filter: #Predicate<ToDoModel> { $0.isRepeat },
        sort: \.order
    )
    private var toDos: [ToDoModel]
    
    private var visibleToDos: [ToDoModel] {
        let sortedToDos = toDos.sorted(option: .sortByGoal)
        return sortedToDos.filter { $0.repeatDays.contains(.now) }
    }
    
    var body: some View {
        NavigationStack {
            GeometryReader { proxy in
                if visibleToDos.isEmpty {
                    ContentUnavailableView(
                        LocalizedString.Today.emptyTitle,
                        systemImage: emptyImageName
                    )
                    .background(Color.background)
                } else {
                    List {
                        ForEach(visibleToDos) { toDo in
                            HStack(alignment: .top, spacing: 10) {
                                // 其他视图...
                                
                                VStack(alignment: .leading, spacing: 8) {
                                    HStack {
                                        // 其他视图...
                                        
                                        if focusedToDo == toDo {
                                            Button {
                                                currentEditingToDo = toDo
                                                isEditing = true
                                            } label: {
                                                Image(systemName: "info.circle")
                                                    .resizable()
                                                    .frame(width: 20, height: 20)
                                            }
                                        }
                                    }
                                    
                                    // 其他视图...
                                }
                            }
                        }
                    }
                }
            }
        }
        .sheet(isPresented: $isEditing) {
            if let toDo = currentEditingToDo {
                ToDoFormSheet(toDo: toDo)
            }
        }
        .onDismiss {
            currentEditingToDo = nil
        }
    }
}

方案2:将Sheet封装到独立的宿主视图

把Sheet的逻辑抽离到一个不会因@Query数据更新而重建的独立视图中,隔离@Query刷新对Sheet的影响:

struct TodayPage: View {
    @Environment(\.modelContext) private var modelContext
    @FocusState private var focusedToDo: ToDoModel?
    @State private var editingToDo: ToDoModel?
    
    @Query(
        filter: #Predicate<ToDoModel> { $0.isRepeat },
        sort: \.order
    )
    private var toDos: [ToDoModel]
    
    private var visibleToDos: [ToDoModel] {
        let sortedToDos = toDos.sorted(option: .sortByGoal)
        return sortedToDos.filter { $0.repeatDays.contains(.now) }
    }
    
    var body: some View {
        SheetHost(editingToDo: $editingToDo) {
            NavigationStack {
                GeometryReader { proxy in
                    if visibleToDos.isEmpty {
                        ContentUnavailableView(
                            LocalizedString.Today.emptyTitle,
                            systemImage: emptyImageName
                        )
                        .background(Color.background)
                    } else {
                        List {
                            ForEach(visibleToDos) { toDo in
                                HStack(alignment: .top, spacing: 10) {
                                    // 其他视图...
                                    
                                    VStack(alignment: .leading, spacing: 8) {
                                        HStack {
                                            // 其他视图...
                                            
                                            if focusedToDo == toDo {
                                                Button {
                                                    editingToDo = toDo
                                                } label: {
                                                    Image(systemName: "info.circle")
                                                        .resizable()
                                                        .frame(width: 20, height: 20)
                                                }
                                            }
                                        }
                                        
                                        // 其他视图...
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

struct SheetHost<Content: View>: View {
    @Binding var editingToDo: ToDoModel?
    let content: Content
    
    init(editingToDo: Binding<ToDoModel?>, @ViewBuilder content: () -> Content) {
        self._editingToDo = editingToDo
        self.content = content()
    }
    
    var body: some View {
        content
            .sheet(item: $editingToDo) { toDo in
                ToDoFormSheet(toDo: toDo)
            }
    }
}

方案3:避免@Query触发不必要的视图刷新

将visibleToDos转换为@State变量,通过onChange监听toDos的变化来更新,减少视图重建次数:

struct TodayPage: View {
    @Environment(\.modelContext) private var modelContext
    @FocusState private var focusedToDo: ToDoModel?
    @State private var editingToDo: ToDoModel?
    @State private var visibleToDos: [ToDoModel] = []
    
    @Query(
        filter: #Predicate<ToDoModel> { $0.isRepeat },
        sort: \.order
    )
    private var toDos: [ToDoModel]
    
    var body: some View {
        NavigationStack {
            GeometryReader { proxy in
                if visibleToDos.isEmpty {
                    ContentUnavailableView(
                        LocalizedString.Today.emptyTitle,
                        systemImage: emptyImageName
                    )
                    .background(Color.background)
                } else {
                    List {
                        ForEach(visibleToDos) { toDo in
                            HStack(alignment: .top, spacing: 10) {
                                // 其他视图...
                                
                                VStack(alignment: .leading, spacing: 8) {
                                    HStack {
                                        // 其他视图...
                                        
                                        if focusedToDo == toDo {
                                            Button {
                                                editingToDo = toDo
                                            } label: {
                                                Image(systemName: "info.circle")
                                                    .resizable()
                                                    .frame(width: 20, height: 20)
                                            }
                                        }
                                    }
                                    
                                    // 其他视图...
                                }
                            }
                        }
                    }
                }
            }
        }
        .sheet(item: $editingToDo) { toDo in
            ToDoFormSheet(toDo: toDo)
        }
        .onChange(of: toDos) { newValue in
            let sortedToDos = newValue.sorted(option: .sortByGoal)
            visibleToDos = sortedToDos.filter { $0.repeatDays.contains(.now) }
        }
    }
}

问题核心原因

@Query是Observable类型,当Core Data数据变化时会触发视图body重新计算,而.sheet(item:content:)的content闭包会在视图重建时被再次执行,加上editingToDo的状态变化,最终导致两次调用。上述方案通过隔离状态变化、减少视图刷新频率等方式解决该问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:34:54