@FocusState — qué es, gestión del foco y el teclado en SwiftUI

Autor: IT Sectr Publicado: 2026-06-26 Tiempo de lectura: 9 min

@FocusState es un property wrapper en SwiftUI, presentado en iOS 15, que permite controlar programáticamente el foco de entrada en campos de texto y otros elementos. Antes de su aparición, los desarrolladores tenían que usar UIViewRepresentable para acceder a los métodos UIKit becomeFirstResponder y resignFirstResponder. @FocusState resuelve este problema de forma nativa: vinculas una propiedad a un campo mediante el modificador .focused(), tras lo cual establecer o quitar el foco se realiza con una simple asignación de valor. Según Apple Developer Documentation — FocusState (2025), @FocusState admite dos modos: Bool para gestión simple (foco activado o desactivado) y enum para varios campos, donde cada caso corresponde a un campo de entrada específico.

Puntos clave

  • @FocusState — un property wrapper para la gestión programática del foco en SwiftUI, disponible desde iOS 15.
  • Modo Bool — para un solo campo usa @FocusState var isFocused: Bool con .focused($isFocused).
  • Modo Enum — para varios campos usa un enum que conforme a FocusStateValue y .focused($field, equals: .fieldName).
  • Ocultar el teclado — establece el foco en nil o false para ocultar el teclado.
  • Foco automático — establece el valor inicial en .onAppear para mostrar el teclado al abrir la pantalla.

Qué es @FocusState en SwiftUI

@FocusState es un property wrapper que vincula el estado del foco a un campo de entrada específico u otro elemento enfocable en SwiftUI. A diferencia de UIKit, donde la gestión del foco ocurre a través de los métodos becomeFirstResponder y resignFirstResponder, SwiftUI utiliza un enfoque declarativo: declaras un estado (@FocusState) y lo vinculas a un elemento mediante el modificador .focused(). Cambiar el estado cambia automáticamente el foco.

Antes de la introducción de @FocusState en iOS 15, los desarrolladores tenían que crear wrappers UIViewRepresentable alrededor de UITextField o usar bibliotecas de terceros. @FocusState está integrado directamente en SwiftUI y funciona con TextField, TextEditor, SecureField y SearchField. Esto hace que el código sea más limpio, reduce la cantidad de puentes UIKit y mejora la capacidad de prueba.

Según WWDC Session 10136 — What's new in SwiftUI (2024), @FocusState utiliza el sistema de preference keys de SwiftUI para pasar información de foco entre elementos. Cuando un campo recibe el foco, SwiftUI actualiza automáticamente la propiedad @FocusState asociada, lo que permite reaccionar a los cambios de foco en el código.

Gestión del foco con Bool

La forma más sencilla de usar @FocusState es con el tipo Bool. Cuando un campo está enfocado, la propiedad es true. Cuando se pierde el foco — false. Puedes forzar el foco asignándole true, o eliminarlo asignándole false.

swift
struct LoginForm: View {
    @State var email = ""
    @FocusState var isEmailFocused: Bool
    
    var body: some View {
        VStack {
            TextField("Email", text: $email)
                .focused($isEmailFocused)
            
            Button("Show Keyboard") {
                isEmailFocused = true
            }
            Button("Hide Keyboard") {
                isEmailFocused = false
            }
        }
    }
}

En este ejemplo, isEmailFocused se vuelve automáticamente true cuando el usuario toca el campo de texto, y false cuando se oculta el teclado. Los botones permiten gestionar el foco programáticamente — útil para teclados personalizados, botones "Siguiente" y situaciones en las que necesitas ocultar forzosamente el teclado después de enviar un formulario.

Gestión del foco con Enum para varios campos

Para formularios con varios campos, @FocusState admite un enum que conforme al protocolo FocusStateValue (o Hashable). Cada caso del enum corresponde a un campo específico. Esto permite cambiar el foco entre campos — por ejemplo, cuando el usuario presiona "Siguiente" en el teclado para moverse al siguiente campo.

swift
struct RegistrationForm: View {
    enum Field: Hashable {
        case email
        case password
        case confirmPassword
    }
    
    @State var email = ""
    @State var password = ""
    @State var confirmPassword = ""
    @FocusState var focusedField: Field?
    
    var body: some View {
        Form {
            TextField("Email", text: $email)
                .focused($focusedField, equals: .email)
                .onSubmit { focusedField = .password }
            
            SecureField("Password", text: $password)
                .focused($focusedField, equals: .password)
                .onSubmit { focusedField = .confirmPassword }
            
            SecureField("Confirm", text: $confirmPassword)
                .focused($focusedField, equals: .confirmPassword)
                .onSubmit { submitForm() }
        }
    }
}

Observa el modificador .onSubmit — se llama cuando el usuario presiona "Return" en el teclado. Dentro de .onSubmit cambiamos focusedField al siguiente campo, lo que mueve automáticamente el foco. El último campo llama a submitForm() para enviar el formulario.

Ocultar y mostrar el teclado

@FocusState proporciona una forma sencilla de ocultar el teclado — solo hay que establecer la propiedad en nil (para enum) o false (para Bool). Sin embargo, a veces necesitas ocultar el teclado sin vincularlo a un campo específico — por ejemplo, al tocar un espacio vacío. En este caso, hay varios enfoques.

swift
struct DismissKeyboardView: View {
    @State var text = ""
    @FocusState var isFocused: Bool
    
    var body: some View {
        TextField("Enter text", text: $text)
            .focused($isFocused)
            .toolbar {
                ToolbarItemGroup(placement: .keyboard) {
                    Spacer()
                    Button("Done") {
                        isFocused = false
                    }
                }
            }
    }
}

El modificador .toolbar con placement .keyboard añade un botón sobre el teclado. Este es un patrón UX estándar en iOS para ocultar el teclado. Un enfoque alternativo es usar .onTapGesture en el VStack raíz para restablecer el foco al tocar el fondo.

Foco y validación de formularios

@FocusState se combina perfectamente con la validación de formularios. Un patrón típico: después de presionar el botón "Enviar", validar todos los campos y establecer el foco en el primer campo con error. Esto mejora la experiencia del usuario — el usuario ve inmediatamente qué campo debe corregir.

swift
struct ValidatedForm: View {
    enum Field: Hashable { case name; case phone }
    
    @State var name = ""
    @State var phone = ""
    @FocusState var focusedField: Field?
    @State var errors: [String] = []
    
    var body: some View {
        Form {
            TextField("Name", text: $name)
                .focused($focusedField, equals: .name)
            TextField("Phone", text: $phone)
                .focused($focusedField, equals: .phone)
            
            Button("Submit") { validateAndSubmit() }
        }
    }
    
    func validateAndSubmit() {
        if name.isEmpty {
            focusedField = .name
            return
        }
        if phone.isEmpty {
            focusedField = .phone
            return
        }
        // submit form
    }
}

En este ejemplo, si el campo name está vacío, el foco se mueve a él, y el usuario ve inmediatamente dónde está el error. Si name está completo, se verifica phone. Este es un comportamiento natural para formularios — el usuario llena los campos de arriba a abajo, y la validación sigue el mismo orden.

Errores comunes con @FocusState

El error más común es intentar usar @FocusState con un tipo que no conforme a Hashable. @FocusState requiere que el tipo de la propiedad sea Hashable (Bool y los enums opcionales ya lo cumplen). Si intentas usar una estructura personalizada, asegúrate de que implemente Hashable.

  • Olvidaste el modificador .focused() — @FocusState por sí solo no gestiona el foco. Debes vincularlo a un campo mediante .focused($property) o .focused($property, equals: .case).
  • Múltiples @FocusState en una misma View — para varios campos, usa un solo @FocusState con un enum, no varias propiedades @FocusState. Varias propiedades Bool no se sincronizarán entre sí.
  • Cambiar @FocusState fuera del hilo principal — @FocusState solo debe cambiarse en el hilo principal, como todas las propiedades UI en SwiftUI. Las operaciones asíncronas deben cambiar a MainActor antes de modificarlo.
  • Reinicio del foco al reconstruir la vista — si la Vista se reconstruye, @FocusState puede reiniciarse. Usa el modificador .id() para una identificación estable de la Vista.
swift
// ❌ Wrong: two @FocusState Bool instead of enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool

// ✅ Correct: single enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?

Preguntas frecuentes

¿Desde qué versiones de iOS está disponible @FocusState?

@FocusState está disponible desde iOS 15, iPadOS 15, macOS 12, tvOS 15 y watchOS 8. Para proyectos que soporten iOS 14 y versiones anteriores, usa UIViewRepresentable con UITextField y becomeFirstResponder, o bibliotecas de terceros con implementación personalizada de gestión de foco.

¿Se puede usar @FocusState con UIViewRepresentable personalizado?

Sí, para ello necesitas implementar el soporte de FocusState en el UIViewRepresentable personalizado a través del protocolo UIViewRepresentable. La vista personalizada debe tener becomeFirstResponder y resignFirstResponder. SwiftUI vinculará automáticamente @FocusState con estos métodos si especificas el modificador .focused().

¿Por qué @FocusState no funciona con TextField en List?

En List o Form, las celdas pueden reutilizarse, lo que rompe la vinculación de @FocusState con el campo. Solución: añade el modificador .id() con un identificador único para cada TextField. Por ejemplo: .id(fieldName). Esto obliga a SwiftUI a crear una instancia de Vista separada para cada campo.

¿Cómo ocultar el teclado al tocar un espacio vacío?

Añade .onTapGesture al contenedor raíz (VStack, ZStack) y restablece el foco: focusedField = nil. Sin embargo, .onTapGesture puede bloquear los toques en los botones internos — usa un contenedor con .contentShape(Rectangle()) y .onTapGesture en él, o un UIKitBackgroundView personalizado.

¿Cómo animar la aparición del teclado con @FocusState?

@FocusState no proporciona una API directa para la animación del teclado — esto es comportamiento del sistema iOS. Sin embargo, puedes reaccionar a los cambios de foco con .onChange(of: focusedField) o .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) para animación personalizada del contenido.

Resumen

  • @FocusState — un property wrapper nativo de SwiftUI para la gestión del foco de entrada, disponible desde iOS 15.
  • Dos modos — Bool para un solo campo, enum Hashable para varios campos del formulario.
  • Modificador .focused() — obligatorio para vincular @FocusState a un campo de entrada específico.
  • Control programático — establecer el valor en nil o false oculta el teclado.
  • Validación de formularios — @FocusState permite establecer el foco en el primer campo con error después de la validación.
  • Enum para varios campos — un solo @FocusState con enum es preferible a varias propiedades Bool.
  • iOS 15+ — para versiones anteriores, usa UIViewRepresentable con becomeFirstResponder.

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también