Contenido del curso

Operaciones CRUD en un proyecto con MVVM

Cómo conectar CoreData y Combine en MVVM

Resumen

Construir un ViewModel reactivo en Swift te permite conectar el modelo de datos con la interfaz sin actualizar nada a mano. Aquí verás cómo armar un ToDoViewModel usando Combine y Core Data dentro de una arquitectura MVVM, paso a paso, para que cualquier cambio en tus tareas se refleje al instante en la vista.

Por qué usar Combine como puente entre vista y modelo

Combine es el framework de Apple que maneja datos de forma reactiva. En lugar de refrescar la pantalla manualmente cada vez que cambia una tarea, suscribes la vista a un dato observable y las actualizaciones ocurren solas [00:25].

En una arquitectura MVVM tienes tres capas: el modelo (los datos), la vista (lo que ves) y el view model (el intermediario que comunica ambos). Combine vive justo en ese intermediario.

¿Qué hace Combine en un ViewModel? Permite que las propiedades marcadas como publicadas notifiquen a la vista cualquier cambio, sin que tú escribas código extra para refrescar la UI.

Qué importaciones necesita el ViewModel

Dentro del archivo ToDoViewModel.swift necesitas tres importaciones clave [01:30]:

  • Foundation: herramientas base de Swift.
  • CoreData: el framework para almacenar datos de forma local.
  • Combine: para manejar datos reactivos.

La clase se declara como public final class ToDoViewModel: ObservableObject. Ese conformismo a ObservableObject es lo que permite que las vistas se suscriban a los cambios de sus propiedades publicadas.

Cómo declarar propiedades publicadas y persistencia

La propiedad central del ViewModel es la colección de tareas. Se declara como un array vacío de tipo ToDoEntity, que es la entidad de Core Data donde vive la estructura de cada tarea [02:50].

Para que cualquier cambio en esa lista se propague automáticamente a la vista, se anota con @Published. Esa anotación es justo lo que conecta Combine con SwiftUI: si agregas, eliminas o modificas un to-do, la vista se redibuja sola.

swift @Published var todos: [ToDoEntity] = [] var cancellables: Set<AnyCancellable> = [] let storeContainer: NSPersistentContainer = ToDoPersistentManager.sharedContainer

La variable cancellables es un Set<AnyCancellable> y sirve para evitar fugas de memoria, cancelando suscripciones cuando ya no las necesitas [04:20]. El storeContainer es de tipo NSPersistentContainer y accede al contenedor compartido del ToDoPersistentManager, que es donde vive la base de datos local.

Cómo cargar las tareas al inicializar

En el init() se llama a fetchTodos(), así cuando creas una instancia del ViewModel ya tienes todas las tareas cargadas [05:50].

La función fetchTodos usa NSFetchRequest<ToDoEntity> para consultar Core Data. Para ordenar los resultados se usa un NSSortDescriptor con la key date y ascending: true, lo que devuelve las tareas ordenadas cronológicamente [07:00].

También se asigna request.returnsObjectsAsFaults = false para evitar cargar objetos fallidos y obtener acceso completo a los datos. Todo esto va envuelto en un do-catch que imprime un mensaje si falla la recuperación.

swift let request: NSFetchRequest<ToDoEntity> = ToDoEntity.fetchRequest() let sortDescriptor = NSSortDescriptor(key: "date", ascending: true) request.sortDescriptors = [sortDescriptor] request.returnsObjectsAsFaults = false do { todos = try storeContainer.viewContext.fetch(request) } catch { print("Error al recuperar todos") }

Qué métodos auxiliares necesita el ToDoViewModel

Más allá de listar tareas, el ViewModel necesita métodos para guardar, validar y localizar elementos.

El método privado saveData() llama a storeContainer.viewContext.save() dentro de un do-catch y luego ejecuta fetchTodos() para refrescar la lista [10:00]. Como todos está marcado con @Published, ese refresh se propaga solo a la vista.

¿Por qué llamar a fetchTodos después de guardar? Porque al modificar la propiedad publicada, Combine notifica a la vista y el cambio aparece sin código adicional.

Cómo encontrar el índice de una tarea

La función privada getTodoIndex(of:) recibe un ToDoEntity y devuelve el índice donde se encuentra dentro del array todos [11:40]. Usa firstIndex(where:) comparando el id del elemento con el del parámetro.

Si no lo encuentra, devuelve nil. Si lo encuentra, devuelve un entero con la posición.

Cómo validar el texto antes de guardar

La función pública validateInputs(of text: String) -> Bool aplica trimmingCharacters(in: .whitespaces) para eliminar espacios en blanco y luego cuenta los caracteres restantes [13:10]. Si el conteo es mayor a cero, retorna true; en caso contrario, false.

Esta validación es útil sobre todo para el título de la tarea, evitando que entren cadenas vacías o solo con espacios.

Qué funciones públicas expone el ViewModel

Para cubrir altas, bajas y modificaciones, el ViewModel declara varias funciones públicas que se implementarán después [14:30]:

  • addTodo: crea una tarea nueva.
  • updateTodo: actualiza el título o descripción.
  • updateTodoStatus: cambia el estado entre completado y no completado.
  • deleteTodo: elimina una tarea.
  • archiveTodo: archiva una tarea.
  • unarchiveTodo: la desarchiva.

Con este conjunto, el ViewModel cubre todo el ciclo de vida de una tarea dentro de la base de datos local.

La pieza que falta es conectar la vista con este ViewModel para consumir los datos de forma reactiva. ¿Cómo crees que SwiftUI debería suscribirse a @Published para mostrar la lista? Cuéntame en los comentarios.