如何在 SwiftUI 预览中预览核心数据数据
How to preview Core Data data inside SwiftUI Previews
这是我的演示(代码有点多,但我希望有人能看懂)。
我在 Core Data 中有一个名为 Activity
的实体,带有一个字符串字段。为此,我使用此扩展程序在预览中显示数据:
extension Activity {
var _name: String {
name ?? ""
}
static var example: Activity {
let controller = DataController(inMemory: true)
let viewContext = controller.container.viewContext
let activity = Activity(context: viewContext)
activity.name = "running"
return activity
}
}
为了设置核心数据,我使用了一个 DataController
对象:
class DataController: ObservableObject {
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Model")
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { storeDescription, error in
if let _ = error {
fatalError("Fatal error loading store")
}
}
}
static var preview: DataController = {
let dataController = DataController(inMemory: true)
let viewContext = dataController.container.viewContext
do {
try dataController.createSampleData()
} catch {
fatalError("Fatal error creating preview")
}
return dataController
}()
func createSampleData() throws {
let viewContext = container.viewContext
for _ in 1...10 {
let activity = Activity(context: viewContext)
activity.name = "run"
}
try viewContext.save()
}
}
在应用程序文件中,我进行了以下设置:
struct TestApp: App {
@StateObject var dataController: DataController
@Environment(\.managedObjectContext) var managedObjectContext
init() {
let dataController = DataController()
_dataController = StateObject(wrappedValue: dataController)
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
}
在我的 ContentView 中,我显示了来自 Core Data 的这个字符串的列表,它工作正常:
struct ContentView: View {
let activities: FetchRequest<Activity>
init() {
activities = FetchRequest<Activity>(entity: Activity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Activity.name, ascending: false)], predicate: nil)
}
var body: some View {
List {
ForEach(activities.wrappedValue) { activity in
ActivityView(activity: activity)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var dataController = DataController.preview
static var previews: some View {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
.environmentObject(dataController)
}
}
但是在我的 ActivityView 中,我在一个简单的文本字段中显示字符串,预览不起作用。
struct ActivityView: View {
let activity: Activity
init(activity: Activity) {
self.activity = activity
}
var body: some View {
Text(activity._name)
}
}
struct ActivityView_Previews: PreviewProvider {
static var previews: some View {
ActivityView(activity: Activity.example)
}
}
我可以在我的列表中看到字符串“运行”10 次,这是设置的方式,但是在 ActivityView 屏幕中我没有看到预览中显示的任何内容。
不知道为什么会这样,我希望有人知道。
编辑:
我在预览中也试过了,但还是不行。
struct ActivityView_Previews: PreviewProvider {
static var dataController = DataController.preview
static var previews: some View {
ActivityView(activity: Activity(context: dataController.container.viewContext))
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
在 SwiftUI 中,我们使用视图层次结构将丰富的模型类型转换为简单的类型。因此,解决此问题的最佳方法是重新设计 ActivityView
以使用简单类型而不是模型类型,这样就可以在不创建托管对象的情况下进行预览。我建议观看 Structure your app for SwiftUI previews,其中介绍了这项技术并提供了一些其他技术,例如协议和泛型。
顺便说一句,我也注意到了这个问题:
init() {
let dataController = DataController()
_dataController = StateObject(wrappedValue: dataController)
}
StateObject 初始化使用 @autoclosure
,例如
@inlinable public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
这意味着对象init需要在括号内,例如
_dataController = StateObject(wrappedValue: DataController())
这就是防止对象在每次 SwiftUI 重新计算 View
层次结构时一遍又一遍地初始化的原因。
这是我的演示(代码有点多,但我希望有人能看懂)。
我在 Core Data 中有一个名为 Activity
的实体,带有一个字符串字段。为此,我使用此扩展程序在预览中显示数据:
extension Activity {
var _name: String {
name ?? ""
}
static var example: Activity {
let controller = DataController(inMemory: true)
let viewContext = controller.container.viewContext
let activity = Activity(context: viewContext)
activity.name = "running"
return activity
}
}
为了设置核心数据,我使用了一个 DataController
对象:
class DataController: ObservableObject {
let container: NSPersistentCloudKitContainer
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Model")
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { storeDescription, error in
if let _ = error {
fatalError("Fatal error loading store")
}
}
}
static var preview: DataController = {
let dataController = DataController(inMemory: true)
let viewContext = dataController.container.viewContext
do {
try dataController.createSampleData()
} catch {
fatalError("Fatal error creating preview")
}
return dataController
}()
func createSampleData() throws {
let viewContext = container.viewContext
for _ in 1...10 {
let activity = Activity(context: viewContext)
activity.name = "run"
}
try viewContext.save()
}
}
在应用程序文件中,我进行了以下设置:
struct TestApp: App {
@StateObject var dataController: DataController
@Environment(\.managedObjectContext) var managedObjectContext
init() {
let dataController = DataController()
_dataController = StateObject(wrappedValue: dataController)
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
}
在我的 ContentView 中,我显示了来自 Core Data 的这个字符串的列表,它工作正常:
struct ContentView: View {
let activities: FetchRequest<Activity>
init() {
activities = FetchRequest<Activity>(entity: Activity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Activity.name, ascending: false)], predicate: nil)
}
var body: some View {
List {
ForEach(activities.wrappedValue) { activity in
ActivityView(activity: activity)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var dataController = DataController.preview
static var previews: some View {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
.environmentObject(dataController)
}
}
但是在我的 ActivityView 中,我在一个简单的文本字段中显示字符串,预览不起作用。
struct ActivityView: View {
let activity: Activity
init(activity: Activity) {
self.activity = activity
}
var body: some View {
Text(activity._name)
}
}
struct ActivityView_Previews: PreviewProvider {
static var previews: some View {
ActivityView(activity: Activity.example)
}
}
我可以在我的列表中看到字符串“运行”10 次,这是设置的方式,但是在 ActivityView 屏幕中我没有看到预览中显示的任何内容。
不知道为什么会这样,我希望有人知道。
编辑:
我在预览中也试过了,但还是不行。
struct ActivityView_Previews: PreviewProvider {
static var dataController = DataController.preview
static var previews: some View {
ActivityView(activity: Activity(context: dataController.container.viewContext))
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
在 SwiftUI 中,我们使用视图层次结构将丰富的模型类型转换为简单的类型。因此,解决此问题的最佳方法是重新设计 ActivityView
以使用简单类型而不是模型类型,这样就可以在不创建托管对象的情况下进行预览。我建议观看 Structure your app for SwiftUI previews,其中介绍了这项技术并提供了一些其他技术,例如协议和泛型。
顺便说一句,我也注意到了这个问题:
init() {
let dataController = DataController()
_dataController = StateObject(wrappedValue: dataController)
}
StateObject 初始化使用 @autoclosure
,例如
@inlinable public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
这意味着对象init需要在括号内,例如
_dataController = StateObject(wrappedValue: DataController())
这就是防止对象在每次 SwiftUI 重新计算 View
层次结构时一遍又一遍地初始化的原因。