Contenido del curso

Operaciones CRUD en un proyecto con MVVM

Listado y detalle de notas en SwiftUI

Resumen

Cuando construyes una app de notas en SwiftUI, agregar tareas es solo la mitad del camino. La otra mitad consiste en listar las notas y mostrar su detalle de forma fluida, usando un patrón MVVM con environment object, filtros y vistas previas animadas. Aquí verás cómo conectar la vista con el view model, filtrar tareas no archivadas y mostrar un preview al tocar cada elemento.

Cómo conecto el view model con la vista de lista

Lo primero es darle a tu vista acceso al view model compartido. En el archivo ToDoList, declaras una referencia con la anotación @EnvironmentObject y creas una variable de tipo ToDoViewModel [00:13]. Esta anotación permite que la vista lea los datos sin tener que instanciar manualmente el modelo.

Junto con esa referencia, necesitas dos variables de estado para controlar la interfaz:

  • showSheet: indica si está abierta la pantalla para crear una nueva nota.
  • toDoToPreview: de tipo ToDoEntity, indica si hay una nota en vista previa.

La segunda se declara con @State private var y arranca vacía [00:48]. Su función es disparar la aparición del detalle cuando el usuario toca una tarjeta.

¿Qué hace @EnvironmentObject en SwiftUI? Inyecta una instancia compartida del view model en cualquier vista del árbol, sin pasarla como parámetro. Es ideal para datos globales como una lista de tareas.

Cómo filtro las tareas no archivadas con una variable computada

Las notas se muestran en dos pantallas distintas, así que necesitas separar las archivadas de las que siguen activas. Para eso creas una variable privada llamada unarchivedTodos de tipo [ToDoEntity] [01:33].

La lógica usa el método filter sobre la propiedad pública todos del view model:

swift private var unarchivedTodos: [ToDoEntity] { viewModel.todos.filter { !$0.isArchived } }

El filter itera toda la colección desde la posición cero y devuelve solo los elementos donde isArchived sea falso [02:30]. Así obtienes una lista limpia para la pantalla principal, sin importar si la tarea está terminada o pendiente.

Cómo muestro las notas en una grilla con LazyVGrid

Dentro del ScrollView validas primero que existan elementos para mostrar. Usas if !unarchivedTodos.isEmpty y, si la condición se cumple, renderizas un LazyVGrid [03:25].

Este componente recibe varios parámetros clave:

  • columns: una variable previa con dos columnas tipo grid.
  • spacing: separación de 8 puntos entre items.
  • padding(.horizontal): para que no se peguen a los bordes.

La estructura queda lista para recibir las tarjetas. Para insertar cada elemento, usas un ForEach que itera sobre unarchivedTodos y, por cada todo, instancia un ToDoItemView enviando el parámetro toDo [05:50].

Cómo diseño cada tarjeta en ToDoItemView

En ToDoItemView declaras de nuevo el @EnvironmentObject y una variable pública let toDo: ToDoEntity. Antes de pintar el VStack, validas que el identificador no esté vacío con un if; en el else devuelves EmptyView() para evitar errores visuales [04:35].

Dentro de la tarjeta accedes a toDo.date para mostrar la fecha y, debajo, agregas una validación if let description = toDo.notes para mostrar la descripción solo cuando exista. El texto usa modificadores como .font(.caption), .fontWeight(.light) y .lineLimit(3) [06:50].

¿Para qué sirve LazyVGrid frente a un VStack? Renderiza los elementos solo cuando entran en pantalla, lo que mejora el rendimiento en listas largas. Además permite distribuir tarjetas en varias columnas con muy poca configuración.

Cómo abro el detalle de una nota con onTapGesture y overlay

Para que cada tarjeta sea interactiva, agregas un onTapGesture dentro del ForEach que asigna la nota tocada a toDoToPreview [07:55]. Ese cambio dispara la aparición de la vista previa.

La magia ocurre en la sección overlay del ZStack. Allí ya existe un if que muestra la pantalla de creación cuando showSheet es verdadero. Agregas un else if que valida toDoToPreview != nil y, si es así, instancia ToDoPreviewView pasándole el binding $toDoToPreview [08:30].

Para que la aparición se sienta natural, añades una transición animada:

swift .transition(AnyTransition.opacity.animation(.easeIn))

También bloqueas la barra de navegación con navigationBarHidden evaluando si showSheet está activo o si toDoToPreview no está vacío [04:00].

Cómo construyo ToDoPreviewView con binding y validación

En ToDoPreviewView defines tres propiedades:

  1. @EnvironmentObject private var viewModel: ToDoViewModel.
  2. @Binding public var toDo: ToDoEntity? para recibir la nota seleccionada.
  3. @State private var showedToDoCreationSheet: Bool = false para controlar la edición.

Dentro del body, envuelves el contenido en un ZStack y usas if let savedToDo = toDo para mostrar el detalle solo si llega una entidad válida [09:50]. En el else, devuelves una vista vacía.

Después accedes a savedToDo.title para el encabezado y, fuera del HStack, validas if let note = savedToDo.notes, !note.isEmpty para pintar la descripción con .multilineTextAlignment(.center) [11:00].

Cómo cierro la vista previa al tocar el fondo difuminado

Falta un detalle de usabilidad: si el usuario toca fuera de la tarjeta, la pantalla debe cerrarse. En la parte superior del ZStack hay un Rectangle que actúa como fondo difuminado. Le agregas una acción que asigna toDo = nil, lo que limpia el binding y oculta el preview automáticamente [11:50].

Con esto cierras el flujo completo: listas las notas no archivadas, las muestras en una grilla, abres el detalle con un tap y cierras la vista previa con otro toque. ¿Qué función agregarías después: archivar, editar o eliminar? Cuéntame en los comentarios cuál implementarías primero.