@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 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
// ❌ 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
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.
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().
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.
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.
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
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.
Leia também