以下代码一直有效,直到对列表项执行删除操作为止。但这并不是每次代码运行时都会发生。它因EXC_BAD_ACCESS
个线程错误代码而崩溃:
然后我意识到,如果应用程序保持运行,则在删除操作后的一段时间后会发生此错误
这就是为什么这个错误很难弄清楚的原因。但是,当我在DispatchQueue.main.async
的{{1}}中添加tasks
方法时,它开始出现。
此代码的目的是当列表中发生任何更改时,使用Model.swift
方法从核心数据重新加载更新的结果。
我注意到的另一个问题是删除后出现红线。
问题:
macOS:10.15.4
XCode:11.5
目标:10.15
Model.swift
self.fetchAll()
AppDelegate.swift
import Foundation
import CoreData
class Model: ObservableObject {
@Published var context: NSManagedObjectContext
@Published var tasks: [Task] = [Task]() {
didSet {
DispatchQueue.main.async {
self.fetchAll()
}
}
}
init(_ viewContext: NSManagedObjectContext) {
context = viewContext
}
}
// MARK: Methods
extension Model {
func fetchAll() {
let req = Task.fetchRequest() as NSFetchRequest<Task>
req.sortDescriptors = [NSSortDescriptor(keyPath: \Task.name, ascending: true)]
do {
self.tasks = try self.context.fetch(req)
} catch let error as NSError {
print(error.localizedDescription)
}
}
func addTask(_ text: String) {
let name = text.trimmingCharacters(in: .whitespacesAndNewlines)
if (name != "") {
let task = Task(context: self.context)
task.id = UUID()
task.name = name
task.creationTimestamp = Date()
task.updatedTimestamp = Date()
self.context.insert(task)
self.save()
}
}
func save() {
guard self.context.hasChanges else { return }
do {
try self.context.save()
print("Saved changes")
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
ContentView.swift
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var model: Model!
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
model = Model(persistentContainer.viewContext)
let contentView = ContentView().environmentObject(model)
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) { }
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "Todo_List")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error {
fatalError("Unresolved error \(error)")
}
})
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.undoManager = nil
container.viewContext.shouldDeleteInaccessibleFaults = true
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
// MARK: - Core Data Saving and Undo support
@IBAction func saveAction(_ sender: AnyObject?) {
let context = persistentContainer.viewContext
if !context.commitEditing() {
NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing before saving")
}
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
NSApplication.shared.presentError(nserror)
}
}
}
func windowWillReturnUndoManager(window: NSWindow) -> UndoManager? {
return persistentContainer.viewContext.undoManager
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
let context = persistentContainer.viewContext
if !context.commitEditing() {
NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing to terminate")
return .terminateCancel
}
if !context.hasChanges {
return .terminateNow
}
do {
try context.save()
} catch {
let nserror = error as NSError
let result = sender.presentError(nserror)
if (result) {
return .terminateCancel
}
let question = NSLocalizedString("Could not save changes while quitting. Quit anyway?", comment: "Quit without saves error question message")
let info = NSLocalizedString("Quitting now will lose any changes you have made since the last successful save", comment: "Quit without saves error question info");
let quitButton = NSLocalizedString("Quit anyway", comment: "Quit anyway button title")
let cancelButton = NSLocalizedString("Cancel", comment: "Cancel button title")
let alert = NSAlert()
alert.messageText = question
alert.informativeText = info
alert.addButton(withTitle: quitButton)
alert.addButton(withTitle: cancelButton)
let answer = alert.runModal()
if answer == .alertSecondButtonReturn {
return .terminateCancel
}
}
return .terminateNow
}
}
TaskList.swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var model: Model
@State var taskName: String = ""
var body: some View {
VStack{
ZStack{
TaskList()
.padding(.top,31)
VStack(spacing:0){
TextField("New Task", text: self.$taskName, onCommit: {
self.model.addTask(self.taskName)
self.taskName = ""
})
.padding(5)
Divider().offset(y:-1)
Spacer()
}
if model.tasks.isEmpty {
Text("Nothing To Do\nPlease add something To Do.")
.multilineTextAlignment(.center)
.font(.headline)
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
.frame(minWidth: 200, maxWidth: .infinity, maxHeight: .infinity)
.onAppear{
self.model.fetchAll()
}
}
}
TaskRow.swift
import SwiftUI
struct TaskList: View {
@EnvironmentObject var model: Model
@State var selection: Task?
var body: some View {
List(selection: self.$selection){
ForEach(self.model.tasks, id: \.self) { task in
TaskRow(task: task).tag(task)
}
.onDelete(perform: onDelete)
}
}
private func onDelete(with indexSet: IndexSet) {
indexSet.forEach { index in
let task = self.model.tasks[index]
self.model.context.delete(task)
}
self.model.save()
}
}
答案 0 :(得分:0)
确保Task
是可识别的:
extension Task: Identifiable {
}
如果您不知道自己应该可以在ForEach
中这样做:
ForEach(self.model.tasks) {task in
}
我实际上也有这个问题。我要做的解决方法是添加一个字段isDeleted
,而不是实际上从CoreData删除记录。然后,您可以在应用程序处于后台运行或终止时稍后对其进行清理。或者只是将记录保存在Coredata中。
答案 1 :(得分:0)
由于我最近有类似的问题可以解决,因此我想尝试一下您的示例。我不确定您的问题是否仍然存在,因为该问题已经存在5个月了,没有得到答复。
简而言之:是的,您可以修复它。但是您将不得不使用SwiftUI,而不是反对它。这意味着:您的模型可以提供获取请求,但您不应该自己执行获取。让SwiftUI为您处理这个问题。
以下是必需的更改:
创建ContentView时,应将托管对象上下文添加到SwiftUI环境中:
let contentView = ContentView()
.environmentObject(model)
.environment(\.managedObjectContext, persistentContainer.viewContext)
删除您发布的tasks
属性和fetchAll
函数。我们将让SwiftUI处理此问题。
因为ContentView对任务一无所知,所以请删除对model.fetchAll()
的调用和对model.tasks.isEmpty
的检查。
添加FetchRequest
以创建您的tasks
属性,并在此处添加tasks.isEmpty
的支票。这是代码:
struct TaskList: View {
@EnvironmentObject var model: Model
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: true)],
animation: .default)
private var tasks: FetchedResults<Task>
@State var selection: Task?
@ViewBuilder
var body: some View {
if tasks.isEmpty {
Text("Nothing To Do\nPlease add something To Do.")
.multilineTextAlignment(.center)
.font(.headline)
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
else {
List(selection: $selection){
ForEach(tasks, id: \.id) { task in
TaskRow(task: task).tag(task)
}
.onDelete(perform: onDelete)
}
}
}
private func onDelete(with indexSet: IndexSet) {
indexSet.forEach { index in
let task = tasks[index]
self.model.context.delete(task)
}
self.model.save()
}
}
使用这种方法确实对我有用。 另外,删除动画现在可以正常工作并可以绘制。