1

我有一个简单的 SwiftUI、CoreData 应用程序。该体系结构是具有用于查看详细信息或编辑详细信息的第二个视图的基本列表。基本结构似乎有一个重要的例外。编辑记录时,应用启动后的第一次编辑在返回 ContentView 列表后正确可见。返回 ContentView 时,第二次和进一步的编辑不会出现在列表中。数据库更改已正确保存。重新启动应用程序将显示正确的数据。我还创建了一个 TabView。如果我禁用编辑后返回到主列表的代码并仅使用 TabView 进行切换,则始终会显示更改。

这是代码(我已经删除了大部分重复的数据字段)。

在场景委托中:

let managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let tabby = TabBar().environment(\.managedObjectContext, managedObjectContext)
window.rootViewController = UIHostingController(rootView: tabby)

在内容视图中:

@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(fetchRequest: ToDoItem.getAllToDoItems()) var toDoItems: FetchedResults<ToDoItem>

@State private var newToDoItem = ""
@State private var gonnaShow = true
@State private var show = false

var body: some View {
    NavigationView {
        List {
             Section(header: Text("Records")) {
                ForEach(self.toDoItems) { toDoItem in
                    NavigationLink(destination: EditToDo(toDoItem: toDoItem)) {
                        ToDoItemView(
                            idString: toDoItem.myID.uuidString,
                            title: toDoItem.title!,
                            firstName: toDoItem.firstName!,
                            lastName: toDoItem.lastName!,
                            createdAt: self.localTimeString(date: toDoItem.createdAt!)
                                    )
                    }
                }//for each
                .onDelete { indexSet in
                    let deleteItem = self.toDoItems[indexSet.first!]
                    self.managedObjectContext.delete(deleteItem)

                    do {
                        try self.managedObjectContext.save()
                    } catch {
                        print(error)
                    }
                }
                .onMove(perform: move)
            }
        }
        .navigationBarTitle("Customers")
        .navigationBarItems(trailing: EditButton())
    }
}

单独的文件 EditToDo:

@Environment(\.managedObjectContext) var managedObjectContext
@Environment(\.presentationMode) var presentationMode

var toDoItem: ToDoItem

@State private var updatedTitle: String = "No Title"
@State private var updatedFirstName: String = "No First Name"
//more data fields

@State private var updatedDate: Date = Date()
@State private var updatedDateString: String = "July 2019"

var body: some View {
    ScrollView {
    VStack {
        Image("JohnForPosting")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 80, height: 80)
            .clipShape(Circle())

        VStack(alignment: .leading) {
            Text("ToDo Title:")
                .padding(.leading, 5)
                .font(.headline)
            TextField("Enter a Title", text: $updatedTitle)
                .onAppear {
                    self.updatedTitle = self.toDoItem.title ?? ""
            }
            .textFieldStyle(RoundedBorderTextFieldStyle())
        }
        .padding(10)

        VStack(alignment: .leading) {
            Text("First Name:")
                .padding(.leading, 5)
                .font(.headline)
            TextField("Enter a First Name", text: $updatedFirstName)
                .onAppear {
                    self.updatedFirstName = self.toDoItem.firstName ?? ""
            }
            .textFieldStyle(RoundedBorderTextFieldStyle())
        }
        .padding(10)
        //more data fields


        VStack {
            Button(action: ({
                self.toDoItem.title = self.updatedTitle
                self.toDoItem.firstName = self.updatedFirstName
                //more data fields

                do {
                    try self.managedObjectContext.save()
                } catch {
                    print(error)
                }

                self.updatedTitle = ""
                self.updatedFirstName = ""
                //more data fields

                self.presentationMode.wrappedValue.dismiss()

            })) {
                Text("Save")
            }
            .padding()
        }
        .padding(10)
        Spacer()
    }
}
} 

单独的文件 ToDoItemView:

var idString: String = ""
var title: String = ""
var firstName: String = ""
//more data fields

var body: some View {
    HStack {
        VStack(alignment: .leading) {
            Text("\(firstName) \(lastName)")
                .font(.headline)
            Text("\(createdAt) and \(idString)")
                .font(.caption)
        }
    }
}

Xcode 11 - 我想这是真正的版本(GM 种子 2 后),Catalina Beta 19A558d,iOS13.1 我认为 @Environment 的更改总是会导致 ContentView 的主体被重绘。这是第一次编辑工作的奇怪行为,其他人则不然。任何指导将不胜感激。

4

2 回答 2

1

我目前的项目也有同样的问题,基本代码也和你一样。由 FetchRequest 提供的项目列表,导航到用于编辑项目的第二个视图。

和你一样,如果我在详细视图中进行更改,这些更改会反映在列表中,但只是第一次。如果我通过更改到不同的选项卡并再次返回来重新加载列表,列表将更新显示更新的数据。

问题的根源在于使用@FetchRequest 的结果创建一个动态列表。FetchRequest 返回一组 NSManagedObjects(引用类型)。由于 List 以一组引用类型为种子,SwiftUI 只会在引用更改时更新视图,而不一定是在对象的某个属性发生更改时更新视图。

我认为@FetchRequest 对于像 Pickers 或菜单选项这样的东西会很好,但是对于 TodoItems 的动态列表,更好的解决方案可能是创建一个 TodoManager。

TodoManager 将是所有 TodoItems 的唯一真实来源。它还将设置一个 NSManagedObjectContextObserver,当发生任何更改时,它会触发 objectWillChange.send() 通知,从而发出 SwiftUI 应该刷新列表的信号。

class TodoManager: ObservableObject {

private var moc: NSManagedObjectContext


public let objectWillChange = PassthroughSubject<Void, Never>()


var todoList: [TodoItem] = []


init( context: NSManagedObjectContext ) {

    moc = context

    setupNSManagedObjectContextObserver( context: context )


    todoList = Array( TodoItem.fetch(in: context ))

}


func setupNSManagedObjectContextObserver( context moc: NSManagedObjectContext ) {

    let notificationCenter = NotificationCenter.default
    notificationCenter.addObserver(self, selector: #selector(managedObjectContextObjectsDidChange), name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: moc)
    notificationCenter.addObserver(self, selector: #selector(managedObjectContextWillSave), name: NSNotification.Name.NSManagedObjectContextWillSave, object: moc)
    notificationCenter.addObserver(self, selector: #selector(managedObjectContextDidSave), name: NSNotification.Name.NSManagedObjectContextDidSave, object: moc)

}


 @objc func managedObjectContextObjectsDidChange(notification: NSNotification) {

    self.objectWillChange.send()    // Crude but works


    guard let userInfo = notification.userInfo else { return }


    if let inserts = userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>, inserts.count > 0 {  }

    if let updates = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>, updates.count > 0 {  }

    if let deletes = userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>, deletes.count > 0 {  }

}

@objc func managedObjectContextWillSave(notification: NSNotification) {

    guard let userInfo = notification.userInfo else { return }

    if let inserts = userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>, inserts.count > 0 {  }

    if let updates = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>, updates.count > 0 {  }

    if let deletes = userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>, deletes.count > 0 {  }

}


@objc func managedObjectContextDidSave(notification: NSNotification) {

    guard let userInfo = notification.userInfo else { return }

    if let inserts = userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>, inserts.count > 0 {  }

    if let updates = userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>, updates.count > 0 {  }

    if let deletes = userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>, deletes.count > 0 {  }

}
}

TodoManager 非常愚蠢,因为它会在 ManagedObjectContext 中的任何数据发生更改时发送 objectWillChange 通知。

史蒂夫

于 2019-09-30T21:10:57.283 回答
0

Xcode 11.1 GM 种子解决了这个问题。对于它的价值,核心数据馈送列表的预览功能仍然不起作用。

于 2019-10-01T17:44:57.957 回答