Novedades de SwiftUI para iOS 27
Cada versión de SwiftUI te dice dónde estaban los puntos de presión del framework a partir de lo que Apple decidió reconstruir. La respuesta de iOS 27 es inusualmente amplia: las listas obtienen reordenamiento de primera clase, los documentos obtienen una nueva familia de protocolos de lectura/escritura, las barras de herramientas obtienen un modelo de desbordamiento con prioridad explícita, y la presentación de errores por fin obtiene un binding al que puedes entregarle directamente un Error. La versión mueve cuatro superficies a la vez, y la mayoría de las apps tocan al menos dos de ellas.1
La tentación con una versión tan amplia es adoptarlo todo. La mejor jugada es reconocer cuáles adiciones cambian la forma en que construyes frente a cuáles son sobrecargas de conveniencia sobre patrones que ya usas. Arrastrar para reordenar y los protocolos de documento son lo primero: reemplazan código que escribiste a mano. Las alertas basadas en elementos y AsyncImage(request:) son lo segundo: eliminan una solución alternativa. Esta entrada clasifica la superficie de SwiftUI de iOS 27 en esos hilos, con las declaraciones reales y el razonamiento de cuándo cada una se gana su lugar en tu código.
En la sesión 269, Apple enmarca la versión como cuatro hilos amplios que se mueven juntos, una apariencia refinada, una nueva y potente API de documentos, nuevas formas de interactuar y trabajo de rendimiento, en lugar de una única función protagonista.35
Resumen / Puntos clave
- Las listas y los contenedores personalizados obtienen reordenamiento declarativo:
reorderContainer(for:isEnabled:move:)marca un contenedor,reorderable()sobreDynamicViewContentinscribe las filas, y recibes unReorderDifferenceen lugar de escribir la aritmética de índices a mano.23 - Los contenedores de arrastre perezoso llegan a través de
dragContainer(for:itemID:in:_:)másdraggable(containerItemID:containerNamespace:), que solo transporta un identificador, así el framework obtiene las cargas útiles de forma perezosa cuando comienza un arrastre.45 - Un nuevo modelo de documento aterriza como
ReadableDocumentyWritableDocument(conDocumentReader/DocumentWriterhaciendo el trabajo en disco yFileWrapperDocumentReader/FileWrapperDocumentWriterpara el caso simple), respaldado porURLDocumentConfiguration.6789101112 - Las barras de herramientas ganan
ToolbarOverflowMenu, la ubicacióntopBarPinnedTrailingyvisibilityPriority(_:), para que decidas qué controles sobreviven cuando la barra se queda sin espacio.131415 - La presentación de errores obtiene
alert(error:actions:message:)y las formas basadas en elementosalert(_:item:actions:)/confirmationDialog, másAsyncImage(request:)para control completo deURLRequestyasyncImageURLSession(_:)para compartir una sesión.1617181920 - Redondeando la superficie:
swipeActions(...onPresentationChanged:),swipeActionsContainer(),NavigationTransition.crossFade,TabRole.prominent,UIHostingSceneDelegateyGestureInputKinds.212223242526
Arrastrar para reordenar se vuelve declarativo
Reordenar una lista en SwiftUI solía significar onMove, un conjunto de índices y un desplazamiento de destino que traducías a tu propia mutación de modelo. iOS 27 reemplaza eso con una declaración en dos partes: marcas un contenedor como reordenable y marcas su contenido como participante. Luego el contenedor te entrega un diff estructurado. El modelo que ese diff muta está, a su vez, mejor observado en iOS 27: SwiftData ganó APIs de observación y de historial persistente de primera clase en la misma versión, así un almacén que reordenas en una vista permanece sincronizado en todas partes donde se lee.
El caso de colección única es el más común. Declaras reorderContainer(for:isEnabled:move:) sobre el contenedor y reorderable() sobre el DynamicViewContent dentro de él:23
struct LandmarkList: View {
@State private var landmarks: [Landmark]
var body: some View {
List {
ForEach(landmarks) { landmark in
LandmarkRow(landmark: landmark)
}
.reorderable()
}
.reorderContainer(for: Landmark.self) { difference in
// Apply the reorder to your model.
landmarks.apply(difference)
}
}
}
La firma te dice el contrato. El modificador del contenedor es genérico sobre Item : Identifiable y te da un ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier>:2
nonisolated func reorderContainer<Item>(
for item: Item.Type,
isEnabled: Bool = true,
move: @escaping (ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier>) -> ()
) -> some View where Item : Identifiable, Item.ID : Sendable
Aquí importan dos decisiones de diseño. Primero, el framework impulsa la interacción: como lo describe la documentación de Apple, un elemento reordenable se puede levantar con un gesto de arrastre, una vista de marcador de posición toma su lugar para mostrar dónde aterrizará el elemento, y el marcador de posición sigue el arrastre por el contenedor.2 Ya no construyes esa señal de interacción; describes la colección y reaccionas al resultado. Segundo, isEnabled es un parámetro en lugar de un modificador separado, así un interruptor de modo de edición se convierte en un booleano en vez de una construcción condicional de vistas.
Cuando un contenedor alberga más de una colección, recurres a la sobrecarga que toma un tipo de identificador de colección, reorderContainer(for:in:isEnabled:move:):27
nonisolated func reorderContainer<Item, CollectionID>(
for item: Item.Type,
in collectionID: CollectionID.Type,
isEnabled: Bool = true,
move: @escaping (ReorderDifference<Item.ID, CollectionID>) -> ()
) -> some View where Item : Identifiable, CollectionID : Hashable, CollectionID : Sendable, Item.ID : Sendable
El ReorderDifference ahora está indexado tanto por el ID del elemento como por un ID de colección, así un movimiento que cruza de una sección a otra es expresable. La guía de Apple es directa: usa la sobrecarga multicolección cuando tu contenedor tenga varias colecciones, y la conveniencia de colección única cuando tenga una.27 El modificador reorderable() acepta el identificador de colección correspondiente cuando necesitas desambiguar.3
reorderable() a un ForEach y un reorderContainer a su elemento padre, manejando la diferencia en el closure.
En la sesión 271, Apple muestra el mismo código de reordenamiento pasando sin cambios de una List a una LazyVGrid, porque los modificadores describen la colección en lugar del contenedor, así el reordenamiento funciona en cualquier contenedor que admita arrastrar y soltar.36
Contenedores de arrastre perezoso
La otra mitad de la historia de arrastre de iOS 27 tiene que ver con el costo. El draggable clásico requiere que produzcas la carga útil por adelantado, lo que significa que el framework puede necesitar materializar un elemento (y a veces renderizarlo) antes incluso de que comience un arrastre. Para una lista perezosa de miles de filas, eso es trabajo desperdiciado.
dragContainer(for:itemID:in:_:) define un contenedor de vistas arrastrables y solicita la carga útil solo una vez, como un closure sobre los identificadores arrastrados:4
nonisolated func dragContainer<ItemID, Item, Data>(
for itemType: Item.Type = Item.self,
itemID: KeyPath<Item, ItemID>,
in namespace: Namespace.ID? = nil,
_ payload: @escaping (Array<ItemID>) -> Data
) -> some View where ItemID : Hashable, ItemID : Sendable, Item : Transferable, Item == Data.Element, Data : Collection
Dentro de ese contenedor, cada hijo arrastrable usa draggable(containerItemID:containerNamespace:), que solo transporta el identificador del elemento:5
nonisolated func draggable<ItemID>(
containerItemID: ItemID,
containerNamespace: Namespace.ID? = nil
) -> some View where ItemID : Hashable, ItemID : Sendable
La razón por la que este es el mejor valor predeterminado para colecciones grandes está en la propia descripción de Apple: como el modificador suministra solo un identificador y no la carga útil, funciona de forma perezosa, así el framework pide los elementos arrastrados reales solo cuando comienza el arrastre y no tiene que renderizar una vista para acceder a su carga útil.5 Un valor Fruit que se identifica a sí mismo por nombre (y nunca se conforma a Identifiable) puede aún ser el origen de un arrastre de varios elementos, porque el contenedor se basa en el KeyPath que proporcionas en lugar de en una conformidad con Identifiable.4
Vale la pena señalar para el código multiplataforma: dragContainer y draggable(containerItemID:containerNamespace:) están disponibles en macOS 26.0, donde la mayor parte del resto de esta versión es macOS 27.0, así que la API de arrastre perezoso es una que ya podrías haber adoptado en la Mac.45
Un nuevo modelo de documento
DocumentGroup y FileDocument cargaron las apps de documentos de SwiftUI durante años, pero los lados de lectura y escritura estaban entrelazados en una sola conformidad. iOS 27 los separa. La lectura y la escritura ahora son protocolos distintos, la lógica en disco es su propia capa, y un tipo de lectura-escritura compone ambos.
Los dos protocolos de nivel superior son ReadableDocument y WritableDocument:67
protocol ReadableDocument : AnyObject
protocol WritableDocument : AnyObject
Un tipo de solo lectura se conforma a ReadableDocument solo. Un tipo de lectura-escritura se conforma a ambos, y Apple proporciona un typealias Document que agrupa los dos para que puedas adoptar el par sin nombrar cada uno.67 Ambos están vinculados a clase (: AnyObject), que es la señal visible de que los documentos son tipos de referencia en este modelo.
La E/S de disco real se traslada a una abstracción de lector y escritor, DocumentReader y DocumentWriter, cada uno parametrizado por el tipo de snapshot que tu documento serializa:89
protocol DocumentReader<Snapshot>
protocol DocumentWriter<Snapshot>
La mayoría de las apps nunca los implementan a mano. Para documentos de tamaño pequeño y mediano que no necesitan lógica personalizada, SwiftUI incluye FileWrapperDocumentReader y FileWrapperDocumentWriter, cada uno respaldado por un file wrapper y descrito por Apple como la opción eficiente para el caso simple:1011
struct FileWrapperDocumentReader<Snapshot>
struct FileWrapperDocumentWriter<Snapshot>
Lo que vincula el documento abierto es URLDocumentConfiguration, una clase de actor principal que contiene los ajustes y propiedades de un documento abierto:12
@MainActor final class URLDocumentConfiguration
La exportación fluye a través de un fileExporter actualizado que toma un WritableDocument cuyo escritor apunta a una URL:28
nonisolated func fileExporter<D>(
isPresented: Binding<Bool>,
document: D?,
contentType: UTType? = nil,
defaultFilename: String? = nil,
onCompletion: @escaping (Result<URL, any Error>) -> Void,
onCancellation: (() -> Void)? = nil
) -> some View where D : WritableDocument, D.Writer.Destination == URL
La restricción D.Writer.Destination == URL es la parte portante: el exportador solo acepta un documento escribible cuyo escritor escriba a una URL, que es exactamente el caso de archivo en disco que maneja el diálogo del sistema. Apple documenta el ciclo de vida con precisión: el diálogo aparece solo cuando document no es nil, isPresented se establece en false antes de que se ejecute onCompletion, y una cancelación del usuario establece isPresented en false y llama a onCancellation.28 La separación entre legible y escribible es lo que permite que una función de solo visualización importe un documento sin conformarse jamás al lado de escritura.
Barras de herramientas que deciden qué sobrevive
Las barras de herramientas se quedan sin espacio. Un iPhone de ancho compacto, una ventana de Mac redimensionada o un campo de búsqueda activo pueden todos dejar menos espacios de los que tienes controles. Antes de iOS 27, el framework tomaba las decisiones de desalojo por ti. Ahora las tomas tú.
ToolbarOverflowMenu es la superficie de desbordamiento explícita. Apple la describe como acciones que siempre se colocan en el menú de desbordamiento de la barra de herramientas sin importar el modo de la barra, la plataforma o la personalización, y en iOS y visionOS ese contenido aterriza en el menú de desbordamiento de la barra de navegación:13
nonisolated struct ToolbarOverflowMenu<Content> where Content : View
Para los controles que deberían resistir el desbordamiento, la nueva ubicación topBarPinnedTrailing fija un elemento al borde final de la barra de herramientas:14
static let topBarPinnedTrailing: ToolbarItemPlacement
El matiz que Apple documenta es que los elementos fijados solo se mueven al menú de desbordamiento cuando la búsqueda está activa y no hay suficiente espacio, y en iOS y visionOS la barra superior es la barra de navegación.14 Así que topBarPinnedTrailing es para el uno o dos controles que nunca quieres enterrar a menos que la búsqueda fuerce la situación.
Cuando la elección es relativa en lugar de absoluta, visibilityPriority(_:) sobre ToolbarContent clasifica los elementos para que el framework conozca el orden de desalojo:15
@MainActor @preconcurrency
func visibilityPriority(_ priority: ToolbarItemVisibilityPriority) -> some ToolbarContent
La regla de Apple: cuando el espacio de la barra de herramientas es limitado, los elementos con menor prioridad se mueven al menú de desbordamiento antes que los elementos con mayor prioridad.15 Un control importante puede ubicarse en el borde final y aún mostrarse a medida que la ventana se encoge. Combinado con topBarPinnedTrailing y ToolbarOverflowMenu, ahora tienes un vocabulario completo para la degradación elegante: fija lo esencial, prioriza el resto y enruta lo siempre secundario al desbordamiento.
Un modificador relacionado vincula la barra de herramientas al comportamiento del desplazamiento y al cromo de Liquid Glass que introdujo iOS 26. toolbarMinimizeBehavior(_:for:) habilita la minimización de la barra de herramientas en respuesta al desplazamiento, y Apple señala que cuando la barra de navegación se minimiza, una barra de pestañas superior integrada se minimiza con ella:29
nonisolated func toolbarMinimizeBehavior(
_ behavior: ToolbarMinimizeBehavior,
for bars: ToolbarPlacement...
) -> some View
La ubicación admitida es la barra de navegación, y de forma predeterminada el área segura se ajusta a medida que la barra se minimiza.29 Si adoptaste barras de herramientas de Liquid Glass y querías que se retiraran a medida que el usuario lee, este es el modificador que lo hace.
Alertas basadas en elementos y presentación de errores
La API de alertas de SwiftUI ha tenido durante mucho tiempo una forma booleana (alert(_:isPresented:)) que te obliga a guardar los datos de la alerta en un @State separado junto al indicador de presentación. iOS 27 agrega las formas basadas en elementos que las APIs de sheet y popover ya tenían, así los datos son el disparador.
La alerta basada en elementos se presenta cada vez que el binding no es nil y pasa el valor desempaquetado a tu constructor de acciones:17
nonisolated func alert<A, T>(
_ title: Text,
item data: Binding<T?>,
@ContentBuilder actions: (T) -> A
) -> some View where A : View
Hay una sobrecarga correspondiente que agrega un constructor de mensaje, alert(_:item:actions:message:), y un par equivalente para confirmationDialog, así el mismo patrón impulsado por elementos se traslada a través de alertas y diálogos.183031 El contrato de Apple es el mismo en cada caso: los datos deben no ser nil para que aparezca la presentación, y los cambios que hagas a los datos después de que ocurre la presentación se ignoran.18
Las sobrecargas de presentación de errores son la capacidad genuinamente nueva. En lugar de mapear un error a una struct personalizada, vinculas un Error directamente:16
nonisolated func alert<E, A, M>(
error: Binding<E?>,
@ContentBuilder actions: (E) -> A,
@ContentBuilder message: (E) -> M
) -> some View where E : Error, A : View, M : View
El comportamiento es lo que hace que valga la pena adoptarlo. Cuando el valor del error no es nil, el sistema presenta la alerta, y el título se infiere del errorDescription del error si el error es un LocalizedError; de lo contrario, el título recurre a la descripción localizada.16 Un LocalizedError que ya definiste ahora impulsa su propio título de alerta sin cableado adicional. Una sobrecarga más simple, alert(error:actions:), descarta el constructor de mensaje cuando una acción OK es todo lo que necesitas:19
nonisolated func alert<E, A>(
error: Binding<E?>,
@ContentBuilder actions: () -> A
) -> some View where E : Error, A : View
struct EditorView: View {
@State private var saveError: SaveError?
var body: some View {
Form { /* ... */ }
.alert(error: $saveError) { error in
Button("Retry") { retry() }
Button("Cancel", role: .cancel) { }
} message: { error in
Text(error.recoverySuggestion ?? "")
}
}
}
El patrón que desaparece: una struct AlertError hecha a mano, un envoltorio identifiable y el código de mapeo entre tu tipo de error real y el origen de datos de la alerta. Vinculas el Error? que tu código ya produce.
AsyncImage madura con URLRequest
AsyncImage se lanzó con un inicializador de URL y sin forma de establecer encabezados, una política de caché o un tiempo de espera. Las adiciones de iOS 27 toman un URLRequest, que es el objeto que transporta los tres.
La forma más simple carga y muestra una imagen desde una solicitud:20
nonisolated init(request: URLRequest, scale: CGFloat = 1) where Content == Image
La forma por fases te da el AsyncImagePhase para impulsar un closure de contenido, y Apple señala que puedes especificar la política de caché y el intervalo de tiempo de espera a través de la solicitud:32
nonisolated init(
request: URLRequest?,
scale: CGFloat = 1,
transaction: Transaction = Transaction(),
@ContentBuilder content: @escaping (AsyncImagePhase) -> Content
)
También hay una forma de content/placeholder para la división común de “muestra esto hasta que cargue, muestra aquello al tener éxito”.33 El comportamiento en las tres es el contrato documentado de AsyncImage: SwiftUI muestra un marcador de posición hasta que la carga se completa, intercambia la imagen al tener éxito y mantiene el marcador de posición en caso de fallo.20
El modificador acompañante es asyncImageURLSession(_:), que entrega a las instancias de AsyncImage dentro de una vista una URLSession con la cual obtener datos:34
nonisolated func asyncImageURLSession(_ urlSession: URLSession) -> some View
var body: some View {
List(avatars) { avatar in
AsyncImage(request: URLRequest(url: avatar.url))
.frame(width: 44, height: 44)
}
.asyncImageURLSession(authenticatedSession)
}
La combinación es la respuesta a la carga de imágenes autenticada. Una solicitud te permite adjuntar un encabezado Authorization o una política de caché personalizada; el modificador de sesión permite que todo un subárbol comparta una URLSession configurada (encabezados personalizados, una caché en disco, un proxy) en lugar de que cada AsyncImage recurra a la sesión compartida. Para una app que carga avatares detrás de un token, esa es la diferencia entre funcionar y no.
También llega
Varias adiciones más pequeñas merecen una línea cada una, porque cada una elimina una fricción específica.
swipeActions gana una sobrecarga con un closure onPresentationChanged: que se dispara con true cuando las acciones de deslizamiento de una fila se vuelven visibles y false cuando se descartan, así puedes atenuar una fila o actualizar el cromo circundante mientras se muestran las acciones.21 Para diseños de fila personalizados construidos sobre ScrollView o LazyVStack en lugar de List, swipeActionsContainer() coordina el descarte y la exclusión mutua entre filas de la forma en que List ya lo hace automáticamente (aplicarlo a una List no tiene efecto).22
NavigationTransition.crossFade es una transición que hace un cross-fade entre las vistas que aparecen y desaparecen; especificada en un sheet, hace que el sheet aparezca con fundido sobre el contenido en lugar de moverlo hacia arriba para cubrir el contenido.23 TabRole.prominent le da a una pestaña un tratamiento visual prominente en las barras de pestañas compatibles, y Apple señala que sin una pestaña .prominent explícita, una pestaña de rol .search puede recibir el tratamiento prominente de forma predeterminada.24
UIHostingSceneDelegate extiende UISceneDelegate para tender un puente a las escenas de SwiftUI, permitiendo que UIKit active una escena de SwiftUI declarada en la propiedad estática rootScene de la clase que se conforma.25 (Es el único elemento aquí que se remonta a iOS 26.0 en la mayoría de las plataformas, llegando a tvOS en la beta 27.0.25) Esa fontanería de escenas importa más de lo que parece, porque iOS 27 también convierte el ciclo de vida basado en escenas de UIKit en un requisito estricto: una app construida con el SDK más reciente que no lo haya adoptado falla por completo al iniciarse. Y GestureInputKinds es un option set que especifica qué tipos de entrada debe reconocer un gesto, la base para gestos que distinguen, por ejemplo, toque de puntero.26
ContentBuilder unifica los result builders
Las adiciones de arriba son superficie de API. Un cambio en el ciclo de 2027 es fontanería, y toca cada vista que compilas en lugar de cualquiera que adoptes. La sesión 269 lo enmarca a través de un error que la mayoría de los desarrolladores de SwiftUI han encontrado: “The compiler is unable to type-check this expression in reasonable time”.37
La causa es la resolución de sobrecarga. Una vista que envuelve su contenido en un Section, un Group y un ForEach obliga al compilador a recorrer un árbol de decisiones. Como lo explica Apple, “first the compiler has to select which overload of Section to use. Section can be initialized with a builder that produces either a View, or TableRowContent. To know which one to use, the compiler has to try both options”. Esa ramificación se anida: “for the nested ForEach the compiler will have to try each one. And then, ForEach’s builder has its own set of options that will also need to be checked”. Cada capa multiplica las rutas, y “trying each of these paths makes type checking increasingly expensive”.37
La solución colapsa el árbol. “The most common set of builders now share a single initializer, leaving just one, straightforward path. This is possible because multiple different builder types have been unified under a single builder: ContentBuilder!”.37 Apple lo posiciona como el comienzo de un arco más largo: “This is a step towards enabling unified builders across all of SwiftUI’s APIs”.37
Dos propiedades hacen que ContentBuilder sea seguro de adoptar ahora en lugar de más tarde. No conlleva costo de objetivo de despliegue: “ContentBuilder can be used with any minimum deployment target, because under the hood, it’s an evolution of the existing ViewBuilder”.37 Y la ganancia aterriza en tiempo de compilación sin importar lo que envíes: “ContentBuilder provides a substantial improvement in type checking performance in SwiftUI when building using Xcode 27; whether you’re targeting the 2027 releases, or previous releases as well”.37 La documentación de Apple confirma la retrocompatibilidad en su declaración: ContentBuilder es un typealias, y su disponibilidad figura hasta iOS 13.0 y macOS 10.15, descrito como “A custom parameter attribute that constructs views and other content types from closures”.38
Holly Borla, gerente de ingeniería de Swift, corroboró el lado del compilador en su entrevista de cierre de la WWDC26. El error “is a fallback in the compiler’s type checker”, explicó, y el equipo redujo dónde aparece: “This year we focused a lot on mitigating that error in nested closures and Swift UI view bodies, which is a really common place to see it”.40 Agregó que el trabajo continúa de forma abierta: “there’s still some more work to be done and you can follow along with that through the Open Source Swift project”.40
El equipo de SwiftUI le dio al cambio una segunda dimensión en un group lab. Parafraseado de una grabación transcrita localmente del SwiftUI Group Lab de la WWDC 2026, el equipo describió las antiguas sobrecargas de builders por tipo como algo que había limitado su propia superficie de API: cada lugar donde querían agregar un builder empeoraba la verificación de tipos, así que se contuvieron, dejando ForEach y similares utilizables en menos posiciones de las que les hubiera gustado.39 La unificación levanta ese techo. El equipo también señaló que el builder unificado ahora se puede usar fuera de las vistas, así puedes ensamblar DSLs personalizados al estilo de SwiftUI a partir de tus propios bloques de construcción en lugar de solo vistas.39 La ganancia del compilador es el titular; el margen de diseño es la consecuencia más silenciosa.
Prioridades de adopción
Una versión tan amplia recompensa el triaje. Recurre a estas primero.
- Reemplaza el reordenamiento escrito a mano. Si mantienes la aritmética de índices de
onMove,reorderContainer(for:isEnabled:move:)másreorderable()es una eliminación neta de código y una mejor interacción (la señal de marcador de posición es del sistema, no tuya).23 Para apps con muchas listas, la API de reordenamiento carga el mayor peso de la versión. - Adopta las alertas con binding de error.
alert(error:actions:message:)elimina la struct envoltorio de error personalizada de cada pantalla que expone fallos, y unLocalizedErrorque ya tienes ahora titula su propia alerta.16 Bajo esfuerzo, legibilidad inmediata. - Cambia los grandes orígenes de arrastre a contenedores perezosos. Cualquier lista de más de unos pocos cientos de filas arrastrables se beneficia de
dragContainer(for:itemID:in:_:)másdraggable(containerItemID:containerNamespace:), porque el framework deja de materializar cargas útiles que quizá nunca use.45 - Dale a tus barras de herramientas una historia de prioridad. Si tu barra de herramientas alguna vez se desborda en ancho compacto,
visibilityPriority(_:),topBarPinnedTrailingyToolbarOverflowMenute permiten decidir qué sobrevive en lugar de aceptar los valores predeterminados del framework.131415 - Migra las apps de documentos deliberadamente, no por reflejo. La separación
ReadableDocument/WritableDocumentes el modelo correcto, pero es un cambio mayor que los demás; adóptalo cuando ya estés tocando la capa de documentos, y apóyate enFileWrapperDocumentReader/FileWrapperDocumentWriterpara el caso pequeño y mediano en lugar de implementar los protocolos de lector y escritor a mano.671011
El hilo conductor: adopta las adiciones que eliminan código que mantienes, posterga las que reestructuran código que ya funciona.
Preguntas frecuentes
¿Cómo hago reordenable una lista de SwiftUI en iOS 27?
Declara reorderContainer(for:isEnabled:move:) sobre el contenedor y aplica reorderable() al DynamicViewContent (normalmente un ForEach) dentro de él. El closure move del contenedor recibe un ReorderDifference que aplicas a tu modelo; el framework maneja el gesto de arrastre, el levantamiento y el marcador de posición que señala la posición de soltado.23 Usa la sobrecarga reorderContainer(for:in:isEnabled:move:) con un tipo de identificador de colección cuando un contenedor alberga múltiples colecciones.27
¿Cuál es la diferencia entre draggable(containerItemID:) y el antiguo draggable?
draggable(containerItemID:containerNamespace:) solo transporta el identificador del elemento, no la carga útil, así funciona de forma perezosa dentro de un dragContainer(for:itemID:in:_:): el framework solicita los elementos arrastrados reales solo cuando comienza el arrastre y no tiene que renderizar una vista para leer su carga útil.45 Eso lo convierte en la opción correcta para colecciones grandes o cargadas de forma perezosa donde producir cada carga útil por adelantado sería trabajo desperdiciado.
¿En qué se diferencia el nuevo modelo de documento de SwiftUI de FileDocument?
iOS 27 separa la lectura y la escritura en protocolos distintos, ReadableDocument y WritableDocument, con DocumentReader/DocumentWriter haciendo el trabajo en disco y un typealias Document para un tipo que es tanto legible como escribible.67 Para documentos pequeños y medianos que no necesitan lógica personalizada, FileWrapperDocumentReader y FileWrapperDocumentWriter proporcionan la implementación; URLDocumentConfiguration describe un documento abierto.101112 La separación permite que una función de solo visualización se conforme solo al lado de lectura.
¿Puedo mostrar una alerta directamente desde un Error en SwiftUI ahora?
Sí. alert(error:actions:message:) y alert(error:actions:) toman un Binding<E?> donde E : Error. Cuando el error vinculado no es nil, el sistema presenta la alerta, y si el error se conforma a LocalizedError, el título se infiere de su errorDescription; de lo contrario, usa la descripción localizada.1619 Ya no envuelves el error en una struct identifiable personalizada.
¿Cómo controlo qué elementos de la barra de herramientas desaparecen cuando el espacio es escaso?
Usa visibilityPriority(_:) sobre tu ToolbarContent para clasificar los elementos: los elementos de menor prioridad se mueven al menú de desbordamiento antes que los de mayor prioridad a medida que el espacio se encoge.15 Usa topBarPinnedTrailing para fijar un control al borde final de modo que solo se mueva al desbordamiento cuando la búsqueda está activa y no hay espacio, y ToolbarOverflowMenu para declarar acciones que siempre viven en el menú de desbordamiento.1314
¿Puede AsyncImage enviar encabezados personalizados o establecer una política de caché en iOS 27?
Sí. La familia AsyncImage(request:scale:) toma un URLRequest, que transporta encabezados, política de caché e intervalo de tiempo de espera; Apple señala que puedes especificar la política de caché y el tiempo de espera a través de la solicitud.2032 Para compartir una URLSession configurada (para autenticación o una caché personalizada) entre las instancias de AsyncImage en un subárbol, aplica asyncImageURLSession(_:).34
El cluster completo de Apple Ecosystem: el sustrato de SwiftUI (result builders, tipos opacos, el árbol de vistas con tipos de valor); los patrones de Liquid Glass en los que entra el comportamiento de minimización de la barra de herramientas de iOS 27; los internals de @Observable que impulsan la capa de estado bajo cada vista de esta entrada; y la superficie paralela de App Intents en iOS 27 para ejecución en segundo plano, sincronización y Spotlight. El hub está en la Serie Apple Ecosystem. Para un contexto más amplio de iOS con agentes de IA, consulta la guía de iOS Agent Development.
Referencias
-
Documentación para desarrolladores de Apple: SwiftUI. La referencia del framework que cubre vistas, listas, documentos, barras de herramientas y las adiciones de iOS 27 descritas aquí. ↩
-
Documentación para desarrolladores de Apple:
reorderContainer(for:isEnabled:move:)(iOS 27.0 beta). Define un contenedor que permite reordenar sus elementos; la conveniencia de colección única que entrega unReorderDifferencea su closuremove. ↩↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
reorderable()(iOS 27.0 beta). Habilita que las vistas deDynamicViewContentse reordenen cuando se usan dentro del alcance de un contenedor de reordenamiento. ↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
dragContainer(for:itemID:in:_:)(iOS 27.0 beta; macOS 26.0). Un contenedor con vistas arrastrables; toma unKeyPathal identificador de cada elemento y un closure de carga útil sobre los identificadores arrastrados. ↩↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
draggable(containerItemID:containerNamespace:)(iOS 27.0 beta; macOS 26.0). Activa una vista como origen de arrastre dentro de un contenedor de arrastre, suministrando solo un identificador para que el contenedor funcione de forma perezosa. ↩↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
ReadableDocument(iOS 27.0 beta). “A type that you use to read documents from file”. Declarado comoprotocol ReadableDocument : AnyObject; para lectura-escritura, conforma también aWritableDocumento usa el typealiasDocument. ↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
WritableDocument(iOS 27.0 beta). “A type that you use to write documents to file”. Declarado comoprotocol WritableDocument : AnyObject; conforma junto aReadableDocumentpara admitir el guardado. ↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
DocumentReader(iOS 27.0 beta). “Implements logic of reading documents from disk”. Declarado comoprotocol DocumentReader<Snapshot>. ↩↩ -
Documentación para desarrolladores de Apple:
DocumentWriter(iOS 27.0 beta). “Implements logic of writing documents to disk”. Declarado comoprotocol DocumentWriter<Snapshot>. ↩↩ -
Documentación para desarrolladores de Apple:
FileWrapperDocumentReader(iOS 27.0 beta). Un lector de documentos respaldado por un file wrapper; eficiente para documentos de tamaño pequeño y mediano que no necesitan lógica de lectura personalizada. ↩↩↩↩ -
Documentación para desarrolladores de Apple:
FileWrapperDocumentWriter(iOS 27.0 beta). Un escritor de documentos respaldado por un file wrapper; eficiente para documentos de tamaño pequeño y mediano que no necesitan lógica de escritura personalizada. ↩↩↩↩ -
Documentación para desarrolladores de Apple:
URLDocumentConfiguration(iOS 27.0 beta). “A set of settings and properties of an open document”. Declarado como@MainActor final class URLDocumentConfiguration. ↩↩↩ -
Documentación para desarrolladores de Apple:
ToolbarOverflowMenu(iOS 27.0 beta). “The overflow menu of a toolbar”. Declarado comononisolated struct ToolbarOverflowMenu<Content> where Content : View; en iOS y visionOS el contenido se coloca en el menú de desbordamiento de la barra de navegación. ↩↩↩↩ -
Documentación para desarrolladores de Apple:
topBarPinnedTrailing(iOS 27.0 beta). “A placement that pins the item to the trailing edge of the toolbar”. Los elementos fijados solo se mueven al menú de desbordamiento cuando la búsqueda está activa y no hay suficiente espacio. ↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
visibilityPriority(_:)(iOS 27.0 beta). “Defines the visibility priority for a toolbar item”. Cuando el espacio de la barra de herramientas es limitado, los elementos de menor prioridad se mueven al menú de desbordamiento antes que los de mayor prioridad. ↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
alert(error:actions:message:)(iOS 27.0 beta). “Presents an alert with a message when an error is present”. El título se infiere delerrorDescriptiondel error si es unLocalizedError; de lo contrario, de la descripción localizada. ↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
alert(_:item:actions:)(iOS 27.0 beta). “Presents an alert using the given data to produce the alert’s content and a text view as a title”. Para que aparezca la alerta,datano debe sernil. ↩↩ -
Documentación para desarrolladores de Apple:
alert(_:item:actions:message:)(iOS 27.0 beta). La sobrecarga de alerta basada en elementos con un constructor de mensaje; los datos deben no ser nil y los cambios posteriores a la presentación se ignoran. ↩↩↩ -
Documentación para desarrolladores de Apple:
alert(error:actions:)(iOS 27.0 beta). “Presents an alert when an error is present”. La sobrecarga con binding de error sin un constructor de mensaje. ↩↩↩ -
Documentación para desarrolladores de Apple:
init(request:scale:)(iOS 27.0 beta). “Loads and displays an image from the specified URL load request”. Declarado comoinit(request: URLRequest, scale: CGFloat = 1) where Content == Image; muestra un marcador de posición hasta que la carga se completa. ↩↩↩↩ -
Documentación para desarrolladores de Apple:
swipeActions(edge:allowsFullSwipe:content:onPresentationChanged:)(iOS 27.0 beta). El closure se llama contruecuando las acciones de deslizamiento de una fila se vuelven visibles yfalsecuando se descartan. ↩↩ -
Documentación para desarrolladores de Apple:
swipeActionsContainer()(iOS 27.0 beta). Coordina el descarte de acciones de deslizamiento y la exclusión mutua entre filas en unScrollViewo contenedor similar; aplicarlo a unaListno tiene efecto. ↩↩ -
Documentación para desarrolladores de Apple:
crossFade(iOS 27.0 beta). “A navigation transition that cross-fades between the appearing view and the disappearing view”. Especificada en un sheet, aparece con fundido sobre el contenido en lugar de moverse hacia arriba para cubrirlo. ↩↩ -
Documentación para desarrolladores de Apple:
prominent(iOS 27.0 beta). “The prominent role”. Proporciona un tratamiento visual prominente a una pestaña en las barras de pestañas compatibles; sin una pestaña.prominentexplícita, una pestaña de rol.searchpuede recibirlo de forma predeterminada. ↩↩ -
Documentación para desarrolladores de Apple:
UIHostingSceneDelegate(iOS 26.0; tvOS 27.0 beta). “ExtendsUISceneDelegateto bridge SwiftUI scenes”. Declara escenas de SwiftUI para activar desde UIKit en la propiedad estáticarootScenede la clase que se conforma. ↩↩↩ -
Documentación para desarrolladores de Apple:
GestureInputKinds(iOS 27.0 beta). “An option set that specifies which input kinds a gesture should recognize”. ↩↩ -
Documentación para desarrolladores de Apple:
reorderContainer(for:in:isEnabled:move:)(iOS 27.0 beta). “Defines a container that allows its items to be reordered”. La sobrecarga multicolección, indexada por un tipo de identificador de colección; úsala cuando un contenedor alberga más de una colección. ↩↩↩ -
Documentación para desarrolladores de Apple:
fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:onCancellation:)(iOS 27.0 beta). Presenta un diálogo del sistema para exportar unWritableDocumentcuyo destino del escritor esURL; el diálogo aparece solo cuandodocumentno es nil. ↩↩ -
Documentación para desarrolladores de Apple:
toolbarMinimizeBehavior(_:for:)(iOS 27.0 beta). “Sets the minimize behavior for the specified bars”. Habilita la minimización de la barra de herramientas en respuesta al desplazamiento; la ubicación admitida es la barra de navegación, y una barra de pestañas superior integrada se minimiza con ella. ↩↩ -
Documentación para desarrolladores de Apple:
confirmationDialog(_:item:titleVisibility:actions:message:)(iOS 27.0 beta). Presenta un diálogo de confirmación con un mensaje usando datos para producir el contenido del diálogo y una vista de texto para el mensaje. ↩ -
Documentación para desarrolladores de Apple:
confirmationDialog(_:item:titleVisibility:actions:)(iOS 27.0 beta). El diálogo de confirmación basado en elementos sin un constructor de mensaje. ↩ -
Documentación para desarrolladores de Apple:
init(request:scale:transaction:content:)(iOS 27.0 beta). “Loads and displays a modifiable image from the specified URL load request in phases”. Puedes especificar la política de caché y el intervalo de tiempo de espera a través de la solicitud. ↩↩ -
Documentación para desarrolladores de Apple:
init(request:scale:content:placeholder:)(iOS 27.0 beta). “Loads and displays a modifiable image from the specified URL load request using a custom placeholder until the image loads”. ↩ -
Documentación para desarrolladores de Apple:
asyncImageURLSession(_:)(iOS 27.0 beta). “A modifier that adds a URL session for asynchronous images contained in the view to use when fetching image data”. ↩↩ -
Apple, sesión 269 de la WWDC26, “What’s new in SwiftUI”. developer.apple.com/videos/play/wwdc2026/269. La sesión enmarca la versión en torno a una apariencia refinada, una nueva API de documentos, nuevas formas de interactuar y mejoras de rendimiento. ↩
-
Apple, sesión 271 de la WWDC26, “Code-along: Build powerful drag and drop in SwiftUI”. developer.apple.com/videos/play/wwdc2026/271. El código de reordenamiento se muestra moviéndose sin cambios entre una
Listy unaLazyVGrid, ya que la API reordenable funciona con cualquier contenedor que admita arrastrar y soltar. ↩ -
Apple, sesión 269 de la WWDC26, “What’s new in SwiftUI”. developer.apple.com/videos/play/wwdc2026/269. Fuente del error “unable to type-check this expression in reasonable time”, el árbol de decisiones de sobrecarga de
Section/Group/ForEach, la unificación de los builders comunes bajoContentBuilder, el paso hacia builders unificados en todo SwiftUI, el soporte de cualquier objetivo de despliegue mínimo como una evolución deViewBuilder, y la mejora de verificación de tipos de Xcode 27 a través de las versiones de 2027 y anteriores. ↩↩↩↩↩↩ -
Documentación para desarrolladores de Apple:
ContentBuilder. “A custom parameter attribute that constructs views and other content types from closures”. Declarado como un typealias, con disponibilidad que figura hasta iOS 13.0 y macOS 10.15. ↩ -
Apple, sesión 8006 de la WWDC26, “SwiftUI Group Lab”. developer.apple.com/videos/play/wwdc2026/8006. Parafraseado de una grabación transcrita localmente del SwiftUI Group Lab de la WWDC 2026; Apple no publica subtítulos oficiales para los labs. Fuente de que las sobrecargas de builders por tipo habían limitado la propia superficie de API del equipo (cada adición empeoraba la verificación de tipos) y de que el builder unificado se puede usar fuera de las vistas para habilitar DSLs personalizados al estilo de SwiftUI. ↩↩
-
Apple, sesión 400 de la WWDC26, “Dub Dub Daily: Day 5”, transcripción oficial. Holly Borla, gerente de ingeniería de Swift, en la entrevista de cierre con Jeff; fuente de la caracterización “fallback in the compiler’s type checker”, el enfoque en closures anidados y cuerpos de vista de SwiftUI, y el trabajo en curso de Open Source Swift. ↩↩