Следующий код работает до тех пор, пока операция удаления не будет выполнена для элементов списка. Но это происходит не каждый раз при запуске кода. Он вылетает с кодом ошибки потока EXC_BAD_ACCESS
:
Then I realized, this error is occurring after a certain time after delete operation if the app is kept running
This is why this error is very confusing to figure out. But it started appearing when I added DispatchQueue.main.async
method to tasks
in Model.swift
.
The purpose of this code is to reload updated results from core data using self.fetchAll()
method when there is any change happened in the list.
Another problem I noticed is the red lines appearing after deleting.
Questions:
- How to update content view struct with least code when a core data list is updated? (This ссылка имеет другой подход, поскольку метод выборки вызывается каждый раз явно в коде.)
- Как оптимизировать этот код с меньшим количеством кода и большей стабильностью?
- Является ли проблема с красной линией на картинке частью существующей ошибки macOS?
macOS : 10.15.4
XCode: 11.5
Целевой: 10.15
Model.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)
}
}
}
AppDelegate.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
}
}
ContentView.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()
}
}
}
TaskList.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()
}
}
TaskRow.swift
struct TaskRow: View {
@ObservedObject var task: Task
@EnvironmentObject var model: Model
var dateString: String {
if let timestamp = task.updatedTimestamp {
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .medium
return formatter.string(from: timestamp)
} else {
return "No date recorded"
}
}
var body: some View {
HStack {
Toggle(isOn: Binding<Bool>(get: { () -> Bool in
return self.task.checked
}, set: { (enabled) in
self.task.checked = enabled
self.task.updatedTimestamp = Date()
self.model.save()
})){
Text("")
}
VStack {
HStack {
Text(task.name ?? "Unknown Task").font(.system(size: 20))
Spacer()
}
HStack {
Text(dateString).font(.system(size: 10)).bold()
Spacer()
}
}
}
}
}