@State to Property Wrapper w SwiftUI do zarządzania stanem lokalnym w obrębie jednego widoku. SwiftUI automatycznie przerysowuje widok przy każdej zmianie właściwości @State, czyniąc interfejs reaktywnym bez ręcznego wywoływania aktualizacji. Według Apple Developer Documentation (2025), @State jest zalecany dla prostych typów i struktur należących do jednego widoku. @State to najprostszy sposób na dodanie interaktywności do interfejsu SwiftUI.
Najważniejsze
@State to Property Wrapper wbudowany w SwiftUI, który pozwala widokowi przechowywać i śledzić własny stan. Gdy wartość @State się zmienia, SwiftUI automatycznie przerysowuje widok, ponownie wywołując właściwość body. To podstawa programowania reaktywnego w SwiftUI: programista deklaruje stan, a framework zajmuje się synchronizacją interfejsu.
@State tworzy obszar przechowywania na stercie zarządzany przez SwiftUI. Ten obszar jest trwały — przetrwa wielokrotne inicjalizacje struktury widoku, które mają miejsce przy każdym renderowaniu. SwiftUI używa identyfikatora widoku (wygenerowanego na podstawie pozycji w hierarchii) do powiązania właściwości @State z konkretnym widokiem. Dzięki temu stan nie jest resetowany przy aktualizacji widoku nadrzędnego.
Ważne ograniczenie: @State jest przeznaczony tylko dla typów wartościowych (struktur, wyliczeń, typów prostych). Dla typów referencyjnych (klas) używaj @StateObject lub @ObservedObject. Jeśli przypiszesz klasę do właściwości @State, SwiftUI nie będzie w stanie wykryć zmian wewnątrz obiektu — tylko całkowitą zamianę referencji.
SwiftUI implementuje @State poprzez wewnętrzny mechanizm Storage. Każda właściwość @State otrzymuje wydzieloną komórkę pamięci przechowywaną w specjalnym kontenerze storage widoku. Gdy następuje zapis do wrappedValue, SwiftUI przez didSet powiadamia swój graf zależności (dependency graph) o konieczności przerysowania.
struct ContentView: View {
@State private var name: String = "User"
@State private var isLoggedIn: Bool = false
var body: some View {
VStack {
Text("Witaj, \(name)")
Button(isLoggedIn ? "Wyloguj" : "Zaloguj") {
isLoggedIn.toggle()
}
}
}
}
W przykładzie dwie właściwości @State: name (String) i isLoggedIn (Bool). Przy wywołaniu isLoggedIn.toggle() SwiftUI oznacza ContentView jako wymagający aktualizacji i ponownie uruchamia body w następnym cyklu renderowania. Kluczowy punkt: właściwości @State zawsze deklaruje się z modyfikatorem private — to sygnał, że stan należy wyłącznie do bieżącego widoku i nie powinien być zmieniany z zewnątrz bezpośrednio.
Do obserwowania zmian SwiftUI używa CurrentValueSubject z Combine. Każda właściwość @State tworzy ukrytego wydawcę, który powiadamia system przy każdej zmianie. Pozwala to SwiftUI przerysowywać tylko minimalnie niezbędny zestaw widoków, unikając pełnej aktualizacji hierarchii.
@State jest optymalny dla prostych stanów lokalnych: pól tekstowych wyszukiwania, flag boolowskich dla okien modalnych, przełączników ustawień, liczników, wybranych elementów listy. Jeśli wartość jest używana tylko w jednym widoku i jego zagnieżdżonych komponentach (przez @Binding), @State jest właściwym wyborem. Dla stanów, które powinny przetrwać zamknięcie widoku (np. dane formularza), @State również się nadaje, dopóki widok pozostaje w hierarchii.
Nie używaj @State do stanów globalnych aplikacji, buforowania danych sieciowych lub obiektów używanych na wielu ekranach. Do tych celów służą @StateObject i @EnvironmentObject. @State nie nadaje się również do przechowywania dużych ilości danych — przy każdej zmianie będzie przerysowywany cały widok.
@Binding to most między @State w widoku nadrzędnym a widokiem potomnym, który potrzebuje zmieniać ten stan. Rodzic deklaruje @State, a komponent potomny otrzymuje Binding przez projekcję $. Zmiana Binding w widoku potomnym automatycznie aktualizuje @State w rodzicu — i odwrotnie. Zapewnia to jednokierunkowy przepływ danych z możliwością sprzężenia zwrotnego.
struct ParentView: View {
@State private var text: String = ""
var body: some View {
ChildView(text: $text)
}
}
struct ChildView: View {
@Binding var text: String
var body: some View {
TextField("Enter text", text: $text)
}
}
W listingu ParentView jest właścicielem @State text, a ChildView otrzymuje $text jako Binding. TextField wewnątrz ChildView wiąże się z tym Binding przez text: $text. Gdy użytkownik pisze w TextField, wartość zmienia się w ChildView przez Binding, co powoduje aktualizację @State w ParentView. Oba widoki przerysowują się z nową wartością.
Najczęstszy błąd to przypisanie klasy do właściwości @State. Jeśli napiszesz @State var model = MyClass(), SwiftUI nie będzie w stanie śledzić zmian właściwości wewnątrz klasy — tylko zastąpienie samego obiektu. Dla klas zawsze używaj @StateObject. Drugi powszechny problem to deklarowanie @State bez modyfikatora private, co narusza zasadę enkapsulacji stanu.
Bezpośrednie przekazywanie @State do widoku potomnego bez $ — kolejny typowy błąd. Jeśli przekażesz TextField(text: text) zamiast TextField(text: $text), komponent potomny otrzyma zwykły string, a nie Binding. Zmiana tekstu w TextField nie będzie synchronizowana z nadrzędnym @State. Zawsze używaj projekcji $ do przekazywania Binding.
Trzeci błąd to wielokrotne właściwości @State dla powiązanych danych. Jeśli kilka wartości logicznie stanowi jedną całość (np. pola formularza), połącz je w jedną strukturę z pojedynczym @State. Upraszcza to przekazywanie stanu do widoków potomnych i zmniejsza liczbę oddzielnych wyzwalaczy aktualizacji.
@State jest używany w większości projektów SwiftUI do podstawowej interaktywności. Rozważmy przykład formularza logowania, gdzie @State zarządza polami tekstowymi i stanem ładowania. Taki wzorzec występuje w każdej aplikacji — od prostych notatek po złożone rozwiązania korporacyjne.
struct LoginView: View {
@State private var email: String = ""
@State private var password: String = ""
@State private var isLoading: Bool = false
@State private var errorMessage: String?
var body: some View {
Form {
TextField("Email", text: $email)
SecureField("Password", text: $password)
Button("Zaloguj") {
login()
}.disabled(isLoading)
}
}
private func login() {
isLoading = true
// Wykonaj żądanie sieciowe
}
}
W przykładzie cztery właściwości @State: email i password dla pól formularza, isLoading do wskazywania ładowania i errorMessage do wyświetlania błędów. Każda właściwość niezależnie zarządza swoją częścią interfejsu. Przy zmianie isLoading przycisk automatycznie się blokuje przez disabled(isLoading) — bez ręcznej aktualizacji UI.
Często zadawane pytania
@State jest przeznaczony dla stanu lokalnego konkretnego widoku. Modyfikator private gwarantuje, że inne komponenty nie zmienią go bezpośrednio, naruszając enkapsulację. Do zewnętrznego dostępu używaj projekcji $.
Tak, @State obsługuje tablice i słowniki, ponieważ są to typy wartościowe. Jednak przy zmianie elementu tablicy SwiftUI przerysowuje cały widok. Dla dużych list efektywniejsze jest użycie @StateObject z @Published.
@State poprawnie działa z typami Optional. Przy przypisaniu nil SwiftUI wykrywa zmianę i przerysowuje widok. Jest to wygodne dla stanów takich jak errorMessage: String?, gdzie nil oznacza brak błędu.
@State zachowuje wartość, dopóki widok pozostaje w hierarchii. Jeśli widok zostanie usunięty z hierarchii i dodany ponownie, @State inicjalizuje się od nowa z wartością domyślną. Do trwałości używaj @AppStorage.
Tak, owiń zmianę w withAnimation: withAnimation(.easeInOut) { isExpanded.toggle() }. SwiftUI animuje przejście między starym a nowym stanem interfejsu z określonym typem animacji.
Podsumowanie
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.
Przeczytaj również