Durante años, era dífícil permitir al usuario seleccionar más de una fecha en un calendario. UIKit no ofrecía una solución nativa directa, obligando a los desarrolladores a depender de librerías de terceros (como FSCalendar) o a construir complejos CollectionView personalizados con lógica de calendario desde cero.
Con la llegada de iOS 16 y las actualizaciones recientes de Xcode, Apple introdujo en SwiftUI el componente que todos esperábamos: MultiDatePicker.
En este tutorial de programación Swift, exploraremos a fondo cómo seleccionar múltiples fechas en SwiftUI, gestionaremos la compleja estructura de datos DateComponents y adaptaremos nuestra implementación para iOS, macOS y watchOS.
¿Qué es MultiDatePicker y por qué es revolucionario?
MultiDatePicker es una vista de control que permite la selección de cero, una o más fechas. A diferencia del DatePicker tradicional (que gestiona un único punto en el tiempo), este nuevo control está diseñado para casos de uso como:
- Selección de días de vacaciones.
- Gestión de turnos laborales rotativos.
- Rastreadores de hábitos (Habit Trackers).
- Planificación de eventos recurrentes irregulares.
El cambio de paradigma: Date vs DateComponents
Para el iOS Developer acostumbrado a trabajar con objetos Date, MultiDatePicker presenta una curva de aprendizaje importante. No trabaja con Date. Trabaja con Set<DateComponents>.
¿Por qué? Porque Date representa un instante preciso en el tiempo universal (segundos desde 1970). Sin embargo, “El 25 de Diciembre” es un concepto de calendario. Apple usa DateComponents para garantizar que la fecha seleccionada sea robusta frente a zonas horarias y sistemas de calendario (Gregoriano, Budista, etc.).
Implementación Básica en iOS
Vamos a abrir Xcode y empezar con lo básico. Necesitamos una variable de estado que almacene un conjunto (Set) de componentes de fecha.
Paso 1: El Estado y la Vista
import SwiftUI
struct BasicMultiDateView: View {
// La fuente de la verdad. Usamos un Set para evitar duplicados automáticamente.
@State private var selectedDates: Set<DateComponents> = []
var body: some View {
VStack {
Text("Días seleccionados: \(selectedDates.count)")
.font(.headline)
.padding()
// El componente estrella
MultiDatePicker("Seleccionar fechas", selection: $selectedDates)
.padding()
}
}
}
Al ejecutar esto en el simulador de iOS, verás un calendario nativo donde puedes tocar varios días. Los días seleccionados se marcan con un círculo relleno.
Trabajando con los Datos: De Componentes a Fechas Reales
Aquí es donde la mayoría de tutoriales de programación Swift se quedan cortos. Tener un Set<DateComponents> es útil para la UI, pero tu backend o tu base de datos (CoreData/SwiftData) probablemente espera objetos Date.
Paso 2: Conversión y Ordenamiento
Vamos a crear una propiedad computada que transforme nuestra selección en algo legible.
var formattedDates: [String] {
let calendar = Calendar.current
// 1. Convertir Components a Date
let dates = selectedDates.compactMap { components -> Date? in
// Es vital forzar la hora a mediodía o inicio del día para evitar errores de zona horaria
calendar.date(from: components)
}
// 2. Ordenar cronológicamente
let sortedDates = dates.sorted()
// 3. Formatear a String
return sortedDates.map { date in
date.formatted(date: .long, time: .omitted)
}
}
Ahora podemos mostrar la lista debajo del calendario:
List(formattedDates, id: \.self) { dateString in
Text(dateString)
}
Restricciones y Rangos (Bounds)
Un escenario común al seleccionar múltiples fechas en swiftui es limitar el rango. Por ejemplo, en una app de nóminas, quizás solo quieras permitir seleccionar días del mes actual.
MultiDatePicker acepta el parámetro in: (rango), similar al DatePicker estándar.
struct PayrollView: View {
@State private var selectedDates: Set<DateComponents> = []
// Definimos el rango: Desde hoy hasta fin de año
var bounds: Range<Date> {
let now = Date()
let calendar = Calendar.current
let endOfYear = calendar.date(from: DateComponents(year: 2024, month: 12, day: 31))!
return now..<endOfYear
}
var body: some View {
MultiDatePicker("Nómina", selection: $selectedDates, in: bounds)
}
}
Al usar in: bounds, el calendario deshabilitará visualmente (grisáceo) todos los días fuera de ese rango, impidiendo la interacción del usuario.
Adaptación Multiplataforma en Xcode
El código de SwiftUI es portable, pero la experiencia de usuario debe adaptarse.
MultiDatePicker en macOS
En macOS, el paradigma cambia. Tenemos más espacio y un puntero de ratón. MultiDatePicker en macOS se renderiza automáticamente como un calendario de escritorio.
Truco Pro para macOS: A diferencia de iOS, donde el calendario suele ocupar el ancho de la pantalla, en macOS querrás restringir su tamaño o ponerlo en un Popover o barra lateral.
// Código optimizado para macOS
MultiDatePicker("Fechas", selection: $selectedDates)
.frame(width: 300) // Importante limitar el ancho en escritorio
.background(Material.ultraThin)
.cornerRadius(10)
MultiDatePicker en watchOS 10+
El Apple Watch recibió soporte para este control recientemente. Dado el tamaño de la pantalla, el diseño cambia radicalmente. Apple presenta una vista compacta que navega a una vista de detalle de mes completo al ser pulsada.
Tutorial Avanzado: App “Gestor de Turnos”
Vamos a combinar todo lo aprendido en un ejemplo real de programación Swift. Crearemos una vista para que un empleado seleccione sus días de teletrabajo.
El Modelo de Vista (ViewModel)
import SwiftUI
@Observable // Swift 5.9+ Macro
class ShiftManager {
var selectedDays: Set<DateComponents> = []
// Lógica para validar: No permitir fines de semana
func validateSelection() {
let calendar = Calendar.current
selectedDays = selectedDays.filter { components in
guard let date = calendar.date(from: components) else { return false }
let weekday = calendar.component(.weekday, from: date)
// 1 = Domingo, 7 = Sábado. Nos quedamos solo con Lunes a Viernes.
return weekday != 1 && weekday != 7
}
}
// Resetear
func clearAll() {
selectedDays.removeAll()
}
}
La Vista Principal
struct ShiftSelectorView: View {
@State private var manager = ShiftManager()
@Environment(\.calendar) var calendar // Usar el calendario del entorno
var body: some View {
NavigationStack {
VStack(spacing: 0) {
// Header Informativo
HStack {
VStack(alignment: .leading) {
Text("Teletrabajo")
.font(.title2.bold())
Text("\(manager.selectedDays.count) días seleccionados")
.foregroundStyle(.secondary)
}
Spacer()
Button("Limpiar") {
withAnimation { manager.clearAll() }
}
.buttonStyle(.bordered)
.tint(.red)
}
.padding()
Divider()
// El Selector
MultiDatePicker("Seleccionar días", selection: $manager.selectedDays)
.tint(.indigo) // Personalizar el color de selección
.padding()
.onChange(of: manager.selectedDays) {
// Validación reactiva
manager.validateSelection()
}
// Lista de confirmación
List {
Section("Resumen") {
if manager.selectedDays.isEmpty {
ContentUnavailableView("Sin fechas", systemImage: "calendar.badge.minus")
} else {
ForEach(manager.selectedDays.sorted(by: {
// Ordenar DateComponents es tricky, mejor convertir a Date si es crítico
($0.year ?? 0, $0.month ?? 0, $0.day ?? 0) < ($1.year ?? 0, $1.month ?? 0, $1.day ?? 0)
}), id: \.self) { day in
Label {
Text("\(day.day!)/\(day.month!)/\(day.year!)")
} icon: {
Image(systemName: "house.fill")
.foregroundStyle(.indigo)
}
}
}
}
}
}
.navigationTitle("Planificación")
}
}
}
Consejos y Trucos de Experto (Xcode Tips)
Para destacar como iOS Developer, debes conocer los detalles finos:
1. El entorno del Calendario (.environment)
MultiDatePicker usa Calendar.current por defecto. Si tu app necesita forzar un calendario específico (ej. Gregoriano en un dispositivo configurado en Budista), debes inyectarlo.
MultiDatePicker(...)
.environment(\.calendar, Calendar(identifier: .gregorian))
2. Rendimiento
Si seleccionas cientos de fechas (ej. un año entero), el rendimiento puede degradarse si intentas convertir y ordenar el array en el hilo principal dentro del body. Mueve la lógica de ordenamiento a un Task en segundo plano o usa Swift Concurrency si manejas volúmenes masivos de fechas.
3. Personalización Limitada
A diferencia de librerías de terceros, no puedes cambiar fácilmente el color de fondo de un día específico (ej. pintar los festivos de rojo y los seleccionados de azul) dentro del MultiDatePicker estándar. Si necesitas eso, MultiDatePicker no es la herramienta; necesitarás UICalendarView (vía UIViewRepresentable) que ofrece delegados para decoraciones personalizadas.
Conclusión
La capacidad de seleccionar múltiples fechas en swiftui con MultiDatePicker ha democratizado la creación de interfaces de calendario complejas. Lo que antes tomaba semanas de desarrollo personalizado o dependencias pesadas, ahora se logra con unas pocas líneas de código nativo en Xcode.
Aunque trabajar con Set<DateComponents> requiere un cambio mental respecto al uso de Date, la robustez que ofrece en términos de precisión de calendario vale la pena. Como iOS Developer, integrar este componente en tus aplicaciones de iOS, macOS y watchOS no solo moderniza tu interfaz, sino que garantiza que estás utilizando las mejores prácticas de accesibilidad y diseño del ecosistema Apple.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte 🙂. Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










