Las barras de herramientas de SwiftUI siempre han tenido cierto comportamiento adaptativo. Podemos declarar varios ToolbarItem y dejar que el sistema decida cómo colocarlos según la plataforma, el tamaño de la ventana o el espacio disponible. El problema aparece cuando esa decisión automática no coincide con la importancia real de cada acción.
En una ventana amplia todo puede parecer correcto, pero al reducirla —o al ejecutar la misma interfaz en un iPhone— algunos botones terminan en el menú de desbordamiento. Hasta ahora teníamos pocas herramientas para indicarle a SwiftUI qué acciones debían conservarse a la vista y cuáles podían desaparecer primero.
Con iOS 27, iPadOS 27 y el resto de sistemas de la nueva generación, SwiftUI amplía considerablemente el control sobre las barras de herramientas. APIs como visibilityPriority(_:), ToolbarOverflowMenu y el nuevo placement topBarPinnedTrailing permiten definir una auténtica jerarquía entre las acciones de la interfaz.
El problema de dejar toda la adaptación en manos del sistema
Imaginemos una aplicación para gestionar tareas. En la pantalla de un proyecto queremos ofrecer acceso rápido a varias operaciones:
struct ProjectView: View {
var body: some View {
TaskList()
.navigationTitle("Lanzamiento")
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Filtrar", systemImage: "line.3.horizontal.decrease") {
// Mostrar filtros
}
Button("Ordenar", systemImage: "arrow.up.arrow.down") {
// Cambiar orden
}
Button("Archivar", systemImage: "archivebox") {
// Archivar proyecto
}
Button("Compartir", systemImage: "square.and.arrow.up") {
// Compartir proyecto
}
}
}
}
}
Mientras haya espacio suficiente veremos todas las acciones. Cuando la barra se estrecha, SwiftUI empieza a trasladar elementos al menú de desbordamiento.
El comportamiento es correcto desde el punto de vista del diseño adaptativo, pero existe una carencia importante: para nuestra aplicación quizá Compartir sea una acción esencial mientras que Ordenar sea totalmente secundaria. El sistema no conoce esa intención.
Aquí es donde entran las nuevas APIs.
visibilityPriority: decidir qué debe permanecer visible
El modificador visibilityPriority(_:) permite asignar una prioridad de visibilidad a los elementos de una barra de herramientas.
Cuando falta espacio, SwiftUI mueve primero al menú de desbordamiento los elementos con menor prioridad. Los que tengan una prioridad superior permanecen visibles durante más tiempo a medida que la ventana se hace más pequeña.
struct ProjectView: View {
var body: some View {
TaskList()
.navigationTitle("Lanzamiento")
.toolbar {
ToolbarItem {
Button("Ordenar", systemImage: "arrow.up.arrow.down") {
// Cambiar orden
}
}
ToolbarItem {
Button("Compartir", systemImage: "square.and.arrow.up") {
// Compartir proyecto
}
}
.visibilityPriority(.high)
}
}
}
En este caso, cuando la barra empiece a quedarse sin espacio, SwiftUI intentará conservar Compartir visible antes que Ordenar.
La prioridad predeterminada es automatic, por lo que no es necesario modificar todos los elementos de una barra. La idea no consiste en asignar .high a cada botón, porque entonces dejaríamos de expresar una jerarquía útil, sino en reservarla para aquellas acciones que realmente deben sobrevivir a los cambios de tamaño.
La prioridad también puede aplicarse a un ToolbarItemGroup, algo especialmente interesante cuando varias acciones forman una unidad conceptual:
ToolbarItemGroup {
Button("Anterior", systemImage: "chevron.backward") {
goToPreviousTask()
}
Button("Siguiente", systemImage: "chevron.forward") {
goToNextTask()
}
}
.visibilityPriority(.high)
De esta forma comunicamos que el grupo de navegación tiene más importancia que otras operaciones presentes en la barra.
ToolbarOverflowMenu: acciones que no necesitan ocupar espacio
Hay otro tipo de controles para los que la prioridad no es suficiente. Algunas acciones son útiles, pero no necesitan mostrarse nunca como botones independientes.
Para estos casos iOS 27 incorpora ToolbarOverflowMenu.
.toolbar {
ToolbarItem {
Button("Nueva tarea", systemImage: "plus") {
createTask()
}
}
ToolbarOverflowMenu {
Button("Duplicar proyecto", systemImage: "plus.square.on.square") {
duplicateProject()
}
Button("Exportar", systemImage: "square.and.arrow.up") {
exportProject()
}
Button("Eliminar completadas", systemImage: "trash") {
removeCompletedTasks()
}
}
}
El detalle importante es que el contenido de ToolbarOverflowMenu siempre se coloca en el menú de desbordamiento. No depende del tamaño actual de la barra ni espera a que falte espacio.
Esto permite separar dos conceptos que antes podían confundirse:
- Una acción con prioridad baja puede mostrarse en la barra mientras exista espacio y pasar al menú cuando sea necesario.
- Una acción dentro de
ToolbarOverflowMenuestá diseñada desde el principio para vivir en ese menú.
Esta distinción resulta muy útil en aplicaciones con muchas funciones. En lugar de llenar la interfaz con botones para cada posibilidad, podemos reservar el espacio visible para las acciones frecuentes y mantener las operaciones ocasionales accesibles desde el menú.
topBarPinnedTrailing: una acción realmente prioritaria
visibilityPriority(.high) expresa una preferencia, pero sigue formando parte del mecanismo normal de adaptación. Si necesitamos que una acción permanezca anclada al extremo derecho de la barra superior, SwiftUI añade el placement topBarPinnedTrailing.
.toolbar {
ToolbarItem {
Button("Buscar", systemImage: "magnifyingglass") {
showSearch = true
}
}
ToolbarItem(placement: .topBarPinnedTrailing) {
Button("Añadir", systemImage: "plus") {
createTask()
}
}
}
En iOS y visionOS, la barra superior corresponde a la barra de navegación. Un elemento colocado en .topBarPinnedTrailing queda fijado en su extremo final y recibe un tratamiento más fuerte que una simple prioridad alta.
Apple especifica que estos elementos solo pasan al menú de desbordamiento cuando hay una búsqueda activa y aun así no existe espacio suficiente. Por tanto, es una opción adecuada para acciones fundamentales como crear contenido, guardar, confirmar o compartir, dependiendo de la función principal de cada pantalla.
Conviene utilizarla con moderación. Si todo se considera imprescindible, la interfaz deja de tener una jerarquía clara y volvemos al mismo problema que intentábamos resolver.
Tres niveles de importancia en una misma toolbar
Las nuevas APIs funcionan especialmente bien cuando se combinan. Podemos pensar en una barra de herramientas con tres niveles:
- Acciones críticas que permanecen ancladas.
- Acciones importantes que deberían conservarse visibles mientras sea posible.
- Acciones secundarias que viven directamente en el menú.
struct ReadingListView: View {
var body: some View {
ArticleList()
.navigationTitle("Lecturas")
.toolbar {
ToolbarItemGroup {
Button("Sin leer", systemImage: "circle") {
showUnreadOnly()
}
Button("Favoritos", systemImage: "star") {
showFavorites()
}
}
.visibilityPriority(.high)
ToolbarOverflowMenu {
Button("Importar", systemImage: "square.and.arrow.down") {
importArticles()
}
Button("Exportar", systemImage: "square.and.arrow.up") {
exportArticles()
}
Button("Vaciar historial", systemImage: "clock.arrow.circlepath") {
clearHistory()
}
}
ToolbarItem(placement: .topBarPinnedTrailing) {
Button("Añadir artículo", systemImage: "plus") {
addArticle()
}
}
}
}
}
Lo interesante de este código es que no describe posiciones exactas para cada tamaño posible. Describe intenciones. SwiftUI sigue siendo responsable de adaptar la interfaz, pero ahora dispone de información suficiente para tomar mejores decisiones.
Ese enfoque encaja mucho mejor con la filosofía del framework que intentar calcular manualmente el ancho disponible o construir distintas barras para iPhone, iPad y Mac.
Espacios adaptativos con ToolbarSpacer
La organización de una barra no depende únicamente de qué elementos aparecen. También importa cómo se agrupan visualmente.
ToolbarSpacer permite introducir separaciones específicas dentro de una barra de herramientas. Puede utilizar un tamaño fijo definido por el sistema o comportarse de forma flexible.
.toolbar {
ToolbarItem {
Button("Atrás", systemImage: "chevron.backward") {
previousItem()
}
}
ToolbarItem {
Button("Adelante", systemImage: "chevron.forward") {
nextItem()
}
}
ToolbarSpacer(.fixed)
ToolbarItem {
Button("Marcar", systemImage: "bookmark") {
toggleBookmark()
}
}
}
Con .fixed, el sistema aplica una separación apropiada para el contexto. También existe .flexible, que puede crecer para ocupar el espacio disponible.
La diferencia respecto a un Spacer() convencional es conceptual: ToolbarSpacer forma parte de la estructura de la propia barra y entiende sus reglas de colocación, adaptación y personalización.
Minimizar la barra durante el scroll
La adaptación introducida en iOS 27 no se limita al ancho. SwiftUI también añade control sobre cómo se minimiza la barra de navegación mientras el usuario se desplaza por el contenido.
NavigationStack {
ScrollView {
LazyVStack {
ForEach(entries) { entry in
EntryRow(entry: entry)
}
}
.padding()
}
.navigationTitle("Diario")
.toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar)
}
Con .onScrollDown, la barra se minimiza al desplazarnos hacia abajo y recupera espacio para el contenido. La API también contempla comportamientos como .onScrollUp, .never y .automatic.
Además, SwiftUI ofrece APIs relacionadas para controlar cómo se restaura la barra y si la zona segura debe reajustarse mientras se minimiza. Esto resulta útil en interfaces con contenido a pantalla completa, fotografías, mapas o vídeo, donde un cambio continuo de la zona segura podría producir movimientos no deseados.
.toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar)
.toolbarMinimizationSafeAreaAdjustment(.disabled, for: .navigationBar)
En este caso, la barra puede minimizarse sin provocar que el contenido reajuste continuamente su zona segura.
Diseñar para ventanas redimensionables
Estas APIs son especialmente importantes en iPadOS y el nuevo iPhone Duo. Las interfaces modernas ya no deberían plantearse únicamente como una colección de tamaños fijos —iPhone, iPad vertical, iPad horizontal—, sino como ventanas que pueden adoptar muchos anchos diferentes.
Una barra con seis acciones puede caber perfectamente en un momento y quedarse sin espacio unos segundos después si el usuario redimensiona la ventana. Intentar detectar manualmente cada escenario genera una gran cantidad de lógica específica que además puede romperse con futuros cambios del sistema.
Con las nuevas herramientas, la estrategia puede ser mucho más sencilla:
- Identificar qué acción es esencial para la pantalla.
- Dar prioridad alta a los grupos utilizados constantemente.
- Mover las operaciones ocasionales a
ToolbarOverflowMenu. - Utilizar
topBarPinnedTrailingsolo para una acción que realmente deba permanecer accesible. - Dejar que SwiftUI resuelva el tamaño y la colocación final.
El resultado no es una interfaz menos controlada, sino una interfaz en la que controlamos la semántica en lugar de los píxeles.
Compatibilidad con versiones anteriores
Las nuevas APIs pertenecen a la generación de iOS 27, por lo que una aplicación cuyo despliegue mínimo sea anterior deberá mantener una alternativa para sistemas anteriores.
Una posibilidad es encapsular la definición de la barra y utilizar disponibilidad para aplicar la versión avanzada únicamente cuando el sistema lo permita.
struct ProjectScreen: View {
var body: some View {
ProjectContent()
.navigationTitle("Proyecto")
.modifier(ProjectToolbarModifier())
}
}
private struct ProjectToolbarModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 27.0, *) {
content.toolbar {
ToolbarItem {
Button("Filtrar", systemImage: "line.3.horizontal.decrease") { }
}
.visibilityPriority(.high)
ToolbarOverflowMenu {
Button("Exportar", systemImage: "square.and.arrow.up") { }
Button("Archivar", systemImage: "archivebox") { }
}
ToolbarItem(placement: .topBarPinnedTrailing) {
Button("Añadir", systemImage: "plus") { }
}
}
} else {
content.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Filtrar", systemImage: "line.3.horizontal.decrease") { }
Button("Añadir", systemImage: "plus") { }
}
}
}
}
}
No siempre será necesario duplicar toda la implementación. En muchos proyectos bastará con mantener una toolbar más sencilla en versiones anteriores y aprovechar la jerarquía adaptativa únicamente en los nuevos sistemas.
Una API más declarativa para un problema cada vez más importante
El cambio interesante de iOS 27 no es que SwiftUI permita meter botones en un menú. Eso ya se podía construir manualmente. Lo relevante es que la barra de herramientas adquiere nuevas reglas declarativas para describir cómo debe reaccionar cuando cambia el espacio disponible.
visibilityPriority(_:) comunica qué elementos queremos proteger frente al desbordamiento. ToolbarOverflowMenu identifica las acciones que deben permanecer siempre agrupadas. topBarPinnedTrailing reserva una posición privilegiada para controles esenciales. ToolbarSpacer ayuda a estructurar visualmente los grupos, mientras que las APIs de minimización permiten adaptar también la barra al desplazamiento vertical.
Todo ello reduce la necesidad de comprobar tamaños, clases de tamaño o plataformas para decidir manualmente qué botones mostrar. En lugar de construir una barra distinta para cada situación, podemos declarar la importancia relativa de sus componentes y dejar que SwiftUI adapte la presentación.
Para aplicaciones que deben funcionar en iPhone, iPad y ventanas redimensionables, este modelo resulta especialmente valioso: la barra deja de ser una colección rígida de botones y pasa a comportarse como una parte realmente adaptativa de la interfaz.