Environment 属性 wrapper throws +entityForName: nil 不是用于搜索实体名称的合法 NSPersistentStoreCoordinator
Environment property wrapper throws +entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name
为什么 Environment(\.managedObjectContext).wrappedValue
上的 entityForName 总是 nil?
我收到此错误 +entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Project'
使用 @Environment(\.managedObjectContext) var viewContext
我没有收到此错误。
但是我需要用我的控制器初始化视图,需要传递 NSManagedObjectContext
。
有人可以帮助理解为什么这两行 return 不是同一个对象吗?还是一样?
@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 might create or recreate a view at any time, so it’s important
that initializing a view with a given set of inputs always results in
the same view. As a result, it’s unsafe to create an observed object
inside a view. Instead, SwiftUI provides the StateObject attribute for
this purpose. You can safely create a Book instance inside a view this
way:
@StateObject var book = Book()
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
根据我的经验,SwiftUI 中的自定义 init
无法提供可靠的体验。我尽量远离他们。如果您必须在 init
上进行自定义工作,请在 class
中将其作为 ViewModel
/ViewController
,这也是 ObservableObject
和 View
不应该做任何工作。
如果您想要替代您想要执行的操作,请参阅
你只需要
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]?, predicate: NSPredicate?, sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
fetchedResultsController = getFetchedResultsController(sortDescriptors: sortDescriptors, predicate: predicate, sectionNameKeyPath: sectionNameKeyPath)
//fetchedResultsController!.delegate = self
do {
try fetchedResultsController!.performFetch()
return fetchedResultsController!
} catch {
print( error)
throw error
}
}
func getFetchedResultsController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, 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[[=15=]] }
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
}
}
为什么 Environment(\.managedObjectContext).wrappedValue
上的 entityForName 总是 nil?
我收到此错误 +entityForName: nil is not a legal NSPersistentStoreCoordinator for searching for entity name 'Project'
使用 @Environment(\.managedObjectContext) var viewContext
我没有收到此错误。
但是我需要用我的控制器初始化视图,需要传递 NSManagedObjectContext
。
有人可以帮助理解为什么这两行 return 不是同一个对象吗?还是一样?
@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 might create or recreate a view at any time, so it’s important that initializing a view with a given set of inputs always results in the same view. As a result, it’s unsafe to create an observed object inside a view. Instead, SwiftUI provides the StateObject attribute for this purpose. You can safely create a Book instance inside a view this way:
@StateObject var book = Book()
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
根据我的经验,SwiftUI 中的自定义 init
无法提供可靠的体验。我尽量远离他们。如果您必须在 init
上进行自定义工作,请在 class
中将其作为 ViewModel
/ViewController
,这也是 ObservableObject
和 View
不应该做任何工作。
如果您想要替代您想要执行的操作,请参阅
你只需要
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]?, predicate: NSPredicate?, sectionNameKeyPath: String) throws -> NSFetchedResultsController<Task> {
fetchedResultsController = getFetchedResultsController(sortDescriptors: sortDescriptors, predicate: predicate, sectionNameKeyPath: sectionNameKeyPath)
//fetchedResultsController!.delegate = self
do {
try fetchedResultsController!.performFetch()
return fetchedResultsController!
} catch {
print( error)
throw error
}
}
func getFetchedResultsController(sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?, 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[[=15=]] }
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
}
}