问题描述
为什么 Environment(\.managedobjectContext).wrappedValue
上的 entityForName 始终为零?
我收到此错误 +entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Project'
使用 @Environment(\.managedobjectContext) var viewContext
我没有收到此错误。
但是我需要使用需要传递 NSManagedobjectContext
的控制器来初始化视图。
有人可以帮助理解为什么这两行不返回相同的对象吗?还是一样?
@main
struct umbrellaApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedobjectContext,persistenceController.container.viewContext)
}
}
}
struct ContentView: View {
@Environment(\.managedobjectContext) var viewContext // Works
@StateObject private var controller: ContentViewController
init() {
// Crashes
let viewContextValue = Environment(\.managedobjectContext).wrappedValue
let controller = ContentViewController(managedobjectContext: viewContextValue)
self._controller = StateObject(wrappedValue: controller)
}
var body: some View {
NavigationView {
Text("Hello World")
}
}
}
ContentViewController
的初始化器。
init(managedobjectContext: NSManagedobjectContext) {
self.managedobjectContext = managedobjectContext
self.projectsController = NSFetchedResultsController(fetchRequest: Project.projectsFetchRequest,managedobjectContext: managedobjectContext,sectionNameKeyPath: nil,cacheName: nil)
super.init()
projectsController.delegate = self
do {
try projectsController.performFetch()
self.projects = projectsController.fetchedobjects ?? []
} catch {
print("Failed to fetch projects!")
}
}
解决方法
简短的回答,Environment
需要 @
,它是一个包装器。您尝试做的不是对 Environment
https://developer.apple.com/documentation/swiftui/environment
长答案,
您还没有提供最小可重复产品,但这是我所看到的
let viewContextValue = Environment(\.managedObjectContext).wrappedValue
它不起作用,因为如您所知,此时 @Environment
不可用,否则您将只使用 viewContext
。
let controller = ContentViewController(managedObjectContext: viewContextValue)
我明白你想在这里做什么,但如上所述,@Environment
只是在 init
self._controller = StateObject(wrappedValue: controller)
虽然表面上“有效”,但它有点违背了 StateObject
SwiftUI 可能随时创建或重新创建视图,所以这很重要
使用给定的一组输入初始化视图总是会导致
相同的观点。因此,创建观察对象是不安全的
在一个视图里面。相反,SwiftUI 为
这个目的。您可以安全地在视图中创建一个 Book 实例
大大地:
@StateObject var book = Book()
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
根据我的经验,SwiftUI 中的自定义 init
不能提供可靠的体验。我尽量远离他们。如果您必须对 init
进行自定义工作,请在 class
中将其作为 ViewModel
/ViewController
进行,这也是 ObservableObject
和 View
不应该做任何工作。
如果您想要替代方法,请参阅this SO question
你只需要
let persistenceController = PersistenceController.shared
在您的 ContentViewController
中并像这样初始化您的 StateObject
。
@StateObject private var controller: ContentViewController = ContentViewController()
这是一个示例,其中我使用了 FetchedResultsController
它有部分
import SwiftUI
import CoreData
class TaskListViewModel: ObservableObject {
let persistenceController = PersistenceController.previewAware()
@Published var fetchedResultsController: NSFetchedResultsController<Task>?
init() {
setupController()
}
func setupController() {
do{
fetchedResultsController = try retrieveFetchedController(sortDescriptors: nil,predicate: nil,sectionNameKeyPath: #keyPath(Task.isComplete))
}catch{
print(error)
}
}
func deleteObject(object: Task) {
persistenceController.container.viewContext.delete(object)
save()
}
func save() {
do {
if persistenceController.container.viewContext.hasChanges{
try persistenceController.container.viewContext.save()
objectWillChange.send()
}else{
}
} catch {
print(error)
}
}
}
//MARK: FetchedResultsController setup
extension TaskListViewModel{
func retrieveFetchedController(sortDescriptors: [NSSortDescriptor]?,predicate: NSPredicate?,sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
return try initFetchedResultsController(sortDescriptors: sortDescriptors,predicate: predicate,sectionNameKeyPath: sectionNameKeyPath)
}
private func initFetchedResultsController(sortDescriptors: [NSSortDescriptor]?,sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
fetchedResultsController = getFetchedResultsController(sortDescriptors: sortDescriptors,sectionNameKeyPath: sectionNameKeyPath)
//fetchedResultsController!.delegate = self
do {
try fetchedResultsController!.performFetch()
return fetchedResultsController!
} catch {
print( error)
throw error
}
}
func getFetchedResultsController(sortDescriptors: [NSSortDescriptor]?,sectionNameKeyPath: String) -> NSFetchedResultsController<Task> {
return NSFetchedResultsController(fetchRequest: getEntityFetchRequest(sortDescriptors: sortDescriptors,predicate: predicate),managedObjectContext: persistenceController.container.viewContext,sectionNameKeyPath: sectionNameKeyPath,cacheName: nil)
}
private func getEntityFetchRequest(sortDescriptors: [NSSortDescriptor]?,predicate: NSPredicate?) -> NSFetchRequest<Task>
{
let fetchRequest: NSFetchRequest<Task> = Task.fetchRequest()
fetchRequest.includesPendingChanges = false
fetchRequest.fetchBatchSize = 20
if sortDescriptors != nil{
fetchRequest.sortDescriptors = sortDescriptors
}else{
fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Task.dateAdded),ascending: false)]
}
if predicate != nil{
fetchRequest.predicate = predicate
}
return fetchRequest
}
}
struct TaskListView: View {
@StateObject var vm: TaskListViewModel = TaskListViewModel()
@State var taskToEdit: Task?
var body: some View {
if vm.fetchedResultsController?.sections != nil{
List{
ForEach(0..<vm.fetchedResultsController!.sections!.count){idx in
let section = vm.fetchedResultsController!.sections![idx]
TaskListSectionView(objects: section.objects as? [Task] ?? [],taskToEdit: $taskToEdit,sectionName: section.name).environmentObject(vm)
}
}.sheet(item: $taskToEdit,onDismiss: {
vm.save()
}){editingTask in
TaskEditView(task: editingTask)
}
}else{
Image(systemName: "empty")
}
}
}
struct TaskEditView: View {
@ObservedObject var task: Task
var body: some View {
TextField("name",text: $task.name.bound)
}
}
struct TaskListSectionView: View {
@EnvironmentObject var vm: TaskListViewModel
let objects: [Task]
@State var deleteAlert: Alert = Alert(title: Text("test"))
@State var presentAlert: Bool = false
@Binding var taskToEdit: Task?
@State var isExpanded: Bool = true
var sectionName: String
var body: some View {
Section(header: Text(sectionName),content: {
ForEach(objects,id: \.self){obj in
let task = obj as Task
Button(action: {
taskToEdit = task
},label: {
Text(task.name ?? "no name")
})
.listRowBackground(Color(UIColor.systemBackground))
}.onDelete(perform: deleteItems)
})
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
deleteAlert = Alert(title: Text("Sure you want to delete?"),primaryButton: Alert.Button.destructive(Text("yes"),action: {
let objs = offsets.map { objects[$0] }
for obj in objs{
vm.deleteObject(object: obj)
}
//Because the objects in the sections aren't being directly observed
vm.objectWillChange.send()
}),secondaryButton: Alert.Button.cancel())
self.presentAlert = true
}
}
}
struct TaskListView_Previews: PreviewProvider {
static var previews: some View {
TaskListView()
}
}
previewAware()
只是决定传递内置 preview
或 shared
static func previewAware() -> PersistenceController{
if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" {
return PersistenceController.preview
}else{
return PersistenceController.shared
}
}