【发布时间】:2021-09-06 17:03:28
【问题描述】:
使用虚拟项目来了解更多关于使用 MVVM 在 SwiftUI 中的 Core Data 的信息。打印语句显示正在保存对象,但除非重新启动应用程序,否则不会显示新条目。已删除的对象会立即删除。所以不知道让新条目立即出现在哪里我错了。我想我可以在每次保存后重新调用 fetchTasks,但由于 fetch 任务在 TaskListViewModel 中,我不知道该怎么做,我应该将 fetch 任务移动到 CoreDataHandler 吗?
参赛作品
import SwiftUI
@main
struct TestCore: App {
//Inject Core Data container managed object context into environment
let coreDataHandler = CoreDataHandler.shared
//Optional: Add property to monitor scene phase
@Environment(\.scenePhase) var scenePhase
var body: some Scene {
WindowGroup {
//Attach Core Date view context
TaskListView().environment(\.managedObjectContext, coreDataHandler.container.viewContext)
}
//Whenver the app is moved to the background CoreDataUtility save() function is called to save any changes
.onChange(of: scenePhase) { _ in
coreDataHandler.save()
}
}
}
CoreDataHandler.swift
import Foundation
import CoreData
struct CoreDataHandler {
//Create a singleton to be used/shared throughout app
static let shared = CoreDataHandler()
//Create the storage container for Core Data
let container: NSPersistentContainer
//MARK: - Test Configuration for SwiftUI Previews
//Create a test configuration for SwiftUI Previews
static var preview: CoreDataHandler = {
let handler = CoreDataHandler(inMemory: true)
//Create example task entries
for _ in 0..<10 {
let task = Task(context: handler.container.viewContext)
task.title = "Untitled Task"
task.dateCreated = Date()
task.dateDue = Date()
task.completed = false
task.archived = false
}
return handler
}()
//MARK: - Initializer to Load Core Data
//Create an initializer to load Core Data, optionally able to use in-memory store
//TODO: PRE-LAUNCH - Change inMemory back to false
init(inMemory: Bool = false) {
//Point the NSPersistentContainer to the Data Model
container = NSPersistentContainer(name: "TestCore")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { description, error in
if let error = error {
fatalError("????ERROR: \(error.localizedDescription)!")
} else {
print("????SUCCESS: Successfully loaded Core Data")
}
}
}
//MARK: - Core Data Interactions
func save() {
let context = container.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
print("????ERROR: Unable to save to Core Data!")
}
}
}
}
TaskListView.swift - 负责显示任务
import SwiftUI
import CoreData
struct TaskListView: View {
@Environment(\.managedObjectContext) var coreDataHandler
@StateObject var viewModel = TaskListViewModel()
@State var showAddTaskView = false
@State var dismissView = false
var body: some View {
NavigationView {
ZStack {
List {
ForEach(viewModel.taskList) { task in
//TODO: Load in Custom Task View
TaskCardView(taskTitle: task.title ?? "No Title")
.toolbar {
EditButton()
}
}
.onDelete(perform: viewModel.deleteTask)
.background(Color.blue)
}
.listStyle(PlainListStyle())
VStack {
Spacer()
Button(action: {
showAddTaskView = true
}, label: {
Text("Add Task")
.frame(width: 150, height: 60)
.font(.headline)
.foregroundColor(.black)
.background(Color.yellow)
.cornerRadius(30)
})
.sheet(isPresented: $showAddTaskView, content: {
AddTaskView()
})
}
}
}
.navigationTitle("Tasks")
.onAppear(perform: viewModel.fetchTasks)
}
}
TaskListViewModel
import Foundation
import CoreData
final class TaskListViewModel : ObservableObject {
var coreData = CoreDataHandler.shared
@Published var taskList : [Task] = []
init() {
fetchTasks()
}
func fetchTasks() {
let request = NSFetchRequest<Task>(entityName: "Task")
do {
taskList = try coreData.container.viewContext.fetch(request)
} catch let error {
print("????ERROR: Error fetching tasks for TaskListView - \(error)")
}
}
func deleteTask(indexSet: IndexSet) {
guard let index = indexSet.first else { return }
let task = taskList[index]
coreData.container.viewContext.delete(task)
//Save change to Core Data
coreData.save()
//Fetch tasks to refresh taskList
fetchTasks()
}
}
AddTaskView.swift - 负责添加任务
import SwiftUI
struct AddTaskView: View {
@Environment(\.managedObjectContext) var coreDataHandler
@Environment(\.presentationMode) var presentationMode
@StateObject var viewModel = AddTaskViewModel()
@StateObject var taskListViewModel = TaskListViewModel()
@State var titleTextFieldText: String = ""
var body: some View {
NavigationView {
VStack(spacing: 20) {
TextField("Task Title", text: $titleTextFieldText)
.font(.headline)
.padding(.leading)
.frame(height: 55)
//TODO: Update to a specific color
.background(Color.gray)
.cornerRadius(10)
.padding(.horizontal)
Button(action: {
viewModel.addTask(taskTitle: titleTextFieldText, taskDueDate: Date())
//Dismiss View if successful
self.presentationMode.wrappedValue.dismiss()
}, label: {
Text("Add Task")
.frame(width: 150, height: 60)
.font(.headline)
.foregroundColor(.black)
.background(Color.yellow)
.cornerRadius(30)
})
Spacer()
}
}
.navigationTitle("Create New Task")
.navigationBarTitleDisplayMode(.large)
}
}
AddTaskViewModel.swift
import Foundation
final class AddTaskViewModel : ObservableObject {
var coreDataHandler = CoreDataHandler.shared
func addTask(taskTitle: String, taskDueDate: Date) {
let newTask = Task(context: coreDataHandler.container.viewContext)
newTask.title = taskTitle;
newTask.dateCreated = Date();
newTask.dateDue = taskDueDate;
newTask.completed = false
newTask.archived = false
coreDataHandler.save()
}
}
【问题讨论】: