@FocusState — o que é, gestão de foco e teclado no SwiftUI

Autor: IT Sectr Publicado: 2026-06-26 Tempo de leitura: 9 min

@FocusState é um property wrapper no SwiftUI, apresentado no iOS 15, que permite controlar programaticamente o foco de entrada em campos de texto e outros elementos. Antes do seu surgimento, os desenvolvedores tinham que usar UIViewRepresentable para aceder aos métodos UIKit becomeFirstResponder e resignFirstResponder. O @FocusState resolve este problema nativamente: você vincula uma propriedade a um campo através do modificador .focused(), após o que definir ou remover o foco é feito com uma simples atribuição de valor. De acordo com a Apple Developer Documentation — FocusState (2025), o @FocusState suporta dois modos: Bool para gestão simples (foco ativado ou desativado) e enum para vários campos, onde cada case corresponde a um campo de entrada específico.

Principais conclusões

  • @FocusState — um property wrapper para gestão programática de foco no SwiftUI, disponível desde o iOS 15.
  • Modo Bool — para um único campo use @FocusState var isFocused: Bool com .focused($isFocused).
  • Modo Enum — para vários campos use um enum que esteja em conformidade com FocusStateValue e .focused($field, equals: .fieldName).
  • Ocultar o teclado — defina o foco como nil ou false para ocultar o teclado.
  • Foco automático — defina o valor inicial em .onAppear para mostrar o teclado ao abrir o ecrã.

O que é @FocusState no SwiftUI

@FocusState é um property wrapper que liga o estado do foco a um campo de entrada específico ou outro elemento focável no SwiftUI. Ao contrário do UIKit, onde a gestão do foco acontece através dos métodos becomeFirstResponder e resignFirstResponder, o SwiftUI utiliza uma abordagem declarativa: você declara um estado (@FocusState) e liga-o a um elemento através do modificador .focused(). Alterar o estado altera automaticamente o foco.

Antes da introdução do @FocusState no iOS 15, os desenvolvedores tinham que criar wrappers UIViewRepresentable à volta do UITextField ou usar bibliotecas de terceiros. O @FocusState está integrado diretamente no SwiftUI e funciona com TextField, TextEditor, SecureField e SearchField. Isto torna o código mais limpo, reduz o número de pontes UIKit e melhora a testabilidade.

De acordo com a WWDC Session 10136 — What's new in SwiftUI (2024), o @FocusState utiliza o sistema de preference keys do SwiftUI para passar informações de foco entre elementos. Quando um campo recebe o foco, o SwiftUI atualiza automaticamente a propriedade @FocusState associada, permitindo reagir a alterações de foco no código.

Gestão de foco com Bool

A forma mais simples de usar @FocusState é com o tipo Bool. Quando um campo está focado, a propriedade é true. Quando o foco é perdido — false. Pode forçar o foco definindo-o como true, ou removê-lo definindo-o como 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
            }
        }
    }
}

Neste exemplo, isEmailFocused torna-se automaticamente true quando o utilizador toca no campo de texto, e false quando o teclado é ocultado. Os botões permitem gerir o foco programaticamente — útil para teclados personalizados, botões "Seguinte" e situações onde precisa de forçar a ocultação do teclado após o envio do formulário.

Gestão de foco com Enum para vários campos

Para formulários com vários campos, o @FocusState suporta um enum em conformidade com o protocolo FocusStateValue (ou Hashable). Cada case do enum corresponde a um campo específico. Isto permite alternar o foco entre campos — por exemplo, quando o utilizador pressiona "Seguinte" no teclado para passar para o campo seguinte.

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() }
        }
    }
}

Observe o modificador .onSubmit — é chamado quando o utilizador pressiona "Return" no teclado. Dentro de .onSubmit mudamos focusedField para o campo seguinte, o que move automaticamente o foco. O último campo chama submitForm() para enviar o formulário.

Ocultar e mostrar o teclado

O @FocusState fornece uma forma simples de ocultar o teclado — basta definir a propriedade como nil (para enum) ou false (para Bool). No entanto, por vezes precisa de ocultar o teclado sem o associar a um campo específico — por exemplo, ao tocar num espaço vazio. Neste caso, existem várias abordagens.

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
                    }
                }
            }
    }
}

O modificador .toolbar com placement .keyboard adiciona um botão acima do teclado. Este é um padrão UX padrão no iOS para ocultar o teclado. Uma abordagem alternativa é usar .onTapGesture no VStack raiz para redefinir o foco ao tocar no fundo.

Foco e validação de formulários

O @FocusState combina perfeitamente com a validação de formulários. Um padrão típico: após pressionar o botão "Enviar", validar todos os campos e definir o foco no primeiro campo com erro. Isto melhora a experiência do utilizador — o utilizador vê imediatamente qual campo precisa de ser corrigido.

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
    }
}

Neste exemplo, se o campo name estiver vazio, o foco move-se para ele, e o utilizador vê imediatamente onde está o erro. Se name estiver preenchido, phone é verificado. Este é um comportamento natural para formulários — o utilizador preenche os campos de cima para baixo, e a validação segue a mesma ordem.

Erros comuns com @FocusState

O erro mais comum é tentar usar @FocusState com um tipo que não esteja em conformidade com Hashable. O @FocusState requer que o tipo da propriedade seja Hashable (Bool e enums opcionais já estão em conformidade). Se estiver a tentar usar uma estrutura personalizada, certifique-se de que implementa Hashable.

  • Esqueceu o modificador .focused() — o @FocusState por si só não gere o foco. Deve ligá-lo a um campo através de .focused($property) ou .focused($property, equals: .case).
  • Múltiplos @FocusState na mesma View — para vários campos, use um único @FocusState com um enum, não várias propriedades @FocusState. Várias propriedades Bool não serão sincronizadas entre si.
  • Alterar @FocusState fora da thread principal — o @FocusState só deve ser alterado na thread principal, como todas as propriedades UI no SwiftUI. As operações assíncronas devem mudar para o MainActor antes de o alterar.
  • Redefinição do foco ao reconstruir a View — se a View for reconstruída, o @FocusState pode ser redefinido. Use o modificador .id() para uma identificação estável da View.
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?

Perguntas frequentes

Desde que versões do iOS está disponível o @FocusState?

O @FocusState está disponível desde o iOS 15, iPadOS 15, macOS 12, tvOS 15 e watchOS 8. Para projetos que suportam iOS 14 e versões anteriores, use UIViewRepresentable com UITextField e becomeFirstResponder, ou bibliotecas de terceiros com implementação personalizada de gestão de foco.

Pode-se usar @FocusState com UIViewRepresentable personalizado?

Sim, para isso precisa de implementar o suporte para FocusState no UIViewRepresentable personalizado através do protocolo UIViewRepresentable. A view personalizada deve ter becomeFirstResponder e resignFirstResponder. O SwiftUI ligará automaticamente o @FocusState a estes métodos se especificar o modificador .focused().

Porque é que o @FocusState não funciona com TextField em List?

Em List ou Form, as células podem ser reutilizadas, o que quebra a ligação do @FocusState com o campo. Solução: adicione o modificador .id() com um identificador único para cada TextField. Por exemplo: .id(fieldName). Isto força o SwiftUI a criar uma instância de View separada para cada campo.

Como ocultar o teclado ao tocar num espaço vazio?

Adicione .onTapGesture ao contentor raiz (VStack, ZStack) e redefina o foco: focusedField = nil. No entanto, o .onTapGesture pode bloquear toques nos botões internos — use um contentor com .contentShape(Rectangle()) e .onTapGesture no mesmo, ou um UIKitBackgroundView personalizado.

Como animar o aparecimento do teclado com @FocusState?

O @FocusState não fornece uma API direta para animação do teclado — este é um comportamento do sistema iOS. No entanto, pode reagir a alterações de foco com .onChange(of: focusedField) ou .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) para animação personalizada do conteúdo.

Resumo

  • @FocusState — um property wrapper nativo do SwiftUI para gestão de foco de entrada, disponível desde o iOS 15.
  • Dois modos — Bool para um único campo, enum Hashable para vários campos do formulário.
  • Modificador .focused() — obrigatório para ligar o @FocusState a um campo de entrada específico.
  • Controlo programático — definir o valor como nil ou false oculta o teclado.
  • Validação de formulários — o @FocusState permite definir o foco no primeiro campo com erro após a validação.
  • Enum para vários campos — um único @FocusState com enum é preferível a várias propriedades Bool.
  • iOS 15+ — para versões mais antigas, use UIViewRepresentable com becomeFirstResponder.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também