En el desarrollo de aplicaciones móviles, la retroalimentación visual es la piedra angular de una buena experiencia de usuario (UX). No hay nada que frustre más a un usuario que una aplicación congelada sin saber si está trabajando en segundo plano o si simplemente ha colapsado. Para un iOS Developer, gestionar estas esperas es crucial. Aquí es donde entra en juego el componente nativo de Apple: el ProgressView.
Con la llegada de la programación Swift moderna y el paradigma declarativo, implementar indicadores de carga ha pasado de ser una tarea tediosa con UIActivityIndicatorView o UIProgressView en UIKit, a ser una experiencia fluida y potente en SwiftUI. En este tutorial exploraremos qué es, cómo funciona y cómo personalizar al máximo un ProgressView en SwiftUI para tus apps de iOS, macOS y watchOS usando Xcode.
¿Qué es un ProgressView en SwiftUI?
Un ProgressView es una vista estructural en SwiftUI diseñada para mostrar el progreso de una tarea hacia su finalización. A diferencia de los enfoques imperativos antiguos, este componente se adapta automáticamente al contexto de la plataforma y al estilo del sistema operativo.
Fundamentalmente, existen dos tipos de ProgressView que todo iOS Developer debe conocer:
- Indeterminado: Utilizado cuando no sabemos cuánto tardará la tarea (el clásico spinner o rueda giratoria). Indica que “algo está pasando”.
- Determinado: Utilizado cuando podemos cuantificar el progreso (una barra que se llena del 0% al 100%). Indica “cuánto falta”.
1. Implementando un ProgressView Indeterminado
El caso de uso más común es la carga de datos desde una API o la espera de un proceso en segundo plano. En Xcode, la implementación más básica es increíblemente sencilla.
struct BasicLoaderView: View {
var body: some View {
VStack {
Text("Cargando perfil...")
ProgressView()
}
}
}
Al escribir ProgressView() sin parámetros, SwiftUI infiere que quieres un estilo indeterminado. En iOS, esto renderiza el clásico indicador de actividad giratorio (spinner). En macOS, puede aparecer como un spinner o una barra barber-pole dependiendo del contexto.
Añadiendo una etiqueta (Label)
Una buena práctica de accesibilidad y UX es describir qué está ocurriendo. El inicializador de ProgressView permite adjuntar una etiqueta que el sistema posicionará adecuadamente.
ProgressView("Conectando con el servidor...")
Aunque visualmente a veces es preferible crear tu propia jerarquía con un VStack y un Text personalizado para tener más control sobre la fuente y el color, usar el inicializador con etiqueta garantiza que VoiceOver lea el estado correctamente para usuarios con discapacidad visual.
2. ProgressView Determinado: Visualizando porcentajes
Cuando descargas un archivo o procesas un lote de imágenes en Swift, necesitas mostrar el avance real. Para ello, utilizamos el inicializador que acepta un value (valor actual) y un total (valor máximo).
struct DownloadProgressView: View {
@State private var progress = 0.5 // 50% completado
var body: some View {
VStack(spacing: 20) {
ProgressView(value: progress, total: 1.0)
.padding()
Button("Incrementar") {
if progress < 1.0 {
progress += 0.1
}
}
}
}
}
Por defecto, si no especificas el total, SwiftUI asume que es 1.0. Esto renderiza una barra de progreso lineal (Linear Progress Bar) estándar del sistema.
3. Personalización con ProgressViewStyle
Aquí es donde la programación Swift con SwiftUI brilla. Al igual que los botones tienen estilos, el ProgressView soporta el modificador .progressViewStyle(). Esto nos permite cambiar radicalmente la apariencia sin cambiar la lógica.
Estilos Nativos
Dependiendo de la plataforma (iOS, macOS, watchOS), tenemos acceso a diferentes estilos:
.circular: Fuerza el estilo de spinner, incluso si tienes un valor determinado (útil para gráficos de anillo pequeños)..linear: Fuerza la barra horizontal..automatic: El comportamiento por defecto.
VStack {
// Spinner clásico
ProgressView()
.progressViewStyle(.circular)
.tint(.purple) // Cambiar el color en iOS 15+
.scaleEffect(2.0) // Hacerlo más grande
// Barra de progreso
ProgressView(value: 0.7)
.progressViewStyle(.linear)
.tint(.orange)
}
4. Creando un Estilo Personalizado (Custom ProgressViewStyle)
Para un iOS Developer que necesita seguir una guía de diseño estricta, los estilos nativos pueden quedarse cortos. SwiftUI nos permite crear nuestros propios estilos conformando el protocolo ProgressViewStyle. Esto nos da control total sobre la renderización.
Vamos a crear una barra de progreso personalizada que sea una cápsula con un degradado y una sombra, algo muy común en apps modernas.
struct GradientBarStyle: ProgressViewStyle {
var height: CGFloat = 20
var colors: [Color] = [.blue, .purple]
func makeBody(configuration: Configuration) -> some View {
// Obtenemos la fracción completada (0.0 a 1.0)
let fraction = configuration.fractionCompleted ?? 0
return GeometryReader { geometry in
ZStack(alignment: .leading) {
// Fondo de la barra (track)
Capsule()
.fill(Color.gray.opacity(0.2))
.frame(height: height)
// Relleno de la barra (fill)
Capsule()
.fill(LinearGradient(gradient: Gradient(colors: colors),
startPoint: .leading,
endPoint: .trailing))
.frame(width: geometry.size.width * CGFloat(fraction),
height: height)
.animation(.spring(), value: fraction) // Animación suave
}
}
.frame(height: height)
}
}
Ahora, aplicar este estilo en tu vista principal es trivial:
ProgressView(value: 0.6)
.progressViewStyle(GradientBarStyle(height: 15, colors: [.green, .blue]))
.padding()
5. Trabajando con Progress de Foundation
En proyectos complejos de Xcode, es posible que estés utilizando el objeto Progress nativo de Foundation (muy común al usar URLSession o CloudKit). SwiftUI tiene un inicializador específico para esto.
struct TaskProgressView: View {
let progressObject: Progress
var body: some View {
// SwiftUI observa automáticamente los cambios en el objeto Progress
ProgressView(progressObject)
}
}
Esto vincula automáticamente la UI con la lógica de negocio, eliminando la necesidad de actualizar manualmente variables @State.
6. Consideraciones Multiplataforma: iOS, macOS y watchOS
Como desarrolladores del ecosistema Apple, a menudo escribimos código compartido. El ProgressView se comporta de manera inteligente en cada sistema:
- iOS: Prioriza la tactilidad y el tamaño legible. El estilo
.linearocupa el ancho horizontal disponible. - watchOS: El espacio es oro. Un
ProgressViewindeterminado es muy común. Si usas uno determinado, considera envolverlo en un estilo de anillo o usar colores de alto contraste sobre el fondo negro. - macOS: Aquí el ProgressView suele ser más delgado y discreto. Es común verlo en barras de herramientas o en la parte inferior de las ventanas.
7. Integración con AsyncImage
Uno de los usos más prácticos de un ProgressView en SwiftUI moderno es dentro de AsyncImage para mostrar un indicador mientras se descarga una foto de internet.
AsyncImage(url: URL(string: "https://ejemplo.com/foto.jpg")) { phase in
switch phase {
case .empty:
ProgressView() // Se muestra mientras carga
.controlSize(.large)
case .success(let image):
image.resizable().aspectRatio(contentMode: .fit)
case .failure:
Image(systemName: "exclamationmark.triangle")
@unknown default:
EmptyView()
}
}
8. Accesibilidad y Mejores Prácticas
Para un iOS Developer senior, la accesibilidad no es opcional. El ProgressView viene con soporte integrado, pero debes configurarlo correctamente:
- Usa etiquetas descriptivas siempre que sea posible.
- Si usas un estilo personalizado que no muestra texto, utiliza
.accessibilityLabel()y.accessibilityValue().
ProgressView(value: 0.5)
.accessibilityLabel("Descargando actualización")
.accessibilityValue("50 por ciento")
Conclusión
El ProgressView en SwiftUI es una herramienta engañosamente simple. Bajo su superficie minimalista, esconde una potencia enorme para comunicar el estado de la aplicación al usuario. Desde simples spinners hasta barras de carga personalizadas con animaciones complejas, dominar este componente es esencial en la programación Swift actual.
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









