问题描述
在学习SwiftData中,预览存在无法显示的情况,但是模拟器是正常的。
预览代码为:
#Preview {
ProfileView()
}
报错的内容为:
CrashReportError: hackingwithswift crashed due to an uncaught exception
hackingwithswift crashed due to an uncaught exception `NSInvalidArgumentException`. Reason: Cannot insert 'Book' in this managed object context because it is not found in the associated managed object model..
经排查发现问题跟数据持久化和状态管理有关。
需要在SwiftUI的预览中,提供一个有效的ModelContainer,并插入示例数据,以便在预览中可以访问到数据。
#Preview {
// 创建一个 ModelContainer,并插入一些示例数据
let container = try! ModelContainer(for: Book.self)
let exampleBook = Book(title: "示例书籍", isbn: "1234567890", publicationYear: 2024, author: "作者名")
// 插入示例数据
let context = container.mainContext
context.insert(exampleBook)
try? context.save() // 注意错误处理,使用 try? 来避免崩溃
return ProfileView()
.modelContainer(container)
}
预览代码
在部分视图中,可以在预览代码中使用modelContainer(for:),以一种更简洁的方式绑定模型容器来进行预览。
// 模型容器绑定单个类型
ContentView()
.modelContainer(for: Friendface.self)
// 模型容器绑定多个类型
ContentView()
.modelContainer(for: [Friendface.self,Friend.self])
同类问题
modelContainer(for:),这个简洁的方法并不总是奏效。
这一次的问题为,一个展示内容视图预览报错:
import SwiftUI
import SwiftData
struct FriendInfo: View {
@Environment(\.modelContext) var modelContext
var friendInfo: Friendface
var body: some View {
Form {
Section("name") {
Text(friendInfo.name)
}
...
}
}
}
#Preview {
FriendInfo(friendInfo: Friendface(
id: "123",
isActive: true,
name: "John Doe",
age: 30,
email: "john.doe@example.com",
address: "123 Swift Street",
about: "A friendly person.",
registered: Date(),
tags: ["developer", "iOS"],
friends: []
))
.modelContainer(container)
}
内容展示视图FriendInfo在模拟器或者真机的状态下,都是正常访问且无报错的,但是Xcode报错。
在Xcode中,内容列表视图ContentView可以通过预览视图访问到这个内容展示视图FriendInfo。
但是FriendInfo视图下预览就报错,因此推测问题是在预览代码中:
#Preview {
FriendInfo(friendInfo: Friendface(
id: "123",
isActive: true,
name: "John Doe",
age: 30,
email: "john.doe@example.com",
address: "123 Swift Street",
about: "A friendly person.",
registered: Date(),
tags: ["developer", "iOS"],
friends: []
))
.modelContainer(for: Friendface.self)
}
但是预览代码也是正常的生成一个Friendface实例并加载了modelContainer(for:)。
在ContentView视图中,modelContainer(for:)是可以正常预览的。
也没有从Xcode报错中找到问题的原因,只知道可能跟Friendface.init 的初始化有关。但没有定位到具体的报错行。
最后,还是通过上次的解决方案解决的这一问题:
Preview {
// 创建一个 ModelContainer,并插入一些示例数据
let container = try! ModelContainer(for: Friendface.self)
let exampleBook = Friendface(
id: "123",
isActive: true,
name: "John Doe",
age: 30,
email: "john.doe@example.com",
address: "123 Swift Street",
about: "A friendly person.",
registered: Date(),
tags: ["developer", "iOS"],
friends: []
)
// 插入示例数据
let context = container.mainContext
context.insert(exampleBook)
try? context.save() // 注意错误处理,使用 try? 来避免崩溃
return FriendInfo(friendInfo: Friendface(
id: "123",
isActive: true,
name: "John Doe",
age: 30,
email: "john.doe@example.com",
address: "123 Swift Street",
about: "A friendly person.",
registered: Date(),
tags: ["developer", "iOS"],
friends: []
))
.modelContainer(container)
}
使用 ModelContainer 和 modelContainer(_:) 解决了预览报错,显式地为预览提供了 SwiftData 的上下文环境和模型容器。
解决步骤
1、ModelContainer 的显式初始化
通过 ModelContainer(for:) 创建了一个与 Friendface 关联的模型容器。这样,预览可以初始化一个完整的 SwiftData 环境,而不再缺少必需的上下文。
let container = try! ModelContainer(for: Friendface.self)
SwiftData 的 @Model 类型需要在一个 ModelContainer 中管理。如果没有提供容器,SwiftData 无法在预览时管理模型对象,这正是之前的错误原因。
2、向容器插入数据
显式地将数据插入到了容器的上下文中:
let context = container.mainContext
context.insert(exampleBook)
try? context.save()
这一步模拟了 SwiftData 数据存储的行为,使得容器中有实际的数据供视图使用,而不会因为缺少数据导致运行时错误。
3、绑定容器到视图的预览环境
最后,通过 modelContainer(_:) 将模型容器绑定到视图,使视图能够访问和使用这个容器的上下文:
.modelContainer(container)
这一绑定建立了视图与 SwiftData 的连接,从而解决了之前预览环境中缺少上下文的报错问题。
为什么之前的预览代码报错?
SwiftData 缺少上下文: @Model 类型依赖一个有效的 ModelContainer 和上下文。如果没有绑定容器,Friendface 的 @Model 特性在预览中无法正确解析和初始化。
没有显式插入数据: 预览中访问的数据是动态加载的。如果上下文中没有插入数据,视图会尝试从空的数据源中加载,可能会导致崩溃或无法渲染。