🦀
[SwiftUI] CoreDataのDataModelをイニシャライザの引数に受け取るViewのCanvas Previewを上手く書く
概要
例えば、以下の様なCore DataのData Modelを想定します。
extension ToDo {
@nonobjc public class func fetchRequest() -> NSFetchRequest<ToDo> {
return NSFetchRequest<ToDo>(entityName: "ToDo")
}
@NSManaged public var id: UUID?
@NSManaged public var title: String?
@NSManaged public var content: String?
}
このData Modelをイニシャライザの引数に取るViewを適当に以下の様に書きます。
import SwiftUI
struct ContentView: View {
var todo: ToDo
var body: some View {
VStack(alignment: .leading) {
Text(todo.title!)
Text(todo.content!)
}
}
}
この時、このContentView
のCanvas Previewを表示するコードをどう書けば上手くいくのか悩みました。
色々ググった結果、以下の記事を見つけました。
解決
前提として、CoreDataのPersistenceController
周りのコードを一つの構造体にまとめておきます。
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentCloudKitContainer
init() {
container = NSPersistentCloudKitContainer(name: "ToDo")
container.loadPersistentStores(completionHandler: { _, error in
if let error = error as NSError? {
print(error.localizedDescription)
}
})
}
static func saveContext() {
let context = Self.shared.container.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
そして、以下の様なCanvas PreviewのWrapperになるコードを書きます。
import CoreData
import SwiftUI
struct PreviewWrapper<Content: View>: View {
let content: (NSManagedObjectContext, ToDo) -> Content
var body: some View {
let context = PersistenceController.shared.container.viewContext
let todo = ToDo(context: context)
todo.id = UUID()
todo.title = "宿題"
todo.content = "算数のドリル10ページ、英単語の書取りの練習100語"
return self.content(context, todo)
}
init(@ViewBuilder content: @escaping (NSManagedObjectContext, ToDo) -> Content) {
self.content = content
}
}
こうすることで、肝心のCanvas Previewのコードが以下の様に書けます。
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
PreviewWrapper { _, todo in
ContentView(todo: todo)
}
}
}
Canvas Previewは以下の様になります。
Listの場合は?
例えば、次の様にイニシャライザに[ToDo]型の引数を取るListを表示したい時はどうでしょうか。
コード内のContentView
は前の例のContentView
をそのまま使用しています。
import SwiftUI
struct ContentListView: View {
var todos: [ToDo]
var body: some View {
List {
ForEach(todos) { todo in
ContentView(todo: todo)
}
}
}
}
この場合は一例として次の様にPreviewのWrapperを作ることで解決できます。
[ToDo]型の配列の長さは適当に10にしています。
struct PreviewListWrapper<Content: View>: View {
let content: (NSManagedObjectContext, [ToDo]) -> Content
var body: some View {
let context = PersistenceController.shared.container.viewContext
let todo = ToDo(context: context)
let todos: [ToDo] = ([Int])(0 ..< 10).map { _ in
todo.id = UUID()
todo.title = "宿題"
todo.content = "算数のドリル10ページ、英単語の書取りの練習100語"
return todo
}
return self.content(context, todos)
}
init(@ViewBuilder content: @escaping (NSManagedObjectContext, [ToDo]) -> Content) {
self.content = content
}
}
Canvas Previewのコード:
struct ContentListView_Previews: PreviewProvider {
static var previews: some View {
PreviewListWrapper { _, todos in
ContentListView(todos: todos)
}
}
}
Canvas Preview:
Discussion