@Binding to Property Wrapper w SwiftUI, który tworzy referencję do danych należących do innego komponentu. Binding nie przechowuje wartości samodzielnie — jedynie udostępnia dostęp do istniejącego źródła prawdy poprzez projekcję $. Według Apple Developer Documentation (2025), Binding zapewnia reaktywne dwukierunkowe połączenie między widokiem nadrzędnym a podrzędnym bez bezpośredniego posiadania danych. @Binding to kluczowy mechanizm przekazywania zmiennego stanu w dół hierarchii.
Najważniejsze
@Binding to Property Wrapper, który tworzy dwukierunkowe połączenie między właściwością przechowywaną w widoku nadrzędnym a komponentem podrzędnym. Główna różnica między Binding a @State: Binding nie posiada danych. On jedynie odczytuje i zapisuje wartość przez prawdziwe źródło — @State, @StateObject lub inny Binding w rodzicu. Bez Binding widoki podrzędne nie mogłyby modyfikować stanu przodka bez callbacków lub delegatów.
Binding jest zaimplementowany jako struktura z dwiema właściwościami: wrappedValue (bieżąca wartość) i projectedValue (sam Binding, dostępny przez $). Gdy widok podrzędny zmienia wrappedValue przez Binding, SwiftUI przekazuje zmianę do źródła danych i przerysowuje wszystkie zależne widoki. Dzieje się to synchronicznie w bieżącym cyklu aktualizacji.
Ważna cecha: @Binding nie ogranicza się do jednopoziomowego przekazywania. Binding można przekazywać przez wiele poziomów hierarchii — każdy komponent podrzędny otrzymuje referencję do tego samego źródła danych. Zmiana na dowolnym poziomie powoduje jednolitą aktualizację wszystkich powiązanych widoków.
Mechanizm dwukierunkowego połączenia przez @Binding opiera się na projekcji Property Wrapperów. Gdy rodzic deklaruje @State var value: T, SwiftUI automatycznie generuje projekcję $value typu Binding
struct SliderContainer: View {
@State private var value: Double = 0.5
var body: some View {
VStack {
Text("Wartość: \(value)")
SliderView(value: $value)
}
}
}
struct SliderView: View {
@Binding var value: Double
var body: some View {
Slider(value: $value, in: 0...1)
}
}
W przykładzie SliderContainer posiada @State value, a SliderView otrzymuje Binding przez $value. Slider wewnątrz SliderView jest powiązany z tym Bindingiem. Podczas przeciągania suwaka Slider zmienia wartość przez Binding, co automatycznie aktualizuje @State w SliderContainer i oba widoki wyświetlają aktualną liczbę. Cały łańcuch działa bez ani jednego callbacka czy powiadomienia.
Do tworzenia Binding z @StateObject lub @ObservedObject używa się tej samej projekcji: $object.property daje Binding
Przed SwiftUI standardowym sposobem przekazywania zmian w górę hierarchii były callbacki i delegaty: rodzic przekazywał domknięcie, komponent podrzędny wywoływał je przy zmianie. @Binding oferuje alternatywę z mniejszą ilością kodu i bardziej deklaratywnym składem. Zamiast przekazywać domknięcie completion wystarczy przekazać $stateValue.
| Kryterium | @Binding | Callbacki |
|---|---|---|
| Kod | Jedna adnotacja + $ | Domknięcie + wywołanie |
| Wielopoziomowość | Automatyczna | Łańcuch domknięć |
| Testowanie | Binding(value:constant) | Mock-domknięcia |
| Czytelność | Wysoka | Średnia |
| Elastyczność | Tylko dane | Dowolna logika |
Używaj @Binding, gdy widok podrzędny potrzebuje tylko odczytywać i zmieniać wartość. Jeśli wymagane jest wykonanie dodatkowych działań przy zmianie (walidacja, logowanie, żądanie sieciowe), łącz Binding z callbackiem: przekazuj Binding dla danych i domknięcie dla zdarzeń. Na przykład TextField może powiązać się z Bindingiem, a onChange uruchomi walidację.
@Binding jest stosowany w kilku typowych scenariuszach. Pierwszy — niestandardowe kontrolki: przełączniki, suwaki, wybory kolorów i inne interaktywne elementy przyjmują Binding do dwukierunkowej synchronizacji. Drugi — okna modalne: flaga pokazania sheet jest przekazywana jako Binding, umożliwiając widokowi podrzędnemu zamknięcie się przez presentationMode lub bezpośrednie ustawienie.
Trzeci wzorzec — formularze z podziałem. Jeśli formularz składa się z wielu pól, każde pole można wydzielić do osobnego komponentu przyjmującego Binding dla swojej wartości. Upraszcza to testowanie i ponowne użycie pól między różnymi formularzami. Komponent nadrzędny pozostaje jedynym właścicielem całego modelu formularza.
struct FormField: View {
let title: String
@Binding var text: String
var body: some View {
VStack(alignment: .leading) {
Text(title).font(.caption)
TextField("Enter \(title.lowercased())", text: $text)
.textFieldStyle(.roundedBorder)
}
}
}
Komponent FormField przyjmuje etykietę i Binding do łańcucha. Wyświetla podpis i TextField powiązany z przekazanym Bindingiem. Dowolny formularz może używać FormField wielokrotnie, przekazując $property dla każdego pola. Zmniejsza to powielanie znaczników i centralizuje stylizację pól tekstowych.
SwiftUI pozwala tworzyć Binding ręcznie przez inicjalizator Binding(get:set:). Jest to przydatne, gdy trzeba dodać logikę przy odczycie lub zapisie wartości. Na przykład można utworzyć Binding, który formatuje liczbę przed zapisaniem, lub Binding synchronizujący wartość z zdalnym serwerem przy każdej zmianie.
struct ValidatedField: View {
@State private var email: String = ""
var emailBinding: Binding<String> {
.init(
get: { email },
set: { email = $0.lowercased().trimmingCharacters(in: .whitespaces) }
)
}
var body: some View {
TextField("Email", text: emailBinding)
}
}
W listingu niestandardowy emailBinding automatycznie przekształca tekst na małe litery i usuwa spacje przy każdej zmianie. TextField używa tego Bindinga zamiast bezpośredniego powiązania z $email. Takie podejście pozwala scentralizować walidację i transformację danych wewnątrz Bindinga, nie zaśmiecając kodu handlerami onChange.
Pierwszy i najczęstszy błąd — przekazanie wartości zamiast Bindinga. Jeśli komponent podrzędny deklaruje @Binding var text: String, a rodzic przekazuje text (bez $), kompilator zgłosi błąd: Cannot convert value of type 'String' to expected argument type 'Binding
Drugi błąd — Binding do danych tylko do odczytu. Jeśli widok podrzędny potrzebuje tylko odczytywać wartość, nie używaj @Binding — wystarczy zwykłe let lub @State z rodzica. Binding implikuje możliwość zapisu, a zbędne uprawnienia do modyfikacji utrudniają debugowanie i naruszają zasadę minimalnych uprawnień.
Trzeci problem — Binding.constant w produkcji. Binding.constant(value) tworzy fikcyjne powiązanie bez sprzężenia zwrotnego — zmiany są ignorowane. Używaj constant tylko do prototypowania i podglądów (Xcode Previews), ale nigdy w rzeczywistym kodzie. Do testów używaj Binding(get:set:) z kontrolowanym zachowaniem.
Często zadawane pytania
@State posiada dane i zarządza ich przechowywaniem na stercie. @Binding tylko odwołuje się do istniejącego stanu bez posiadania. @State jest zawsze private, @Binding — parametrem wejściowym widoku podrzędnego.
Tak, przez inicjalizator Binding(get:set:) lub Binding.constant(value). Binding można również uzyskać z @StateObject przez projekcję $object.$property oraz z Publisher przez Binding(get:set:) wewnątrz Subscribe.
@Binding jest przekazywany przez łańcuch: każdy pośredni komponent deklaruje @Binding i przekazuje go dalej przez $. Wszystkie poziomy odwołują się do jednego źródła danych w głównym widoku.
Binding.constant tworzy niemą otoczkę — setter ignoruje nowe wartości. Jest przeznaczony tylko do prototypowania i SwiftUI Previews, gdzie nie jest wymagane sprzężenie zwrotne od komponentu podrzędnego.
Tak, Binding<T?> jest obsługiwany. Jeśli przekażesz Binding<String?>, widok podrzędny będzie mógł ustawić nil. Jest to wygodne dla opcjonalnych pól formularzy lub stanów z możliwością resetowania.
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ż