Cómo usar PreviewModifier para crear Previews eficientes

PreviewModifier es un protocolo que permite compartir datos entre previews, eliminando la duplicidad de código. Lo interesante de usar este protocolo es que el sistema de previews cachea los datos utilizados en un PreviewModifier, permitiendo que sean reutilizados en las demás previews que lo implementen. Esto hace que tus previews sean mucho más eficientes.

PreviewModifier es especialmente útil para mockear servicios en vistas que realizan peticiones a una API o acceso a bases de datos a través de dicho servicio. Esto asegura que la preview se cargue mucho más rápido.

App de ejemplo

Para demostrar las capacidades de PreviewModifier, comienza con este simple ejemplo:

import Foundation
@Observable final class SearchManager {
private(set) var results = [String]()
func update(_ results: [String]) {
self.results = results
}
}

SearchManager es una clase que administra una lista de resultados contenida en la variable results y posee la función update para reemplazar la lista de resultados. Además, la clase está marcada como @Observable, lo que notifica a cualquier vista de SwiftUI que use SearchManager sobre los cambios en results.

Si quieres conocer más sobre @Observable, te recomiendo nuestro artículo sobre Observation en SwiftUI.

Para utilizar SearchManager, crea la siguiente vista:

import SwiftUI
// 1
struct ContentView: View {
// 2
@Environment(SearchManager.self) private var searchManager
var body: some View {
// 3
List(searchManager.results, id: \.self) { result in
Text(result)
}
}
}
// 4
#Preview {
ContentView()
.environment(SearchManager())
}
  1. Se define una vista llamada ContentView.
  2. Se crea la variable searchManager para acceder a SearchManager desde el entorno mediante @Environment.
  3. Se muestra una lista de resultados provenientes de SearchManager.
  4. Se muestra una preview de ContentView en Xcode, asignando una instancia de SearchManager al entorno de la vista.

Hasta este punto, la preview de ContentView se muestra en blanco porque no se han agregado resultados con la función update, por lo que la lista no tiene datos para mostrar.

Usando PreviewModifier

Una vez creado el ejemplo de la app, es hora de utilizar PreviewModifier para configurar los datos de SearchManager y usarlos en ContentView. Crea la estructura PreviewSearchManager que implemente el protocolo PreviewModifier:

import SwiftUI
struct PreviewSearchManager: PreviewModifier {
}

El protocolo requiere definir cuál será el tipo de datos a utilizar en las previews a través del alias Context, en este caso SearchManager. Esto permite usar protocolos de una manera genérica. Añade el siguiente código dentro de PreviewSearchManager:

typealias Context = SearchManager

Con esto, ya puedes conformar las dos funciones del protocolo. La primera es makeSharedContext, que sirve para crear el contexto que utilizarán las previews, además de cachear los datos. Esto significa que el contexto se creará una sola vez para todas las previews que usen este modificador. Su firma es la siguiente:

@MainActor static func makeSharedContext() async throws -> Self.Context

La segunda función es body, que aplica el contexto con el parámetro context al contenido de la vista mediante el parámetro content. También puedes aplicar otros modificadores al contenido de la vista. Su firma es la siguiente:

@ViewBuilder @MainActor func body(content: Self.Content, context: Self.Context) -> Self.Body

Debajo del alias Context, añade el siguiente código:

static func makeSharedContext() async throws -> SearchManager {
// 1
let results = ["Swift", "SwiftUI", "UIKit", "Combine", "MapKit"]
// 2
let searchManager = SearchManager()
// 3
searchManager.update(results)
// 4
return searchManager
}
func body(content: Content, context: SearchManager) -> some View {
// 5
content.environment(context)
}
  1. Se define una lista de resultados.
  2. Se crea una instancia de SearchManager.
  3. Se actualizan los resultados de searchManager con los valores predefinidos.
  4. Se retorna la instancia de SearchManager con los valores establecidos.
  5. Se incluye el SearchManager creado previamente en la función makeSharedContext a la vista.

Ahora estás listo para utilizar PreviewSearchManager en la preview de ContentView. Sustituye todo el código de la macro #Preview con:

#Preview(traits: .modifier(PreviewSearchManager())) {
ContentView()
}

La diferencia con el antiguo #Preview es que ahora se utiliza el parámetro traits para aplicar el modificador PreviewSearchManager, y se ha eliminado la asignación del entorno de la vista en ContentView porque ya se está realizando en la función body de PreviewSearchManager.

Vista de SwiftUI con un listado de resultados

Puedes simplificar aún más el uso del modificador dentro del parámetro traits de la preview creando una variable estática en la extensión PreviewTrait. Por ejemplo:

extension PreviewTrait where T == Preview.ViewTraits {
@MainActor static var previewSearchManager: Self = .modifier(PreviewSearchManager())
}

De esta manera, podrás usar tus previews de la siguiente forma:

#Preview(traits: .previewSearchManager) {
ContentView()
}

Si has seguido todas las instrucciones, tu código completo debería verse así:

import SwiftUI
@Observable final class SearchManager {
private(set) var results = [String]()
func update(_ results: [String]) {
self.results = results
}
}
struct ContentView: View {
@Environment(SearchManager.self) private var searchManager
var body: some View {
List(searchManager.results, id: \.self) { result in
Text(result)
}
}
}
#Preview(traits: .previewSearchManager) {
ContentView()
}
struct PreviewSearchManager: PreviewModifier {
typealias Context = SearchManager
static func makeSharedContext() async throws -> SearchManager {
let results = ["Swift", "SwiftUI", "UIKit", "Combine", "MapKit"]
let searchManager = SearchManager()
searchManager.update(results)
return searchManager
}
func body(content: Content, context: SearchManager) -> some View {
content.environment(context)
}
}
extension PreviewTrait where T == Preview.ViewTraits {
@MainActor static var previewSearchManager: Self = .modifier(PreviewSearchManager())
}

PreviewModifier + SwiftData

Otro caso donde PreviewModifier es muy útil es para mockear datos de SwiftData. Mira un ejemplo:

import SwiftData
@Model final class SearchResult {
var text: String
init(text: String) {
self.text = text
}
static var mockData: [SearchResult] = [
SearchResult(text: "Swift"),
SearchResult(text: "SwiftUI"),
SearchResult(text: "Apple"),
]
}

Se define el modelo SearchResult, que representa los resultados de una búsqueda. Contiene una propiedad text y una variable mockData con datos de ejemplo.

import SwiftUI
import SwiftData
// 1
struct MockModelContainer: PreviewModifier {
// 2
typealias Context = ModelContainer
// 3
static func makeSharedContext() async throws -> ModelContainer {
let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: SearchResult.self, configurations: configuration)
SearchResult.mockData.forEach { container.mainContext.insert($0) }
return container
}
// 4
func body(content: Content, context: ModelContainer) -> some View {
content.modelContainer(context)
}
}
// 5
extension PreviewTrait where T == Preview.ViewTraits {
@MainActor static var mockModelContainer: Self = .modifier(MockModelContainer())
}
  1. Se define la estructura MockModelContainer, que conforma al protocolo PreviewModifier para mockear los datos de SwiftData.
  2. Se establece el ModelContainer como el contexto que utilizarán las previews.
  3. Se crea una instancia de ModelContainer con una configuración específica, utilizando la variable mockData de SearchResult para insertar datos en el modelo.
  4. Se aplica el contenedor de SwiftData proveniente del contexto ModelContainer al contenido de la vista.
  5. Se simplifica el uso del modificador en los traits de la preview creando la variable mockModelContainer en la extensión PreviewTrait.
import SwiftUI
import SwiftData
// 1
struct SearchResultsView: View {
@Query private var searchResults: [SearchResult]
var body: some View {
List(searchResults) { result in
Text(result.text)
}
}
}
// 2
#Preview(traits: .mockModelContainer) {
SearchResultsView()
}
  1. Se define la vista SearchResultsView, que muestra una lista de resultados de búsqueda a partir de una consulta al modelo SearchResult.
  2. Se define la preview de SearchResultsView con el mock del contenedor de SwiftData.
Vista de SwiftUI con un listado de resultados provenientes de un Mock de SwiftData

Si quieres conocer más sobre SwiftData te recomiendo nuestra guía esencial para comenzar con SwiftData.

Compatibilidad

PreviewModifier puede utilizarse a partir de las siguientes versiones de los sistemas operativos de Apple:

  • iOS 18.0+.
  • iPadOS 18.0+.
  • Mac Catalyst 13.0+.
  • macOS 15.0+.
  • tvOS 18.0+.
  • visionOS 2.0+.
  • watchOS 11.0+.

Comparte este artículo

Subscríbete a nuestro Newsletter

Mantente al día en el mundo de las aplicaciones móviles con nuestro blog especializado.

Artículos semanales

Todas las semanas artículos nuevos sobre el mundo de las aplicaciones móviles.

No spam

No te enviaremos spam, solo contenido de calidad. Puedes darte de baja cuando quieras.

Contenido de calidad

Nada de contenido generado de manera automática usando ChatGPT.

Recomendaciones

Tips indispensables sobre mejores prácticas y metodologías.

© 2025 AsyncLearn