Contenido del curso

Integración Nativa en iOS 18

Galería de fotos con PhotosUI en SwiftUI

Resumen

Acceder a la galería del dispositivo en SwiftUI se volvió mucho más sencillo gracias al framework PhotosUI, disponible desde iOS 16. Si estás construyendo una app que necesita subir fotos de perfil, publicar contenido multimedia o adjuntar imágenes a un reporte, este enfoque reemplaza al antiguo UIImagePickerController con una API más moderna, declarativa y compatible con SwiftUI.

¿Qué es PhotosUI y por qué reemplaza a UIImagePickerController?

PhotosUI es el framework de Apple que expone componentes nativos para seleccionar fotos y videos desde la galería del dispositivo. Su pieza estrella es PhotosPicker, una vista que abre el selector del sistema, respeta los permisos del usuario y se integra de forma natural con el ciclo declarativo de SwiftUI.

¿Cuál es la diferencia entre PhotosPicker y UIImagePickerController? PhotosPicker es declarativo, vive dentro de SwiftUI, soporta selección múltiple y filtros por tipo de medio. UIImagePickerController pertenece a UIKit, requiere envoltorios y ofrece menos control sobre la selección.

Para empezar, basta con importar el framework al inicio del archivo [1:13]:

swift import PhotosUI

¿Cómo declarar el estado para guardar las fotos seleccionadas?

Antes de pintar cualquier vista necesitas tres variables de estado que sostengan la información: las fotos crudas que devuelve el picker, las imágenes ya convertidas a UIImage para mostrarlas, y un mensaje de error opcional para validaciones.

  • selectedPhotos: arreglo de PhotosPickerItem, el formato propio del picker.
  • images: arreglo de UIImage, listo para renderizarse en pantalla.
  • errorMessage: cadena para reportar fallos de carga.

swift @State private var selectedPhotos: [PhotosPickerItem] = [] @State private var images: [UIImage] = [] @State private var errorMessage: String = ""

La distinción entre selectedPhotos y images es clave: el picker entrega referencias asíncronas que aún no son imágenes utilizables, así que necesitas una colección intermedia para el render visual [3:42].

¿Cómo se construye el botón con PhotosPicker en SwiftUI?

El componente PhotosPicker recibe la selección, un límite de imágenes y un filtro de tipo de medio. Dentro defines el contenido visual del botón, normalmente un Label con icono.

swift PhotosPicker( selection: $selectedPhotos, maxSelectionCount: 3, matching: .images ) { Label("Seleccionar imágenes de la galería", systemImage: "photo") } .onChange(of: selectedPhotos) { _ in loadSelectedPhotos() }

El parámetro maxSelectionCount define cuántas fotos puede elegir el usuario; al llegar al tope, el sistema bloquea la selección automáticamente. El parámetro matching: .images filtra la galería para mostrar solo fotografías. Y el modificador onChange es el disparador que conecta la selección con la lógica de carga [8:30].

¿Cómo cargar imágenes de forma asíncrona con TaskGroup?

Cada PhotosPickerItem debe transformarse en UIImage mediante loadTransferable, una operación asíncrona que puede tardar distinto tiempo según el peso de cada foto. Por eso conviene paralelizar con withTaskGroup, que ejecuta las cargas de forma concurrente y agrupa los resultados.

swift private func loadSelectedPhotos() { images.removeAll() errorMessage = ""

Task { await withTaskGroup(of: UIImage?.self) { taskGroup in for photoItem in selectedPhotos { taskGroup.addTask { do { if let data = try await photoItem.loadTransferable(type: Data.self), let image = UIImage(data: data) { return image } return nil } catch { return nil } } } for await result in taskGroup { if let image = result { images.append(image) } else { errorMessage = "Falló en obtener una o más imágenes" } } } }

}

Antes de iniciar la tarea limpias images y errorMessage para evitar duplicados: si no lo haces, una segunda selección concatenaría sobre la anterior y terminarías con seis imágenes cuando el usuario eligió tres [15:20].

¿Por qué usar withTaskGroup en lugar de un simple for? Porque permite que varias imágenes se decodifiquen en paralelo. Si una pesa más, no bloquea a las demás, y el resultado se concatena conforme cada tarea termina.

¿Cómo mostrar las imágenes seleccionadas en la vista?

Una vez que images contiene los UIImage, los iteras con ForEach y aplicas modificadores de presentación: redimensionado, escala proporcional, esquinas redondeadas y separación vertical.

swift Section { ForEach(images, id: .self) { image in Image(uiImage: image) .resizable() .scaledToFit() .frame(maxWidth: .infinity) .clipShape(RoundedRectangle(cornerRadius: 20)) .padding(.vertical, 10) } }

El modificador resizable habilita el redimensionado, scaledToFit evita deformaciones, clipShape con RoundedRectangle da el acabado de esquinas suaves, y el padding vertical separa cada foto del resto del listado [13:10].

¿Cómo manejar errores de carga de imágenes?

Dentro del formulario conviene reservar un espacio para mostrar fallos. Si una transferencia falla o el archivo no puede convertirse a UIImage, el errorMessage se actualiza y se renderiza en rojo.

swift if !errorMessage.isEmpty { Text(errorMessage) .foregroundColor(.red) .padding() }

Esto cubre escenarios reales: imágenes corruptas, formatos no soportados o cancelaciones inesperadas. El usuario ve feedback inmediato en lugar de una pantalla en blanco.

¿Qué hace onChange dentro de PhotosPicker? Se ejecuta cada vez que selectedPhotos cambia: al añadir, quitar o reemplazar una foto. Es el puente entre la selección del usuario y la recarga del listado visual.

Con estos bloques tienes un selector de galería completo en SwiftUI: declarativo, asíncrono, con límite configurable y feedback de errores. Cambiar maxSelectionCount de 3 a 5 amplía el límite sin tocar nada más, y la misma estructura te sirve para fotos de perfil, reportes con evidencia o publicaciones multimedia. ¿En qué pantalla de tu app vas a integrar PhotosPicker primero? Cuéntanos en los comentarios.