DEV Community

GoyesDev
GoyesDev

Posted on

[SUI] Inyectando datos en Preview con PreviewModifier

Al construir una aplicación con base de datos, resulta conveniente inyectar algo de información en el Preview en la base de datos, no de forma persistente sino en memoria.

Para esto podría directamente crear una fábrica para el ModelContainer que solo se compile en DEBUG, como ilustro a continuación:

#if DEBUG
enum ContainerFactory {
  static func create() -> ModelContainer {

    let container = try! ModelContainer(for: Model1.self, Model2.self, Model3.self, configurations: .init(isStoredInMemoryOnly: true))

    let context = container.mainContext

    // Preview seed data
    for i in 0..<Int.random(in: 1..<5) {
      let newItem = Model1(...)
      context.insert(newItem)
    }

    for i in 0..<Int.random(in: 1..<3) {
      let newItem = Model2(...)
      context.insert(newItem)
    }

    for i in 0..<Int.random(in: 1..<4) {
      let newItem = Model3(...)
      context.insert(newItem)
    }

    try? context.save()

    return container
  }
}
#endif
Enter fullscreen mode Exit fullscreen mode

Luego, en el Preview podría hacer uso de la fábrica de este modo:

#Preview {
  let container = ContainerFactory.create()

  return ContentView()
    .modelContainer(container)
}
Enter fullscreen mode Exit fullscreen mode

Aunque la aproximación funciona, SwiftUI también ofrece una estrategia un poco más limpia para hacer la creación e inyección de estas dependencias en el Preview por medio de PreviewModifier. Para ello solo hay que conformar el protocolo en una struct, definir el typealias para Context y luego implementar los métodos makeSharedContext() (donde se construye el contexto) y body(content:context:) (donde se lo inyecta). Por ejemplo:

struct PreviewData: PreviewModifier {
  typealias Context = ModelContainer

  static func makeSharedContext() async throws -> ModelContainer {
    return ContainerFactory.create()
  }

  func body(content: Content, context: Context) -> some View {
    content
      .modelContainer(context)
  }
}
Enter fullscreen mode Exit fullscreen mode

Notar que en el caso anterior, en body(content:context:) estoy inyectando la dependencia ModelContainer por medio de .modelContainer y que el tipo de la variable es Context. No confundir ese Context, que es un associated type de PreviewModifier con ModelContext de SwiftData.

Aunque puede parecer tentador poner todo el código de construcción dentro de makeSharedContext(), recordar que esa no necesariamente es su responsabilidad, y que se puede extraer en una fábrica.

Luego, se puede usar la struct que conforma PreviewModifier en un Preview por medio del trait modifier(_:):

#Preview(traits: .modifier(PreviewData())) {
  ContentView()
}
Enter fullscreen mode Exit fullscreen mode

Bibliografía

Top comments (0)