[SwiftUI] CoreDataのDataModelをイニシャライザの引数に受け取るViewのCanvas Previewを上手く書く

## 概要

例えば、以下の様なCore DataのData Modelを想定します。

```swift
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を適当に以下の様に書きます。

```swift
import SwiftUI

struct ContentView: View {
    var todo: ToDo
    var body: some View {
        VStack(alignment: .leading) {
            Text(todo.title!)
            Text(todo.content!)
        }
    }
}
```

この時、この`ContentView`のCanvas Previewを表示するコードをどう書けば上手くいくのか悩みました。

色々ググった結果、以下の記事を見つけました。

https://lailo.ch/tips/swiftui-preview-with-coredata

## 解決

前提として、CoreDataの`PersistenceController`周りのコードを一つの構造体にまとめておきます。

```swift
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になるコードを書きます。

```swift
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のコードが以下の様に書けます。

```swift
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        PreviewWrapper { _, todo in
            ContentView(todo: todo)
        }
    }
}
```

Canvas Previewは以下の様になります。

![スクリーンショット 2021-02-15 7.30.35（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226452/a50430c3-e542-dfb8-6982-c2e8e172f9fb.png)


## Listの場合は？

例えば、次の様にイニシャライザに[ToDo]型の引数を取るListを表示したい時はどうでしょうか。
コード内の`ContentView`は前の例の`ContentView`をそのまま使用しています。

```swift
import SwiftUI

struct ContentListView: View {
    
    var todos: [ToDo]
    
    var body: some View {
        List {
            ForEach(todos) { todo in
                ContentView(todo: todo)
            }
        }
    }
}
```

この場合は一例として次の様にPreviewのWrapperを作ることで解決できます。
[ToDo]型の配列の長さは適当に10にしています。

```swift
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のコード:

```swift
struct ContentListView_Previews: PreviewProvider {
    static var previews: some View {
        PreviewListWrapper { _, todos in
            ContentListView(todos: todos)
        }
    }
}
```

Canvas Preview:

![スクリーンショット 2021-02-15 7.40.32（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/226452/f2e7ca7e-72f7-54d7-ab80-f72a4d49839e.png)