En el competitivo mundo del desarrollo de aplicaciones, la experiencia de usuario (UX) es el factor que separa a las aplicaciones mediocres de las verdaderamente excepcionales. Como iOS Developer, te habrás enfrentado a un problema clásico y frustrante: cuando un usuario abre el teclado numérico (.numberPad) o el de teléfono (.phonePad), no hay un botón nativo de “Aceptar” o “Intro” para cerrarlo. El teclado se queda atascado en la pantalla, arruinando la navegación.
Afortunadamente, la programación Swift ha evolucionado enormemente. En este tutorial, vamos a sumergirnos en cómo añadir una barra de herramientas a un teclado con SwiftUI, una técnica fundamental que todo desarrollador debe dominar. Además, no solo nos limitaremos al iPhone; exploraremos cómo este código de Swift se comporta dentro de Xcode al compilar para macOS y watchOS, demostrando el verdadero poder de SwiftUI.
1. El problema histórico: De UIKit a SwiftUI
En la época dorada de UIKit, añadir botones sobre el teclado requería crear un UIToolbar, inicializar botones (UIBarButtonItem), manejar los selectores con @objc y asignar esa barra a la propiedad inputAccessoryView del UITextField. Era un proceso tedioso y propenso a errores de memoria si no se gestionaban bien las referencias.
Con la llegada de SwiftUI, Apple introdujo un paradigma declarativo. Sin embargo, en las primeras versiones (iOS 13 y 14), replicar el comportamiento de inputAccessoryView requería envolver componentes de UIKit usando UIViewRepresentable.
Todo esto cambió a partir de iOS 15. Apple introdujo el modificador .toolbar con el placement .keyboard, permitiendo a cualquier iOS Developer crear barras de herramientas de teclado con apenas tres líneas de código.
2. Implementación Básica en Xcode
Vamos a abrir Xcode y crear un nuevo proyecto usando SwiftUI. Nuestro objetivo inicial es crear un formulario simple donde el usuario pueda ingresar su edad (requiriendo un teclado numérico) y proporcionar un botón para ocultar el teclado.
El Código Fundamental
import SwiftUI
struct SimpleKeyboardView: View {
@State private var userAge: String = ""
@FocusState private var isInputActive: Bool
var body: some View {
NavigationStack {
Form {
Section(header: Text("Datos Personales")) {
TextField("Introduce tu edad", text: $userAge)
.keyboardType(.numberPad)
// Vinculamos el estado de foco del teclado
.focused($isInputActive)
}
}
.navigationTitle("Perfil")
// Aquí es donde ocurre la magia
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer() // Empuja el botón hacia la derecha
Button("Cerrar") {
isInputActive = false // Oculta el teclado
}
}
}
}
}
}
Desglosando la Programación Swift
@FocusState: Esta es una de las adiciones más potentes a la programación Swift moderna. Nos permite rastrear y modificar programáticamente qué campo de texto tiene el foco del sistema..keyboardType(.numberPad): Invoca el teclado numérico, el cual carece de un botón de retorno nativo..toolbaryToolbarItemGroup(placement: .keyboard): Le indica a SwiftUI que los elementos dentro de este grupo no deben ir en la barra de navegación superior, sino anclados en la parte superior del teclado virtual.Spacer(): En SwiftUI, elSpacerempuja el contenido. Al ponerlo antes del botón, nos aseguramos de que el botón “Cerrar” se alinee a la derecha, siguiendo las convenciones de diseño (Human Interface Guidelines) de Apple.
3. Manejo de Múltiples Campos de Texto (Formularios Avanzados)
Saber como añadir una barra de herramientas a un teclado con SwiftUI es solo el principio. En el mundo real, un iOS Developer rara vez trabaja con un solo campo de texto. Los formularios de registro o pago tienen múltiples entradas, y los usuarios esperan poder saltar de un campo a otro usando flechas en el teclado.
Vamos a elevar nuestro nivel de Swift creando una navegación entre campos fluida.
Definiendo los Campos con un Enum
Para manejar múltiples focos, la mejor práctica en SwiftUI es usar una enumeración (enum) que se conforme a Hashable.
import SwiftUI
struct AdvancedFormView: View {
enum FormField: Hashable {
case firstName
case lastName
case phoneNumber
}
@State private var firstName: String = ""
@State private var lastName: String = ""
@State private var phoneNumber: String = ""
// Nuestro FocusState ahora rastrea el enum
@FocusState private var focusedField: FormField?
var body: some View {
NavigationStack {
Form {
TextField("Nombre", text: $firstName)
.focused($focusedField, equals: .firstName)
.textContentType(.givenName)
.submitLabel(.next)
TextField("Apellidos", text: $lastName)
.focused($focusedField, equals: .lastName)
.textContentType(.familyName)
.submitLabel(.next)
TextField("Teléfono", text: $phoneNumber)
.focused($focusedField, equals: .phoneNumber)
.keyboardType(.phonePad)
}
.navigationTitle("Registro")
.onSubmit {
// Maneja el botón "Intro" del teclado estándar
switch focusedField {
case .firstName:
focusedField = .lastName
case .lastName:
focusedField = .phoneNumber
default:
focusedField = nil
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
// Botones de navegación (Anterior / Siguiente)
Button(action: previousField) {
Image(systemName: "chevron.up")
}
.disabled(focusedField == .firstName)
Button(action: nextField) {
Image(systemName: "chevron.down")
}
.disabled(focusedField == .phoneNumber)
Spacer()
// Botón de confirmación
Button("Listo") {
focusedField = nil
}
.bold()
}
}
}
}
// Métodos auxiliares de navegación
private func nextField() {
switch focusedField {
case .firstName: focusedField = .lastName
case .lastName: focusedField = .phoneNumber
default: break
}
}
private func previousField() {
switch focusedField {
case .phoneNumber: focusedField = .lastName
case .lastName: focusedField = .firstName
default: break
}
}
}
Análisis de Arquitectura
Con este código, hemos creado una experiencia premium. Hemos utilizado Image(systemName:) para integrar iconos nativos de SF Symbols, creando los clásicos botones de “Arriba/Abajo” que los usuarios de iOS esperan al rellenar formularios. Además, mediante la propiedad .disabled(), evitamos que el usuario intente navegar “arriba” si ya está en el primer campo, previniendo errores lógicos en la programación Swift.
4. El Comportamiento Multiplataforma: macOS y watchOS
La promesa de SwiftUI es “Aprender una vez, aplicar en cualquier lugar”. Sin embargo, un iOS Developer senior sabe que el contexto importa. ¿Qué sucede con nuestro código cuando abrimos este mismo proyecto en Xcode y cambiamos el target a macOS o watchOS?
macOS
En los ordenadores Mac, los usuarios tienen un teclado físico. No existe el concepto de un “teclado virtual en pantalla” que se desliza desde abajo (salvo en casos de accesibilidad muy específicos).
- ¿Qué pasa con
.keyboardplacement? En macOS, SwiftUI es lo suficientemente inteligente como para ignorarToolbarItemGroup(placement: .keyboard). Tu aplicación compilará perfectamente, pero esos botones de “Listo” o las flechas simplemente no se renderizarán, ya que el usuario puede usar la teclaTaboEnterde su teclado físico. Esto demuestra la elegancia de SwiftUI: no necesitas plagar tu código con directivas de compilación#if os(iOS)solo para evitar que la interfaz se rompa.
watchOS
El Apple Watch es un dispositivo con una pantalla extremadamente limitada. El ingreso de texto en watchOS no se hace a través de un teclado numérico emergente de la misma manera que en el iPhone, sino a través de pantallas dedicadas (Scribble, Dictado o el teclado QWERTY completo en los modelos Ultra/Series 7+).
- Comportamiento: Al igual que en Mac, el placement
.keyboardno es aplicable en watchOS. Si quieres ofrecer botones de “Aceptar” en watchOS, debes integrarlos directamente en la vista principal o usar un botón estándar debajo del campo de texto. Para mantener tu código limpio si compartes vistas, aquí sí es recomendable usar el encapsulamiento multiplataforma:
#if os(iOS)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Listo") { focusedField = nil }
}
}
#endif
5. Mejores Prácticas y Rendimiento en Xcode
Al aprender como añadir una barra de herramientas a un teclado con SwiftUI, es crucial seguir las directrices de rendimiento y accesibilidad.
1. Extrayendo la Barra de Herramientas
Si tienes muchos formularios en tu aplicación, no copies y pegues el código del ToolbarItemGroup. En Swift, puedes crear un modificador de vista personalizado (ViewModifier) o simplemente una función de extensión para reutilizar esta barra de herramientas, manteniendo tu código DRY (Don’t Repeat Yourself).
2. Accesibilidad (VoiceOver)
Asegúrate de que los botones en tu barra de herramientas sean accesibles. Aunque los textos como “Listo” son leídos automáticamente por VoiceOver, si usas iconos (como las flechas), debes añadir etiquetas de accesibilidad explícitas:
Button(action: nextField) {
Image(systemName: "chevron.down")
}
.accessibilityLabel("Siguiente campo de texto")
3. Evitar conflictos de estado
Asegúrate de que tu variable @FocusState sea de acceso private. Modificar el estado de foco desde múltiples lugares inesperados en la arquitectura de tu app puede causar comportamientos erráticos en el teclado, donde se oculta y reaparece rápidamente (un error visual común en versiones tempranas de iOS 15).
6. Conclusión
El ecosistema de Apple evoluciona rápidamente, pero dominar los fundamentos de la interacción del usuario es lo que consolida tu carrera como iOS Developer. Saber exactamente como añadir una barra de herramientas a un teclado con SwiftUI es una habilidad imprescindible que transforma un formulario tosco en una experiencia fluida y profesional.
A través de la programación Swift, hemos superado las limitaciones de las APIs antiguas, utilizando herramientas modernas en Xcode como @FocusState y el modificador .toolbar. Además, al entender el contexto de macOS y watchOS, estás pensando no solo como un programador de móviles, sino como un arquitecto de software de todo el ecosistema de Apple.








