首页 > 解决方案 > SwiftUI:创建 CoreData 对象后无法关闭工作表

问题描述

我正在尝试创建一个具有两个视图的非常基本的应用程序。主视图在 NavigationView 中显示 CoreData 对象,并具有用于打开“添加项目”模式表的工具栏按钮。“添加项目”表应该能够插入一个新的 CoreData 对象并自行关闭。我观察到的是,只要模式表不插入 CoreData 对象,就可以关闭它,但是一旦它与 CoreData 交互,我所有关闭工作表的尝试都不再有效。此外,当工作表创建 CoreData 对象时,我收到以下错误:

[Presentation] Attempt to present < TtGC7SwiftUI22SheetHostingControllerVS_7AnyView : 0x7f8fcbc49a10> on < TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier _: 0x7f8fcbd072c0> (from < TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVVS_22_VariadicView_Children7ElementGVS_18StyleContextWriterVS_19SidebarStyleContext __: 0x7f8fbbd0ba80>) which is already presenting < TtGC7SwiftUI22SheetHostingControllerVS_7AnyView : 0x7f8fcbd310b0>.

我使用的大部分代码都是 Xcode 自己的样板代码,以及从网络上借来的常见模式。你能帮我调试一下吗?

为了重现,在 Xcode 12 中,使用 CoreData 创建一个新的 SwiftUI iOS 应用程序。将 ContentView.swift 替换为:

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>
    
    @State var showingSheet: Bool = false

    var body: some View {
        NavigationView {
            List {
                //Text("static text")

                ForEach(items) { item in
                    Text("Item at \(item.timestamp!, formatter: itemFormatter)")
                }
                .onDelete(perform: deleteItems)
            }
            .navigationTitle("List of items")
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    Button(action: {
                        showingSheet = true
                    }) {
                        Text("Open sheet")
                    }
                    .sheet(isPresented: $showingSheet) {
                        MySheetView(isPresented: $showingSheet)
                            .environment(\.managedObjectContext, viewContext)
                    }
                }
            }
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[$0] }.forEach(viewContext.delete)
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .medium
    return formatter
}()

创建一个新的 MySheetView.swift:

struct MySheetView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @Binding var isPresented: Bool
    
    var body: some View {
        NavigationView {
            Form {
                Button(action: {
                    self.isPresented = false
                }) {
                    Text("Dismiss this sheet")
                }
                
                Button(action: {
                    let newItem = Item(context: viewContext)
                    newItem.timestamp = Date()

                    do {
                        try viewContext.save()
                    } catch {
                        let nsError = error as NSError
                        fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                    }
                }) {
                    Text("Add Item")
                }
            }
        }
    }
}

运行时,如果点击“打开工作表”,然后可以点击“关闭此工作表”,它就可以工作了。但是,如果您点击“打开工作表”,然后点击“添加项目”,则会打印错误并且“关闭此工作表”按钮停止工作。(您仍然可以向下滑动以关闭工作表。)

我也尝试了presentationmode.wrappedvalue.dismiss() 方法,但没有成功。

我发现,如果您在主视图上注释掉 ForEach 循环并取消注释文本(“静态文本”),那么在工作表上添加项目不再打印出错误或中断工作表的解散。所以这个问题在某种程度上与在主工作表上显示数据有关。但显然我确实需要实际显示数据,这似乎是一种非常常见和基本的模式。我做错了吗?

标签: iosswiftcore-dataswiftui

解决方案


.sheet被放置在 ToolbarItem 上。只需更改它NavigationView,它就可以工作。

这是 ContentView 的主体

var body: some View {
    NavigationView {
        List {
            //Text("static text")

            ForEach(items, id:\.self) { item in
                Text("Item at \(item.timestamp!, formatter: itemFormatter)")
            }
            .onDelete(perform: deleteItems)
        }
        .navigationTitle("List of items")
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(action: {
                    showingSheet = true
                }) {
                    Text("Open sheet")
                }
            }
        }
        .sheet(isPresented: $showingSheet) {
            MySheetView(isPresented: $showingSheet)
                .environment(\.managedObjectContext, viewContext)
        }

    }
}

推荐阅读