Crea una app con base de datos usando SwiftData y SwiftUI
Crea una app con base de datos usando SwiftData y SwiftUI

SwiftData en SwiftUI

SwiftData es el nuevo framework de Apple para persistir información de nuestra app. Este framework nos permite crear una base de datos dentro de nuestra app y poder guardar, hacer queries, actualizar y borrar información. Está completamente pensado para SwiftUI pero se puede desacoplar de la UI

SwiftBeta

Tabla de contenido


👇 SÍGUEME PARA APRENDER SWIFTUI, SWIFT, XCODE, etc 👇
Aprende a usar SwiftData en SwiftUI en Español
Aprende a usar SwiftData en SwiftUI en Español

Hoy en SwiftBeta vamos a explorar el nuevo framework de Apple que nos permite persistir los datos de nuestra aplicación, este framework se llama SwiftData y nos permite tener una base de datos dentro de nuestra aplicación.

Este nuevo framework ha sido pensado para hacerlo lo más fácil posible en nuestras aplicaciones creadas con SwiftUI, y en el video de hoy vamos a ver que con muy pocas líneas de código podemos crear una base de datos muy sencilla y potente.

En el video de hoy vamos a crear una aplicación que me va a servir todos los videos que quiero priorizar para el canal de Youtube, va a ser como un backlog de videos que quiero crear para aprender o compartir con vosotros. De esta manera tendré un listado priorizado. También, crearemos una relación por cada video, pudiendo conectar con otro modelo llamado Metadatos, estos metadados serán muy sencillos y solo tendran una propiedad para indicar que un video lo marcamos como favorito. También te enseñaré el modo debug para que veas dónde se encuentra la base de datos para que la puedas inspeccionar y ver todo su contenido. Y por último, aprendemos a desacoplar SwiftData de las vistas en SwiftUI y para ello moveremos toda la responsabilidad a un ViewModel.
El video va a ser una introducción a SwiftData pero vas a ver que es muy muy completo.

A medida que vayamos añadiendo videos, irán apareciendo en nuestro listado. Y si pulsamos en uno de ellos lo podremos modificar, viendo los cambios reflejados automáticamente cuando volvamos a la vista principal de nuestra aplicación.

Va a ser una app muy sencilla, pero vamos a explorar todos los aspectos clave de SwiftData. También comentarte que es muy reciente este framework y puede que Apple realice algún update hasta su fecha final de lanzamiento, si quieres ver los updates dejaré un link en la descripción del video

Componentes clave para persistir información usando SwiftData

Lo primero de todo, antes de crear nuestro proyecto. Te voy a dar 3 conceptos clave que vamos a ver en el video, y que van a ser las bases para poder persistir en nuestra base de datos.

Modelo, va a ser el tipo (o los tipos) que queremos almacenar en nuestra base de datos. En nuestro caso vamos a crear 2 tipos, un tipo llamado Video, y otro tipo llamado Metadata. Video tendrá una relación con Metadata. Para crear nuestros modelos usaremos diferentes macros, por ejemplo Model, Attributes, Relationship y Transient. Más adelante los explicaré uno por uno (vas a ver que son muy sencillos).

Container, es el recipiente donde almacenaremos los modelos que hemos especificado en el anterior paso. Si no hay container no hay base de datos. Al crear un container podemos especificar que sea dentro de un fichero o en memoria, la ruta del fichero de la base de datos, etc. también podemos pasarle las migraciones de nuestra base de datos (esto no lo veremos en el video de hoy). Y una vez tenemos el container creado, ya podemos utilizar la última parte

Contexto, el contexto es lo que nos permite interacturar con nuestra base de datos. Es decir, podemos hacer peticiones para obtener la información que tiene almacenada el container, podemos guardar los modelos, borrárlos, etc. El contexto lo usamos con las instancias de los modelos que hemos comentado en el primer punto, mientras que los tipos en sí, los schemas son para decirle al container qué queremos almacenar.

Una vez tenemos claro estos 3 componentes, vamos a crear el proyecto en Xcode.

Antes de continuar, si quieres apoyar el contenido que subo cada semana, suscríbete. De esta manera seguiré publicando contenido completamente grautito en Youtube.

Creamos el proyecto en Xcode

Al crear nuestro proyecto en la Beta de Xcode 15 tenemos que seguir los pasos de siempre, en Interface seleccionar SwiftUI. Nos fijamos y aquí aparece una opción de Data Store, podemos seleccionar SwiftData o CoreData, pero nos vamos a quedar con la opción de None ya que no queremos que Xcode nos genere código por defecto. Todo lo que vamos a crear va a ser desde cero y explicado paso por paso.

Una vez creado el proyecto, lo primero que vamos hacer es crear el esquema que vamos a tener. Para hacerlo creamos una carpeta llamada Model y dentro de ella creamos el primer fichero llamado Video. Este fichero va a ser el modelo, que se guardará en nuestra base de datos.

Una vez creado, vamos a crear usar class y vamos a crear las siguiente propiedades:

class Video {
    var id: UUID
    var title: String

    var numberOfLikes: Int { title.count }
}

Al hacerlo, obtenemos un error. Vamos a crear el inicializador de nuestra class Video, y para ello vamos a usar el potencial del autocompletado de Xcode. Ponemos init y automáticamente vemos que nos crea el inicializador con todas las propiedades:

class Video {
    var id: UUID
    var title: String

    var numberOfLikes: Int { title.count }
    
    init(id: UUID, title: String) {
        self.id = id
        self.title = title
    }
}

Macro @Model

Una vez tenemos el modelo creado, vamos a usar el potencial de SwiftData. Este es el modelo que queremos almacenar en nuestra base de datos. Para proporcionarle esta implementación, tan solo debemos usar la macro @Model. Las macros es una nueva funcionalidad del lenguaje de programación Swift 5.9, lo exploraremos en detalle en un futuro video del canal, pero ahora solo debes saber que al usar esta macro Model, indicamos que este modelo se puede persistir en nuestra base de datos.

@Model
class Video {
    var id: UUID
    var title: String

    var numberOfLikes: Int { title.count }
    
    init(id: UUID, title: String) {
        self.id = id
        self.title = title
    }
}

Usando la macro Model, indicamos que cualquier cambio en Video se ve reflejado automáticamente en la base de datos, pudiendo trabajar siempre con los datos más actualizados.

Macro @Attribute

En muchas aplicaciones ya sería suficiente usar la macro @Model, pero en otras quizás necesitamos customizar un poco nuestro modelo. Es decir, quizás necesitas que ciertas propiedades tengan un valor único, que esté encriptado, etc. Para especificarlo, tenemos otra macro llamada @Attribute que podemos usar directamente en nuestra propiedades, es decir, si queremos indicar que la id debe ser única en nuestra base de datos, podemos usar @Attribute(.unique), puedes ver un listado de valores si dentro de la macro @Attribute añades un .

Nuestro modelo pasa a tener el siguiente código:

@Model
class Video {
    @Attribute(.unique) var id: UUID
    var title: String

    var numberOfLikes: Int { title.count }
    
    init(id: UUID, title: String) {
        self.id = id
        self.title = title
    }
}

Macro @Transient

Hasta ahora, todas las propiedades de nuestro tipo Video se van a persistir en la base de datos, pero quizás no te interesa que todas las propiedades de Video acaben en la base de datos. Esto puede ser por muchos motivos, desde optimización de recursos, simplicidad, etc. Para indicar que no quieres almacenar un valor en la base de datos, puedes usar la macro @Transient. En nuestro caso lo vamos a usar para indicar que nuestra propiedad computada numberOfLikes, no se persista su valor. Y para hacerlo, es exactamente igual que la macro Attribute:

@Model
class Video {
    @Attribute(.unique) var id: UUID
    var title: String

    @Transient var numberOfLikes: Int { title.count }
    
    init(id: UUID, title: String) {
        self.id = id
        self.title = title
    }
}

De momento, hemos visto a cómo indicar que un tipo (junto con todas sus propiedades) se pueda almacenar en una base de datos. Hemos visto la macro Model, Attribute y Transient. Pero, ¿cómo podemos crear relaciones dentro de nuestra base de datos? Este concepto es muy importante para crear referencias y conectar distintos modelos entre sí.

Creamos 2do modelo Metadata

Para crear referencias vamos a necesitar otro modelo. El modelo que vamos a crear solo va a contener una propiedad, y nos va a servir para persistir si un video lo hemos marcado como favorito o no. Así que justo debajo, vamos a crear el modelo Metadata:

@Model
class Metadata {
    var isFavorite: Bool
    
    init(isFavorite: Bool) {
        self.isFavorite = isFavorite
    }
}

Este modelo por si solo no nos está dando información, es por eso que vamos a crear una referencia desde nuestro modelo Video. De esta manera podremos ver si un video es favorito o no.

Macro @Relationship

Para hacerlo, primero vamos a nuestro modelo Video y creamos la propiedad metadata:

@Model
class Video {
    @Attribute(.unique) var id: UUID
    var title: String

    @Transient var numberOfLikes: Int { title.count }
    
    var metadata: Metadata
    
    init(id: UUID, title: String, metadata: Metadata) {
        self.id = id
        self.title = title
        self.metadata = metadata
    }
}

Fíjate que también he actualizado el init, añadiendo como parámetro metadata. Una vez hemos añadido la propiedad, ahora debemos indicar que queremos crear una relación de video a metadata, y para hacerlo usamos la macro @Relationship:

@Model
class Video {
    @Attribute(.unique) var id: UUID
    var title: String

    @Transient var numberOfLikes: Int { title.count }
    
    @Relationship var metadata: Metadata
    
    init(id: UUID, title: String, metadata: Metadata) {
        self.id = id
        self.title = title
        self.metadata = metadata
    }
}

Igual que con la macro Attribute, también podemos customizar ciertos comportamientos. En este caso, queremos que cuando nuestro video se borre de la base de datos, también se borre la relación de la propiedad Metadata. No tendría sentido tener en nuestra base de datos un Metadata sin un video asociado, ya que no nos estaría aportando nada de valor, solo ocupando espacio en la base de datos.

Para indicar que queremos borrar el modelo metadata cuando el video se borre de la base de datos, lo que hacemos es pasarle el valor .cascade. Si te fijas, si pones un . puedes ver varias opciones que admite esta macro, pero de momento nos vamos a quedar con la opción .cascade.

¡Ya tenemos la primera parte! Una vez sabemos los datos que queremos almacenar, es decir, el tipo Video y Metadata, ¿qué crees que necesitamos ahora? Ahora necesitamos crear un container para indicar a nuestra base de datos que puede almacenar los tipos Video y Metadata.

ViewModifier modelContainer

Ahora vamos a crear nuestro container, hay varias maneras de hacerlo pero ahora usaremos la que es 100% SwiftUI, y esta es usando el viewModifier modelContainer.

Vamos a usar este modificador en el tipo de entrada de nuestra app, el tipo que contiene @main, en mi caso se llamada SwiftBetaVideosApp

@main
struct SwiftBetaVideosApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: [Video.self,
                              Metadata.self])
    }
}

Hay varios modificadores, donde solo le pasamos un modelo, array de modelos, configuración indicando si queremos crear la base de datos en memoria, soportar la funcionalidad de deshacer cambios, autoguardado, etc. En nuestro caso usamos este de aquí.

Ahora mismo ya tenemos los modelos que queremos almacenar en nuestro container, y el container. Vamos con la siguiente parte, el contexto. La parte que nos va a permitir interactuar con nuestra base de datos.

Para tener acceso al contexto, en SwiftUI podemos usar directamente el property wrapper @Environment. Así que desde ContentView, añadimos la siguiente propiedad:

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
...
}

Property Wrapper Query

Ya tenemos toda las partes de persistencia necesarias. Ahora vamos a crear una vista que nos permita añadir videos rápidamente. Lo primero de todo, vamos a usar el property wrapper @Query, este property wrapper es nuevo, y nos permite hacer una query de todos los videos que tenemos almacenados en nuestra base de datos. Fíjate que podemos pasarle parámetros, en este caso le indicamos que queremos ordenar los videos por la propiedad title. Deberíamos tener el siguiente código:

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query(sort: \.title) var videos: [Video]
    ...
}

En este paso, voy a eliminar la preview, ya que siempre que intento utilizar me da un crash en el canvas. Quizás es por algún error con la beta de Xcode 15.

Como te decía, ahora vamos a probar que todas las partes de nuestra base de datos funcionan, lo primero de todo, voy a crear un NavigationStack, y dentro de él voy a usar un ForEach para mostrar cada video almacenado en mi base de datos:

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query(sort: \.title) var videos: [Video]

    var body: some View {
        NavigationStack {
            List {
                ForEach(videos) { video in
                    NavigationLink(value: video) {
                        HStack {
                            Text(video.title)
                            if video.metadata.isFavorite {
                                Image(systemName: "star.fill")
                                    .foregroundColor(.yellow)
                            }
                        }
                    }
                }
            }
            .navigationTitle("SwiftBeta Videos")
        }
    }
}

Model Context

Lo siguiente que voy hacer es añadir un button a mi toolbar para que al pulsarlo, se cree un video con una valor estático, tan sencillo como el siguiente código:

.toolbar {
    ToolbarItem {
        Button(action: {
            
        }, label: {
            Label("Add Item", systemImage: "plus")
        })
    }
}

Cuando pulsemo el Button que queremos que pase? queremos crear una instancia de Video, y queremos usar la operación insert para añadir este modelo a nuestra base de datos:

ToolbarItem {
    Button(action: {
        withAnimation {
            let newVideo = Video(id: UUID(), title: "SwiftBeta", metadata: .init(isFavorite: true))
            modelContext.insert(newVideo)
        }
    }, label: {
        Label("Add Item", systemImage: "plus")
    })
}

Al pulsar este Button, el modelContext inserta un nuevo modelo en la base de datos. Y no haría falta llamar al modelContext save para persistirlos, ya que en SwiftData se va guardando la información en la base de datos triggereados por eventos que pasan en la UI y también por los inputs del user (no tenemos que preocuparnos explicitamente de llamar a modelContext.save() ya que SwiftData lo hace por nosotros)

Una vez usado el contexto para añadir el nuevo tipo, ya es hora de probar nuestra aplicación. Así que vamos a compilar a ver qué ocurre. Al pulsar en el Button que acabamos de añadir de la toolbar, vemos como se van mostrando videos con el texto que hemos añadido. Si cerramos nuestra app y volvemos a compilar, vemos que los resultado vuelven a aparecer. Demostrando que nuestra base de datos ha sido creada correctamente, pudiendo almacenar y obtener los modelos que le hemos especificado.

Localización de la base de datos de mi app

Como detalle curioso, antes de continuar quiero enseñarte un pequeño truco para saber exactamente dónde está alojada la base de datos. Para hacerlo, nos vamos al edit schema de nuestra app, aquí buscamos la sección Run, y dentro de la sección Rub escogemos Arguments. Aquí podemos indicarle a Xcode que nos muestre información extra de qué está ocurriendo en nuestra base de datos en todo momento. Esta información aparecerá por consola, será una información extra que le estamos solicitando a Xcode. Dentro de Arguments Passed On Launch, creamos un nuevo argumento llamado SQLDebug

-com.apple.CoreData.SQLDebug 1

Y una vez añadido, para ver esta nueva información, lo que hacemos es volver a compilar. Fíjate que en la consola aparece mucha más información que antes, y justo nosotros vamos hacer scroll al inicio, de esta manera podemos ver la ruta de dónde se ha creado la base de datos. Si cogemos esta ruta podemos navegar a ella rápidamente usando el Finder. Copiamos la URL, y lo siguiente que hacemos es ir al finder GO -> GO TO FOLDER y allí pegamos la ruta (sin el default.store) y pulsamos enter.

Aquí verás un archivo llamado default.store. Esta es la base de datos de tu app. Que sepas que puedes customizar el nombre de tu base de datos, y el path. Pero esto es más avanzado para el video de hoy. Pero aún así, te voy a enseñar un recurso para que puedas ver de un simple vistazo el contenido de tu base de datos, hay diferentes apps para abrir el fichero default.store, pero vamos a visitar esta web https://sqliteviewer.app/

Una vez allí, arrastramos la base de datos y podemos observar todo el contenido de nuestra base de datos. No vamos a la tabla de videos

Borrar todos los Videos de la base de datos

Una vez explicado este paso, vamos a continuar. Ahora me gustaría poder borrar todos los videos de mi base de datos. Y para hacerlo voy a añadir otro Button a mi toolbar, y en lugar de llamar al insert de mi contexto, voy a llamar a la operación delete y en este caso voy a llamar también al save para que se guarden los cambios inmediatamente en nuestra base de datos (verás más adelante, que si no añado el save obtengo un crash en mi aplicación).

ToolbarItem {
    Button(action: {
        withAnimation {
            videos.forEach {
                modelContext.delete($0)
            }
            try? modelContext.save()
        }
    }, label: {
        Label("Remove All", systemImage: "trash")
    })
}

Si ahora compilamos, podemos ver que funciona perfectamente. Se eliminan todos los videos que hemos introducido. Al borrar todos los videos, también se borran las relaciones y los modelos asociados de Metadata (recuerda que hemos creado la relación de video con metadata con el valor de cascada), de esta manera no quedan tipos huérfanos de metadata (ya que no nos aportan ningún tipo de valor).

Ahora lo que vamos hacer es poder modificar el título de cada video, pero lo vamos hacer dentro de una nueva vista, para que veas lo sencillo que es actualizar un valor de nuestro Array de videos, en otra vista. A parte del título también podremos modificar si el video es favorito o no.

Creamos una nueva vista en SwiftUI llamada UpdateView:

import Foundation

struct UpdateView: View {
    @Bindable var video: Video
    
    var body: some View {
        Form {
            TextField("Edita el título del video", text: $video.title)
            Toggle("Video Favorito", isOn: $video.metadata.isFavorite)
        }
    }
}

Esta vista tiene una única dependencia, y es el video que le pasaremos desde ContentView. Para poder propagar los cambios que ocurran dentro de esta View, lo que debemos usar es el Property Wrapper llamado Bindable. De esta manera cualquier cambio que hagamos en Update View, se verá reflejado y actualizado en nuestra base de datos.

Ahora, el siguiente paso es poder abrir esta vista UpdateView cuando pulsemos en una de las celdas de Video. Para poder navegar, tan solo debemos usar el ViewModifier navigationDestination. Así que justo debajo del ViewModifier navigationTitle, añadimos el siguiente código:

.navigationDestination(for: Video.self) { video in
    UpdateView(video: video)
}

Si ahora compilamos y pulsamos en una celda, vemos que navegamos directamente a la vista UpdateView. Ahí podemos modificar el valor del título, y también modificar el valor del Toggle. Al hacerlo, vemos que aparecen mensajes por consola indicando de un cambio en la base de datos de nuestro modelo. Y si volvemos atrás vemos que se muestra el listado actualizado, y como estaba ordenado por orden alfábetico, depende del nombre que hayas añadido, aparecerá en otra posición. También podemos ver que al desmarcar el video como favorito ya no aparece la imagen de fav.

Y por último, para estar 100% seguros que funciona perfectamente, vamos a cerrar nuestra app y volver a compilar. Si lo hacemos observamos que los datos se han persistido correctamente! 🙌

Modificar Property Wrapper Query

Antes de continuar, hace unos minutos usábamos el property wrapper Query para ordenar el listado de videos. Pero podemos hacer mucho más, podemos crear una Query que nos filtre videos. Vamos a verlo rápidamente.

En lugar de usar @Query(sort:), vamos a usar @Query(filter:) y en este caso le pasamos un predicate. #Predicate es una nueva macro que podemos usar en Swift 5.9, y es tan fácil como crear el siguiente código:

struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    //@Query(sort: \.title) var videos: [Video]
    @Query(filter: #Predicate {
        $0.title == "SwiftBeta"
    }) var videos: [Video]
...
}

Si ahora compilamos, solo se mostrarán en nuestro listado todos los títulos que sean SwiftBeta, y todos los demás serán filtrados.

Esta muy bien usar todas las ventajas que nos ofrece SwiftData con SwiftUI, pero qué ocurre si lo queremos desacoplar? cómo podemos crear el container, y contexto desde un ViewModel por ejemplo? Vamos a verlo

Desacoplar SwiftData y SwiftUI de la capa de Vista

La última parte del video va a ser desacoplar SwiftData de SwiftUI, vamos a crear un ViewModel con varios métodos que se van a encargar de:

  • Obtener todos los videos
  • Insertar un video
  • Borrar todos los videos

Creamos ViewModel

Lo primero de todo, creamos un fichero llamado ViewModel que tenga un container, y que podamos acceder al contexto del container:

import SwiftData

@Observable
final class ViewModel: ObservableObject {
    var videos: [Video] = []
    let container = try! ModelContainer(for: [Video.self,
                                              Metadata.self])
    
    @MainActor
    var modelContext: ModelContext {
        container.mainContext
    }

También he añadido una propiedad para poder acceder al Array de videos. Estos videos se mostrarán por la UI, lo mismo que hemos hecho con el property wrapper Query, pero esta vez más manual.

Lo siguiente que vamos hacer es crear los 3 métodos, primero el getVideos, creando nosotros un predicate e indicando el tipo de orden en el que queremos recibir los valores.

@MainActor
func getVideos() {
    let fetchDescriptor = FetchDescriptor<Video>(predicate: nil,
                                                 sortBy: [SortDescriptor<Video>(\.title)])
    videos = try! modelContext.fetch(fetchDescriptor)
    print(videos)
}

El segundo método va a ser el de insertar:

@MainActor
func insert(video: Video) {
    modelContext.insert(video)
    videos = []
    getVideos()
}

Y el tercer y último método va a ser el de borrar todos los videos:

@MainActor
func deleteAllVideos() {
    videos.forEach {
        modelContext.delete($0)
    }
    videos = []
    getVideos()
}

environmentObject del ViewModel

Una vez hemos creado toda la implementación del ViewModel, ahora toca limpiar la UI. Primero nos vamos a SwiftBetaVideosApp y eliminamos el viewModifier modelContainer (ya que esto lo hacemos desde nuestro ViewModel).

Y una vez borrado vamos a usar el ViewModifier environmentObject para inyectar una instancia de nuestro ViewModel, de esta manera podremos acceder desde el resto de vistas a la instancia viewModel.

@main
struct SwiftBetaVideosApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(ViewModel())
        }
    }
}

A continuación, vamos a ContentView y comentamos todas las propiedades, una vez hecho añadimos la siguiente propiedad para poder acceder a la instancia de ViewModel que acabamos de inyectar desde SwiftBetaVideosApp:

struct ContentView: View {
    @Environment(ViewModel.self) var viewModel
    //@Environment(\.modelContext) var modelContext
    //@Query(sort: \.title) var videos: [Video]
    ...
}

Ahora ya podemos sustituir las referencias que teníamos de videos, y usar la propiedad videos de nuestro ViewModel:

struct ContentView: View {
    @Environment(ViewModel.self) var viewModel
    //@Environment(\.modelContext) var modelContext
    //@Query(sort: \.title) var videos: [Video]

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.videos) { video in

Si ahora intentamos compilar obtenemos varios errores, vamos a usar las referencias del ViewModel para modelContext.

.toolbar {
    ToolbarItem {
        Button(action: {
            withAnimation {
                let newVideo = Video(id: UUID(), title: "SwiftBeta", metadata: .init(isFavorite: true))
                viewModel.insert(video: newVideo)
            }
        }, label: {
            Label("Add Item", systemImage: "plus")
        })
    }
    ToolbarItem {
        Button(action: {
            withAnimation {
                viewModel.deleteAllVideos()
            }
        }, label: {
            Label("Remove All", systemImage: "trash")
        })
    }
}

Por último, cuando la vista ContentView aparezca, vamos a llamar al método getVideos de nuestro ViewModel, de esta manera se cargaran los últimos videos:

        }
        .onAppear {
            viewModel.getVideos()
        }
    }
}

Ya lo tenemos listo, hemos eliminado todo rastro de SwiftData de la UI y lo hemos desacoplado. Ahora todo el comportamiento de la base de datos está en el ViewModel, se puede optimizar mucho este código pero como demostración e introducción a SwiftData es más que suficiente.

Vamos a compilar para ver que todo funciona correctamente. Al añadir elementos y borrarlos se persistent entre ejecuciones de nuestra app. Pero he detectado un crash, el crash de la app se produce cuando añadimos varios elementos y rápidamente damos al button de eliminar todos los videos de la base de datos. Para solucionarlo podemos fixearlo añadiendo un modelContext save de la siguiente manera:

@MainActor
func deleteAllVideos() {
    videos.forEach {
        modelContext.delete($0)
    }
    try! modelContext.save()
    videos = []
    getVideos()
}

Si ahora compilamos y volvemos a probarlo, vemos que todo funciona como esperamos.

Conclusión

En el video de hoy hemos aprendido a cómo usar el nuevo framework de persistencia de datos de SwiftData. Hemos aprendido a como SwiftData se acopla mucho a la vista de nuestra app en SwiftUI, pero que lo podemos solucionar extrayéndolo en una clase aparte, de esta manera logramos desacoplar la lógica de nuestra app.

El ejemplo que hemos visto nos ha permitido tener nuestra primera toma de contacto con SwiftData y crear una app que persiste información, en este caso el modelo Video y el modelo Metadata, entre ejecuciones de nuestra app, pudiendo recuperar la información.

Y hasta aquí el video de hoy!