[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:

좋은 웹페이지 즐겨찾기