Programación en Swift y SwiftUI para iOS Developers

Cómo añadir una barra de herramientas a un teclado en SwiftUI

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

  1. @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.
  2. .keyboardType(.numberPad): Invoca el teclado numérico, el cual carece de un botón de retorno nativo.
  3. .toolbar y ToolbarItemGroup(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.
  4. Spacer(): En SwiftUI, el Spacer empuja 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 .keyboard placement? En macOS, SwiftUI es lo suficientemente inteligente como para ignorar ToolbarItemGroup(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 tecla Tab o Enter de 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 .keyboard no 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.

Leave a Reply

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

Previous Article

Cómo compartir contenido a una historia de Instagram con SwiftUI

Related Posts