AgendaT es una aplicación sencilla de agenda telefónica, desarrollada con SwiftUI y diseñada para macOS 13 (Ventura) y versiones posteriores. La aplicación ofrece una forma sencilla de explorar, buscar y editar contactos telefónicos almacenados en un archivo XML.
Más que una aplicación para uso real (aunque funciona bien), debería considerarse un ejercicio para aprender SwiftUI y a manejar archivos XML como origen para un conjunto de datos.
Arquitectura
Tecnologías principales
- SwiftUI: Framework de interfaz de usuario
- Análisis XML: Analizador XML nativo para el manejo de datos (framework Foundation que provee funcionalidades como almacenamiento de datos, procesado de textos, fecha y hora, etc.)
- Compatibilidad con varios idiomas: Sistema de traducciones integrado con detección automática de idioma.

Estructura del proyecto
AgendaT/
├── AgendaTApp.swift # Punto de entrada de la aplicación
├── AppDelegate.swift # Delegado de la aplicación
├── AgendaT.entitlements # Permisos y capacidades de la app
├── Assets.xcassets/ # Iconos y recursos visuales de la app
│ ├── AccentColor.colorset/
│ └── AppIcon.appiconset/
├── Models/
│ ├── FilterSortManager.swift # Lógica de filtrado y ordenación
│ ├── LocalizationManager.swift # Detección de idioma y traducción
│ ├── PhoneEntry.swift # Modelo de datos para registros de teléfono
│ ├── PhonebookManager.swift # Gestión de datos de la agenda
│ └── XMLParser.swift # Lógica de análisis XML
├── Resources/
│ ├── Phonebook.xml # Almacenamiento de datos de los contactos
│ ├── en.lproj/ # Idioma inglés
│ │ └── Localizable.strings
│ ├── es.lproj/ # Idioma español
│ │ └── Localizable.strings
│ └── fr.lproj/ # Idioma francés
│ └── Localizable.strings
└── Views/
└── ContentView.swift # Interfaz de usuario principal
Componentes clave
1. Modelo de datos (PhoneEntry.swift)
La estructura PhoneEntry representa un contacto individual con:
– id: Identificador numérico único (obligatorio, no nulo)
– name: Nombre del contacto
– phone1: Número de teléfono principal (solo numérico)
– phone2: Número de teléfono secundario (solo numérico).
Implementa:
– Identifiable: Para la representación en listas/cuadrículas de SwiftUI
– Codable: Para la compatibilidad con la serialización JSON
– Equatable: Para comparar entradas de una lista.
Nota: Todas las propiedades son mutables (var) para permitir la edición.
2. Analizador XML (XMLParser.swift)
Clase PhonebookXMLParser:
- Implementa XMLParserDelegate para el análisis estilo SAX (en forma de secuencia)
- La clase XMLParser lee el contenido de un archivo XML e informa de lo que encuentra mediante XMLParserDelegate, no hace nada con los datos salvo informarlos.
- Analiza phonebook.xml con elementos Contact que contienen subelementos Name, Phone1, Phone2 e ID
- Devuelve un array de objetos PhoneEntry
- Gestiona los datos con formato incorrecto (omite las entradas no válidas)
Función loadPhonebookData():
- Carga phonebook.xml desde el directorio de documentos del usuario (o lo copia desde el paquete de la app en la primera ejecución)
- Devuelve un array vacío si no se encuentra el archivo (a prueba de fallos)
- Ordena los contactos alfabéticamente por nombre mediante una comparación que tiene en cuenta la configuración regional y no distingue entre mayúsculas y minúsculas
- Proporciona registro y gestión de errores:
- Estado de carga del archivo y progreso del análisis XML
- Análisis de contactos individuales con validación de ID
- Nombres del primer y último contacto tras la ordenación.
Función savePhonebookData():
- Guarda los contactos editados en el directorio de documentos del usuario (los recursos del paquete son de sólo lectura)
- Escapa los caracteres especiales XML (&, <, >, ‘, «) en los nombres de los contactos
- Crea XML con formato correcto y sangría
- Devuelve el estado de éxito/error
- Proporciona un registro detallado para la depuración.
3. Interfaz de usuario (ContentView.swift)
Características principales:
- Visualización en cuadrícula ordenable: LazyVGrid con 4 encabezados de columna interactivos (Nombre, Teléfono 1, Teléfono 2, ID)
- Ordenación por columna: Haz clic en cualquier encabezado de columna para ordenar por ese campo (ascendente/descendente)
- Indicadores de ordenación: Iconos visuales en forma de V que muestran el campo actual de ordenación y la dirección
- Búsqueda de texto: Campo de texto para búsqueda parcial por nombre
- Filtro alfabético: 26 botones de letras (A-Z) para filtrar por primera letra
- Filtrado combinado: La búsqueda de texto y el filtro alfabético funcionan conjuntamente
- Contador de registros: Muestra el recuento de registros filtrados
- Añadir contacto: Botón para crear nuevos contactos
- Editar contacto: Hacer clic en el nombre del contacto para editarlo
- Eliminar contacto: Icono de papelera junto al ID de cada contacto (muestra diálogo de confirmación).
Gestión del estado:
- Propiedades @State para actualizaciones reactivas de la interfaz de usuario
- allEntries: Conjunto de datos completo cargado al iniciar
- filteredEntries: Vista actual después de aplicar filtros y ordenar
- searchText: Texto de búsqueda del usuario
- selectedLetter: Filtro de letra seleccionado actualmente
- sortField: Columna de ordenación actual (nombre, teléfono1, teléfono2, id)
- sortAscending: Indicador de dirección de ordenación
- editingEntry: Contacto que se está editando.
Lógica de filtrado y ordenación:
- Filtro de letra: Coincidencia de prefijo sin distinción entre mayúsculas y minúsculas
- Búsqueda de texto: Coincidencia de subcadena sin distinción entre mayúsculas y minúsculas (localizedCaseInsensitiveContains)
- Los filtros se combinan (lógica AND) cuando ambos están activos
- La ordenación se aplica después del Filtrado
- Alternar orden: Haz clic en el encabezado de la misma columna para invertir el orden
- El botón Borrar restablece los filtros, pero mantiene la preferencia de orden.
Diálogo de edición (EditContactView):
- Hoja modal para añadir/editar contactos
- Formulario con campos para Nombre, Teléfono 1 y Teléfono 2
- Los campos de teléfono filtran automáticamente las entradas no numéricas
- El campo ID es de sólo lectura (se genera automáticamente para los contactos nuevos)
- El botón Guardar se desactiva cuando el nombre está vacío
- Atajos de teclado (Escape para cancelar, Intro para guardar).
4. Sistema de traducción (LocalizationManager.swift)
Detección automática de idioma:
- Lee las preferencias del idioma de macOS mediante Locale.preferredLanguages
- Admite tres idiomas: inglés (en), español (es) y francés (fr)
- Si el idioma del sistema no es compatible, utiliza el inglés como alternativa
- No requiere selección manual de idioma.
Clase LocalizationManager:
- Detecta el idioma al inicializarse
- Proporciona el método localizedString(_:) para la búsqueda de claves
- Implementa la cadena de búsqueda: Idioma actual → Inglés → Clave.
Función auxiliar global:
- localized(_:) para un acceso sencillo en toda la aplicación
- Uso: Text(localized(«phone_numbers»)).
Idiomas compatibles
- Inglés (en): Idioma predeterminado y de respaldo
- Español (es) y Francés (fr): Traducción completa de los elementos de la interfaz de usuario.
Proceso de detección
- La aplicación lee las preferencias de idioma del sistema al iniciarse
- Extrae el código de idioma (los dos primeros caracteres)
- Comprueba si el código coincide con alguno de los idiomas compatibles
- Selecciona la primera coincidencia o usa el inglés como idioma predeterminado
- Carga el paquete .lproj correspondiente para la localización de cadenas.
Para agregar compatibilidad con idiomas adicionales:
- Crea un nuevo directorio .lproj (por ejemplo, de.lproj para alemán)
- Agrega el archivo Localizable.strings con las traducciones
- Actualiza la matriz supportedLanguages en LocalizationManager
- No se requieren cambios en el código de las vistas.
Formato de datos
Estructura XML
<?xml version="1.0" encoding="UTF-8"?>
<Phonebook>
<Contact>
<Name>John Doe</Name>
<Phone1>1234567890</Phone1>
<Phone2>9876543210</Phone2>
<ID>1</ID>
</Contact>
<!-- Más contactos... -->
</Phonebook>
Requisitos:
- Elemento raíz: Phonebook
- Cada contacto: elemento Contact
- Campos obligatorios: ID (entero), Name , Phone1 , Phone2
- Números de teléfono: cadenas numéricas
- Los ID deben ser únicos.
El archivo XML se guarda en la carpeta de documentos del usuario (no en la app):
~/Library/Containers/perez987.AgendaT/Data/Documents/Phonebook.xml
Consideraciones de diseño
Rendimiento
- LazyVGrid: Representación eficiente para grandes conjuntos de datos (sólo se representan los elementos visibles)
- Filtrado en memoria: Todo el filtrado se realiza sobre el conjunto de datos cargado (sin volver a analizar)
- Análisis XML único: Los datos se cargan una sola vez al iniciar la aplicación
- Los contactos se ordenan mediante una comparación que tiene en cuenta la configuración regional
- La ordenación se aplica a los resultados filtrados, no al conjunto de datos completo.
Experiencia de usuario
- Ordenación por varias columnas: Clic en cualquier encabezado de columna para ordenar; Clic de nuevo para invertir
- Indicadores visuales de ordenación: Los iconos de flecha muestran la columna de ordenación activa y la dirección
- Orden alfabético: Los contactos se pueden ordenar por nombre, número de teléfono o ID
- Indicadores visuales: La letra seleccionada se resalta con color
- Borrar: Un solo botón restablece todos los filtros a su estado inicial
- Funcionalidad de edición: Clic en el nombre del contacto para editarlo, icono de papelera para eliminarlo
- ID generados automáticamente: Los nuevos contactos obtienen automáticamente ID únicos
- Registro de depuración: Salida de consola para una fácil identificación.
Requisitos de programación
Requisitos del sistema
- macOS 13.0 (Ventura) o posterior
Xcode 15.0 o posterior
Swift 5.0 o posterior.
Compilación del proyecto
- Abre AgendaT.xcodeproj en Xcode
- Selecciona dispositivo/arquitectura de destino
- Compila y ejecuta (⌘R).
Guía de estilo de código
Convenciones de Swift
- CamelCase para propiedades y funciones (primera palabra empieza con minúscula, el resto con mayúscula, ej: sortedEntries)
- PascalCase para tipos (todas las palabras empiezan por mayúscula, ej: LocalizationManager)
- Nombres descriptivos en lugar de breves
- Comentarios para lógica no evidente
- Agrupar la funcionalidad relacionada con comentarios // MARK: (cuando corresponda).
Limitaciones conocidas
- Fuente de un único archivo: Todos los datos deben caber en un solo archivo XML
- Sin sincronización con iCloud: Sólo datos locales.
Consideraciones de accesibilidad
- Todos los elementos interactivos son accesibles mediante teclado
- Compatible con VoiceOver (compatibilidad nativa con SwiftUI)
- El texto se adapta al tamaño de fuente del sistema
- Se mantiene la estructura semántica (encabezados, listas, botones).
Seguridad y privacidad
- Sin acceso a la red: Aplicación totalmente offline
- Sólo datos locales: Archivo XML almacenado en el directorio de documentos del usuario
- Sin seguimiento de usuarios: Sin análisis ni telemetría
- Sandbox: Restricciones estándar del entorno aislado de macOS
- Almacenamiento editable: Los datos se guardan en la carpeta de documentos, accesible para el usuario.
Descarga el proyecto
Puedes descargar el proyecto completo para Xcode desde aquí.
