Programación en Swift y SwiftUI para iOS Developers

Cómo añadir botones a una toolbar en SwiftUI

En el desarrollo de aplicaciones móviles modernas, el espacio en pantalla es el activo más valioso. La interfaz de usuario debe ser limpia, intuitiva y, sobre todo, funcional. Aquí es donde entra en juego la Toolbar (Barra de Herramientas). Ya no se trata solo de esa barra en la parte superior donde vive el título; en SwiftUI, la Toolbar es un sistema de gestión de acciones inteligente y semántico que se adapta a iOS, iPadOS, macOS y watchOS.

Si vienes de UIKit, recordarás los días de configurar navigationItem.rightBarButtonItem y lidiar con el ciclo de vida del controlador. SwiftUI cambia el juego con el modificador declarativo .toolbar.

En este tutorial exhaustivo de 2000 palabras, vamos a diseccionar cómo añadir botones, gestionar su ubicación, controlar su estado y estilizar la barra de herramientas en Xcode para crear experiencias de usuario de primer nivel.


1. El Fundamento: NavigationStack

Antes de poder colocar un solo botón, necesitamos un contexto. Las barras de herramientas en SwiftUI no flotan en el vacío; generalmente viven dentro de una estructura de navegación.

Hasta iOS 15 usábamos NavigationView, pero el estándar moderno (y el que debes usar en 2025) es NavigationStack. Este contenedor proporciona el “lienzo” superior (la barra de navegación) y la capacidad de apilar vistas.

Configuración Inicial del Proyecto

Abre Xcode y crea un nuevo proyecto SwiftUI. En tu ContentView.swift, establece la estructura base:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack {
                Image(systemName: "globe")
                    .imageScale(.large)
                    .foregroundStyle(.tint)
                Text("Bienvenido al Tutorial de Toolbar")
            }
            .navigationTitle("Inicio")
            .navigationBarTitleDisplayMode(.large)
        }
    }
}

Al ejecutar esto, verás una barra de navegación vacía con el título “Inicio”. Ahora estamos listos para poblarla.


2. Anatomía del Modificador .toolbar

El modificador .toolbar se aplica a la vista dentro del stack de navegación, no al stack en sí. Esto es crucial: la toolbar pertenece al contenido, no al contenedor.

Para añadir un botón, utilizamos la estructura ToolbarItem.

.toolbar {
    ToolbarItem(placement: .topBarTrailing) {
        Button("Guardar") {
            print("Acción de guardar ejecutada")
        }
    }
}

¿Por qué ToolbarItem?

Podrías simplemente lanzar un Button dentro de .toolbar y a veces funcionaría, pero ToolbarItem te da control explícito sobre el placement (ubicación). SwiftUI utiliza un sistema semántico para determinar dónde colocar los elementos.


3. Dominando las Ubicaciones (ToolbarItemPlacement)

El parámetro placement es donde ocurre la magia. En lugar de pensar en píxeles (x, y), pensamos en roles. Veamos las opciones más importantes y cuándo usarlas.

A. .topBarTrailing y .topBarLeading (La Cima)

Estas son las ubicaciones estándar en la barra de navegación superior.

  • .topBarLeading: El borde inicial (Izquierda en idiomas LTR). Ideal para botones de “Cancelar”, “Cerrar” o menús laterales (Hamburger menu).
  • .topBarTrailing: El borde final (Derecha en idiomas LTR). Ideal para la acción principal de la pantalla: “Guardar”, “Editar”, “Añadir”.

Nota de compatibilidad: Si soportas versiones anteriores a iOS 17, verás navigationBarLeading y navigationBarTrailing. Apple los ha renombrado a topBar... para generalizar su uso, pero el comportamiento es idéntico.

.toolbar {
    ToolbarItem(placement: .topBarLeading) {
        Button("Cancelar", role: .cancel) { }
    }
    ToolbarItem(placement: .topBarTrailing) {
        Button(action: saveDocument) {
            Label("Guardar", systemImage: "square.and.arrow.down")
        }
    }
}

B. .principal (El Centro de Atención)

Por defecto, el centro de la barra de navegación muestra el navigationTitle. Sin embargo, a veces necesitas algo más complejo: un logotipo de marca, un selector de fecha o un Picker (segment control).

El placement .principal reemplaza el título estándar con tu vista personalizada.

ToolbarItem(placement: .principal) {
    VStack {
        Text("Documento 1").font(.headline)
        Text("Editado hace 5 min").font(.caption2).foregroundStyle(.secondary)
    }
}

C. .bottomBar (La Barra Inferior)

Muchos desarrolladores olvidan que SwiftUI puede generar automáticamente una barra de herramientas inferior (similar a la de Safari en iOS) simplemente cambiando el placement. Esto es útil para mover acciones secundarias y dejar la parte superior limpia.

ToolbarItem(placement: .bottomBar) {
    HStack {
        Button(action: {}) { Image(systemName: "chevron.left") }
        Spacer() // Empuja los botones a los extremos
        Button(action: {}) { Image(systemName: "square.and.arrow.up") }
    }
}

D. .keyboard (El Truco de Productividad)

Este es, quizás, el placement más útil para formularios. Permite añadir botones directamente sobre el teclado virtual del sistema. Es perfecto para añadir un botón “Listo” (Done) en teclados numéricos que no tienen tecla de retorno.

ToolbarItem(placement: .keyboard) {
    HStack {
        Spacer()
        Button("Listo") {
            // Código para cerrar el teclado
            UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
        }
    }
}

4. Agrupando Botones: ToolbarItemGroup

Imagina que necesitas tres botones a la derecha: “Buscar”, “Filtrar” y “Perfil”. Si escribes tres ToolbarItem con el mismo placement .topBarTrailing, funcionará, pero el código será repetitivo.

Para esto existe ToolbarItemGroup. Agrupa múltiples vistas bajo una misma ubicación y deja que SwiftUI gestione el espaciado (padding) entre ellas.

.toolbar {
    ToolbarItemGroup(placement: .topBarTrailing) {
        Button(action: {}) { Image(systemName: "magnifyingglass") }
        Button(action: {}) { Image(systemName: "slider.horizontal.3") }
        
        Link(destination: URL(string: "https://apple.com")!) {
            Image(systemName: "person.circle")
        }
    }
}

Consejo de Diseño: Evita saturar la barra superior. Si tienes más de 2 o 3 acciones principales, considera moverlas a un menú contextual o a la barra inferior.


5. Diseño y Estilizado de Botones

Los botones en la toolbar no tienen por qué ser simples textos azules. SwiftUI nos da herramientas para estilizarlos y hacerlos más accesibles.

Uso de Label para Accesibilidad

En lugar de usar Image(systemName: "plus"), acostúmbrate a usar Label("Añadir", systemImage: "plus"). En la toolbar, SwiftUI mostrará solo el icono automáticamente para ahorrar espacio, pero VoiceOver leerá el texto “Añadir”. Es una ganancia de accesibilidad gratuita.

Cambiando el Color (.tint)

Puedes cambiar el color de los botones usando el modificador .tint.

Button("Borrar") { ... }
    .tint(.red)

Botones Prominentes

A veces quieres que la acción principal destaque visualmente, por ejemplo, un botón con fondo de color (“capsule”). Desde iOS 16, podemos aplicar estilos de botón dentro de la toolbar.

ToolbarItem(placement: .topBarTrailing) {
    Button("Comprar") { }
        .buttonStyle(.borderedProminent)
        .tint(.green)
        .clipShape(Capsule())
}

Nota: Úsalo con moderación. Un botón con borde en la barra de navegación llama mucho la atención.


6. Lógica de Estado: Habilitar y Deshabilitar Botones

Una toolbar estática es aburrida. Queremos que reaccione a lo que hace el usuario. Por ejemplo, el botón “Guardar” debería estar deshabilitado si el formulario está vacío.

struct FormularioView: View {
    @State private var nombre: String = ""
    @State private var guardando: Bool = false

    var body: some View {
        NavigationStack {
            Form {
                TextField("Nombre del producto", text: $nombre)
            }
            .navigationTitle("Nuevo Producto")
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    if guardando {
                        ProgressView()
                    } else {
                        Button("Guardar") {
                            simularGuardado()
                        }
                        .disabled(nombre.isEmpty) // Deshabilitado si no hay texto
                    }
                }
            }
        }
    }

    func simularGuardado() {
        guardando = true
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            guardando = false
            nombre = ""
        }
    }
}

En este ejemplo, usamos .confirmationAction (un alias semántico para la acción positiva a la derecha) y conectamos el modificador .disabled a nuestra variable de estado. También reemplazamos dinámicamente el botón por un ProgressViewmientras se realiza la acción.


7. Menús Desplegables en la Toolbar

Cuando el espacio es limitado, el componente Menu es tu mejor amigo. Permite agrupar acciones secundarias bajo un solo botón.

ToolbarItem(placement: .topBarTrailing) {
    Menu {
        Button("Duplicar", systemImage: "plus.square.on.square") { }
        Button("Renombrar", systemImage: "pencil") { }
        Divider()
        Button("Eliminar", systemImage: "trash", role: .destructive) { }
    } label: {
        Label("Opciones", systemImage: "ellipsis.circle")
    }
}

Esto despliega un menú nativo de iOS elegante y funcional, manteniendo tu interfaz limpia.


8. Personalización del Fondo de la Toolbar

Hasta hace poco, cambiar el color de fondo de la barra de navegación en SwiftUI era una pesadilla que requieria hacks de UIKit (UINavigationBar.appearance). Ahora, tenemos una API nativa: .toolbarBackground.

Visibilidad y Color

Por defecto, la barra es transparente y se vuelve translúcida al hacer scroll. Para forzar un color corporativo:

.toolbarBackground(Color.indigo, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
  • Color: Define el fondo (puede ser un ColorGradient o Material).
  • Visible: Fuerza que la barra se vea sólida incluso sin hacer scroll.
  • ColorScheme: .dark indica que el fondo es oscuro, por lo que SwiftUI pintará el texto y los botones de color blanco.

9. Ejemplo Completo: El Editor de Notas

Vamos a combinar todo lo aprendido en una pantalla realista. Imagina una app de notas donde podemos escribir, ver el recuento de caracteres y tener acciones de formato.

import SwiftUI

struct EditorNotasView: View {
    @State private var texto: String = "Escribe tu próxima gran idea..."
    @State private var esFavorito: Bool = false
    @FocusState private var focoTeclado: Bool

    var body: some View {
        NavigationStack {
            TextEditor(text: $texto)
                .padding()
                .focused($focoTeclado)
                .navigationTitle("Nota Rápida")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    // 1. Grupo a la izquierda: Cerrar teclado
                    ToolbarItem(placement: .topBarLeading) {
                        if focoTeclado {
                            Button("Hecho") {
                                focoTeclado = false
                            }
                        }
                    }

                    // 2. Grupo a la derecha: Favorito y Compartir
                    ToolbarItemGroup(placement: .topBarTrailing) {
                        Button {
                            withAnimation { esFavorito.toggle() }
                        } label: {
                            Image(systemName: esFavorito ? "star.fill" : "star")
                                .foregroundStyle(esFavorito ? .yellow : .primary)
                                .symbolEffect(.bounce, value: esFavorito) // Animación iOS 17
                        }

                        ShareLink(item: texto) {
                            Image(systemName: "square.and.arrow.up")
                        }
                    }

                    // 3. Barra inferior: Contador de caracteres
                    ToolbarItem(placement: .bottomBar) {
                        HStack {
                            Text("\(texto.count) caracteres")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                            Spacer()
                            Text("Última edición: Hoy")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                    }
                }
        }
    }
}

Desglose del Ejemplo Pro:

  1. Reactividad: El botón “Hecho” a la izquierda solo aparece si el teclado está activo (focoTeclado).
  2. Feedback Visual: El botón de estrella cambia de icono (fill vs outline) y de color. Además, usamos .symbolEffect(nuevo en iOS 17) para darle un rebote animado al pulsarlo.
  3. ShareLink: Usamos un componente nativo de SwiftUI dentro de la toolbar para compartir el texto.
  4. Barra Inferior Informativa: Usamos el espacio inferior para metadatos, equilibrando la interfaz.

Conclusión

La gestión de botones en la Toolbar de SwiftUI ha evolucionado para ser una herramienta increíblemente potente y flexible. Ya no se trata de colocar vistas en coordenadas; se trata de definir intenciones semánticas y dejar que el sistema optimice la experiencia para el usuario.

Recuerda los pilares clave:

  1. Usa siempre ToolbarItem con el placement correcto.
  2. Agrupa acciones relacionadas con ToolbarItemGroup o Menu.
  3. No olvides la accesibilidad usando Label.
  4. Aprovecha la .bottomBar y la barra de .keyboard para mejorar la ergonomía.

Con estas técnicas en tu arsenal, estás listo para crear interfaces de usuario profesionales, limpias y nativas que encantarán a tus usuarios.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

Cómo usar la Dynamic Island del iPhone en SwiftUI

Next Article

Cómo añadir Swipe Actions a una List en SwiftUI

Related Posts