@FocusState — co to jest, zarządzanie fokusem i klawiaturą w SwiftUI

Autor: IT Sectr Opublikowano: 2026-06-26 Czas czytania: 9 min

@FocusState — to property wrapper w SwiftUI, wprowadzony w iOS 15, który umożliwia programowe zarządzanie fokusem wprowadzania na polach tekstowych i innych elementach. Przed jego pojawieniem się programiści musieli używać UIViewRepresentable, aby uzyskać dostęp do UIKit-owych metod becomeFirstResponder i resignFirstResponder. @FocusState rozwiązuje ten problem w natywny sposób: łączysz właściwość z polem za pomocą modyfikatora .focused(), po czym ustawienie lub zresetowanie fokusu następuje przez proste przypisanie wartości. Według Apple Developer Documentation — FocusState (2025), @FocusState obsługuje dwa tryby: Bool dla prostego zarządzania (fokus jest lub go nie ma) oraz enum dla wielu pól, gdzie każdy case odpowiada konkretnemu polu wprowadzania.

Najważniejsze

  • @FocusState — property wrapper do programowego zarządzania fokusem w SwiftUI, dostępny od iOS 15.
  • Tryb Bool — dla jednego pola użyj @FocusState var isFocused: Bool z .focused($isFocused).
  • Tryb Enum — dla wielu pól użyj enum z protokołem FocusStateValue i .focused($field, equals: .fieldName).
  • Ukrywanie klawiatury — ustaw fokus na nil lub false, aby ukryć klawiaturę.
  • Automatyczny fokus — ustaw początkową wartość w .onAppear, aby pokazać klawiaturę przy otwarciu ekranu.

Co to jest @FocusState w SwiftUI

@FocusState — to property wrapper, który łączy stan fokusu z konkretnym polem wprowadzania lub innym focusable-elementem w SwiftUI. W przeciwieństwie do UIKit, gdzie zarządzanie fokusem odbywa się przez metody becomeFirstResponder i resignFirstResponder, SwiftUI używa deklaratywnego podejścia: deklarujesz stan (@FocusState) i łączysz go z elementem za pomocą modyfikatora .focused(). Zmiana stanu automatycznie zmienia fokus.

Przed pojawieniem się @FocusState w iOS 15 programiści musieli tworzyć UIViewRepresentable opakowania wokół UITextField lub używać zewnętrznych bibliotek. @FocusState jest zintegrowany bezpośrednio z SwiftUI i działa z TextField, TextEditor, SecureField i SearchField. To sprawia, że kod jest czystszy, zmniejsza ilość UIKit-mostków i poprawia testowalność.

Według WWDC Session 10136 — What's new in SwiftUI (2024), @FocusState używa systemu preference-kluczy SwiftUI do przesyłania informacji o fokusie między elementami. Gdy pole otrzymuje fokus, SwiftUI automatycznie aktualizuje powiązaną właściwość @FocusState, co pozwala reagować na zmiany fokusu w kodzie.

Zarządzanie fokusem za pomocą Bool

Najprostszy sposób użycia @FocusState — typ Bool. Gdy pole jest w fokusie, właściwość ma wartość true. Gdy fokus odchodzi — false. Możesz wymusić ustawienie fokusu, przypisując true, lub zresetować go, przypisując false.

swift
struct LoginForm: View {
    @State var email = ""
    @FocusState var isEmailFocused: Bool
    
    var body: some View {
        VStack {
            TextField("Email", text: $email)
                .focused($isEmailFocused)
            
            Button("Pokaż klawiaturę") {
                isEmailFocused = true
            }
            Button("Ukryj klawiaturę") {
                isEmailFocused = false
            }
        }
    }
}

W tym przykładzie isEmailFocused automatycznie staje się true, gdy użytkownik dotknie pola tekstowego, i false, gdy klawiatura jest ukrywana. Przyciski umożliwiają programowe zarządzanie fokusem — jest to przydatne dla niestandardowych klawiatur, przycisków „Dalej” i sytuacji, gdy trzeba wymusić ukrycie klawiatury po wysłaniu formularza.

Zarządzanie fokusem za pomocą Enum dla wielu pól

Dla formularzy z wieloma polami @FocusState obsługuje enum zgodny z protokołem FocusStateValue (lub Hashable). Każdy case enum odpowiada konkretnemu polu. Pozwala to przełączać fokus między polami — na przykład przy naciśnięciu „Dalej” na klawiaturze przechodzić do następnego pola.

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

Zwóć uwagę na modyfikator .onSubmit — jest wywoływany, gdy użytkownik naciśnie „Return” na klawiaturze. Wewnątrz .onSubmit przełączamy focusedField na następne pole, co automatycznie przenosi fokus. Ostatnie pole wywołuje submitForm() w celu wysłania formularza.

Ukrywanie i pokazywanie klawiatury

@FocusState zapewnia prosty sposób ukrycia klawiatury — wystarczy ustawić właściwość na nil (dla enum) lub false (dla Bool). Czasami jednak trzeba ukryć klawiaturę bez wiązania z konkretnym polem — na przykład przy dotknięciu pustego miejsca. W tym przypadku jest kilka podejść.

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("Gotowe") {
                        isFocused = false
                    }
                }
            }
    }
}

Modyfikator .toolbar z placement .keyboard dodaje przycisk nad klawiaturą. Jest to standardowy wzorzec UX w iOS do ukrywania klawiatury. Alternatywne podejście — użycie .onTapGesture na głównym VStack do zresetowania fokusu przy dotknięciu tła.

Fokus i walidacja formularza

@FocusState doskonale łączy się z walidacją formularza. Typowy wzorzec: po naciśnięciu przycisku „Wyślij” sprawdź wszystkie pola i ustaw fokus na pierwszym polu z błędem. Poprawia to doświadczenie użytkownika — użytkownik od razu widzi, które pole wymaga poprawy.

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("Wyślij") { validateAndSubmit() }
        }
    }
    
    func validateAndSubmit() {
        if name.isEmpty {
            focusedField = .name
            return
        }
        if phone.isEmpty {
            focusedField = .phone
            return
        }
        // prześlij formularz
    }
}

W tym przykładzie, gdy pole name jest puste, fokus przenosi się na nie, a użytkownik od razu widzi, gdzie jest błąd. Jeśli name jest wypełnione, sprawdzane jest phone. Jest to naturalne zachowanie dla formularzy — użytkownik wypełnia pola od góry do dołu, a walidacja podąża w tej samej kolejności.

Typowe błędy z @FocusState

Najczęstszy błąd — próba użycia @FocusState z typem niezgodnym z Hashable. @FocusState wymaga, aby typ właściwości był Hashable (Bool i opcjonalne enum już są zgodne). Jeśli próbujesz użyć niestandardowej struktury, upewnij się, że implementuje Hashable.

  • Zapomniałeś modyfikatora .focused() — @FocusState sam w sobie nie zarządza fokusem. Trzeba koniecznie połączyć go z polem przez .focused($property) lub .focused($property, equals: .case).
  • Wiele @FocusState w jednym View — dla wielu pól użyj jednego @FocusState z enum, a nie wielu właściwości @FocusState. Wiele właściwości Bool nie będzie ze sobą zsynchronizowanych.
  • Zmiana @FocusState poza main thread — @FocusState należy zmieniać tylko na głównym wątku, podobnie jak wszystkie właściwości UI w SwiftUI. Operacje asynchroniczne powinny przełączać się na MainActor przed zmianą.
  • Resetowanie fokusu przy przebudowie — jeśli View jest przebudowywany, @FocusState może zostać zresetowany. Użyj modyfikatora .id() dla stabilnej identyfikacji View.
swift
// ❌ Błąd: dwa @FocusState Bool zamiast enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool

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

Często zadawane pytania

Od jakich wersji iOS jest dostępny @FocusState?

@FocusState jest dostępny od iOS 15, iPadOS 15, macOS 12, tvOS 15 i watchOS 8. Dla projektów obsługujących iOS 14 i starsze, użyj UIViewRepresentable z UITextField i becomeFirstResponder, albo zewnętrznych bibliotek z niestandardową implementacją zarządzania fokusem.

Czy można używać @FocusState z niestandardowymi UIViewRepresentable?

Tak, w tym celu w niestandardowym UIViewRepresentable trzeba zaimplementować obsługę FocusState przez protokół UIViewRepresentable. Niestandardowy view musi mieć becomeFirstResponder i resignFirstResponder. SwiftUI automatycznie połączy @FocusState z tymi metodami, jeśli podasz modyfikator .focused().

Dlaczego @FocusState nie działa z TextField w List?

W List lub Form komórki mogą być ponownie używane, co psuje połączenie @FocusState z polem. Rozwiązanie: dodaj modyfikator .id() z unikalnym identyfikatorem dla każdego TextField. Na przykład: .id(fieldName). To zmusza SwiftUI do tworzenia oddzielnej instancji View dla każdego pola.

Jak ukryć klawiaturę przy dotknięciu pustego miejsca?

Dodaj .onTapGesture na głównym kontenerze (VStack, ZStack) i zresetuj fokus: focusedField = nil. Jednak .onTapGesture może blokować dotknięcia na przyciski wewnątrz — użyj kontenera z .contentShape(Rectangle()) i .onTapGesture na nim, albo niestandardowego UIKitBackgroundView.

Jak animować pojawienie się klawiatury z @FocusState?

@FocusState nie zapewnia bezpośredniego API do animacji klawiatury — jest to systemowe zachowanie iOS. Możesz jednak reagować na zmiany fokusu za pomocą .onChange(of: focusedField) lub .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) do niestandardowej animacji zawartości.

Podsumowanie

  • @FocusState — natywny SwiftUI property wrapper do zarządzania fokusem wprowadzania, dostępny od iOS 15.
  • Dwa tryby — Bool dla jednego pola, Hashable enum dla wielu pól formularza.
  • Modyfikator .focused() — obowiązkowy do połączenia @FocusState z konkretnym polem wprowadzania.
  • Programowe zarządzanie — ustawienie wartości nil lub false ukrywa klawiaturę.
  • Walidacja formularzy — @FocusState umożliwia ustawienie fokusu na pierwszym polu z błędem po sprawdzeniu.
  • Enum dla wielu pól — jeden @FocusState z enum jest preferowany nad wieloma właściwościami Bool.
  • iOS 15+ — dla starszych wersji użyj UIViewRepresentable z becomeFirstResponder.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również