@Binding — це Property Wrapper у SwiftUI, який створює посилання на дані, якими володіє інший компонент. Binding не зберігає значення самостійно — він лише надає доступ до існуючого джерела істини через проекцію $. За даними Apple Developer Documentation (2025), Binding забезпечує реактивний двосторонній зв'язок між батьківським і дочірнім view без прямого володіння даними. @Binding — ключовий механізм для передачі змінюваного стану вниз по ієрархії.
Головне
@Binding — це Property Wrapper, який створює двосторонній зв'язок між властивістю, що зберігається в батьківському view, і дочірнім компонентом. Головна відмінність Binding від @State: Binding не володіє даними. Він тільки читає та записує значення через істинне джерело — @State, @StateObject або інший Binding у батьку. Без Binding дочірні view не могли б модифікувати стан предка без колбеків або делегатів.
Binding реалізований як структура з двома властивостями: wrappedValue (поточне значення) і projectedValue (сам Binding, доступний через $). Коли дочірнє view змінює wrappedValue через Binding, SwiftUI передає зміну до джерела даних і перемальовує всі залежні view. Це відбувається синхронно в рамках поточного циклу оновлення.
Важлива особливість: @Binding не обмежується однорівневою передачею. Binding можна передавати через кілька рівнів ієрархії — кожен дочірній компонент отримує посилання на одне й те саме джерело даних. Зміна на будь-якому рівні викликає єдине оновлення всіх пов'язаних view.
Механізм двостороннього зв'язку через @Binding будується на проекціях Property Wrappers. Коли батько оголошує @State var value: T, SwiftUI автоматично генерує проекцію $value типу Binding<T>. Передаючи $value в дочірній компонент із @Binding var value: T, ви з'єднуєте обидва view з однією коміркою пам'яті. Будь-який запис через Binding у дочірньому view викликає перемальовку обох компонентів.
struct SliderContainer: View {
@State private var value: Double = 0.5
var body: some View {
VStack {
Text("Value: \(value)")
SliderView(value: $value)
}
}
}
struct SliderView: View {
@Binding var value: Double
var body: some View {
Slider(value: $value, in: 0...1)
}
}
У прикладі SliderContainer володіє @State value, а SliderView отримує Binding через $value. Slider всередині SliderView прив'язаний до цього Binding. При перетягуванні повзунка Slider змінює значення через Binding, що автоматично оновлює @State у SliderContainer, і обидва view відображають актуальне число. Весь ланцюг працює без жодного колбека або сповіщення.
Для створення Binding із @StateObject або @ObservedObject використовується та сама проекція: $object.property дає Binding<PropertyType>. Це дозволяє передавати окремі властивості ObservableObject у дочірні view без передачі всього об'єкта. Такий підхід забезпечує більш вузьке прив'язування та запобігає зайвим перемальовкам.
До SwiftUI стандартним способом передачі змін вгору по ієрархії були колбеки та делегати: батько передавав замикання, дочірній компонент викликав його при зміні. @Binding пропонує альтернативу з меншим обсягом коду та більш декларативним синтаксисом. Замість передачі completion-замикання достатньо передати $stateValue.
| Критерій | @Binding | Колбеки |
|---|---|---|
| Код | Одна анотація + $ | Замикання + виклик |
| Багаторівневість | Автоматична | Ланцюг замикань |
| Тестування | Binding(value:constant) | Mock-замикання |
| Читабельність | Висока | Середня |
| Гнучкість | Тільки дані | Будь-яка логіка |
Використовуйте @Binding, коли дочірньому view потрібно тільки читати та змінювати значення. Якщо потрібно виконати побічні дії при зміні (валідація, логування, мережевий запит), комбінуйте Binding з колбеком: передавайте Binding для даних і замикання для подій. Наприклад, TextField може прив'язатися до Binding, а onChange запускатиме валідацію.
@Binding застосовується в кількох типових сценаріях. Перший — кастомні контроли: перемикачі, повзунки, колірні пікери та інші інтерактивні елементи приймають Binding для двосторонньої синхронізації. Другий — модальні вікна: прапорець показу sheet передається як Binding, дозволяючи дочірньому view закрити себе через presentationMode або пряме встановлення.
Третій паттерн — форми з розділенням. Якщо форма складається з багатьох полів, кожне поле можна винести в окремий компонент, який приймає Binding для свого значення. Це спрощує тестування та перевикористання полів між різними формами. Батьківський компонент залишається єдиним власником усієї моделі форми.
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)
}
}
}
Компонент FormField приймає заголовок і Binding до рядка. Він відображає підпис і TextField, прив'язаний до переданого Binding. Будь-яка форма може використовувати FormField багаторазово, передаючи $property для кожного поля. Це знижує дублювання розмітки та централізує стилізацію текстових полів.
SwiftUI дозволяє створювати Binding вручну через ініціалізатор Binding(get:set:). Це корисно, коли потрібно додати логіку при читанні або записі значення. Наприклад, можна створити Binding, який форматує число перед збереженням, або Binding, який синхронізує значення з віддаленим сервером при кожній зміні.
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)
}
}
У лістингу кастомний emailBinding автоматично приводить текст до нижнього регістру та обрізає пробіли при кожній зміні. TextField використовує цей Binding замість прямого прив'язування до $email. Такий підхід дозволяє централізувати валідацію та трансформацію даних всередині Binding, не засмічуючи код обробниками onChange.
Перша і найпоширеніша помилка — передача значення замість Binding. Якщо дочірній компонент оголошує @Binding var text: String, а батько передає text (без $), компілятор видасть помилку: Cannot convert value of type 'String' to expected argument type 'Binding<String>'. Рішення — завжди використовувати префікс $ при передачі: $text.
Друга помилка — Binding на readonly-дані. Якщо дочірньому view потрібно тільки читати значення, не використовуйте @Binding — достатньо звичайного let або @State від батька. Binding передбачає можливість запису, а зайві права зміни ускладнюють налагодження та порушують принцип мінімальних привілеїв.
Третя проблема — Binding.constant у продакшені. Binding.constant(value) створює фіктивне зв'язування без зворотного зв'язку — зміни ігноруються. Використовуйте constant тільки для прототипування та прев'ю (Xcode Previews), але ніколи в реальному коді. Для тестів використовуйте Binding(get:set:) із контрольованою поведінкою.
Часті запитання
@State володіє даними та керує їх зберіганням у купі. @Binding тільки посилається на існуючий стан без володіння. @State завжди private, @Binding — вхідний параметр дочірнього view.
Так, через ініціалізатор Binding(get:set:) або Binding.constant(value). Також Binding можна отримати з @StateObject через проекцію $object.$property і з Publisher через Binding(get:set:) всередині Subscribe.
@Binding передається по ланцюгу: кожен проміжний компонент оголошує @Binding і передає його далі через $. Усі рівні посилаються на одне джерело даних у кореневому view.
Binding.constant створює німу обгортку — сеттер ігнорує нові значення. Він призначений тільки для прототипування та SwiftUI Previews, де не потрібен зворотний зв'язок від дочірнього компонента.
Так, Binding<T?> підтримується. Якщо передати Binding<String?>, дочірнє view зможе встановити nil. Це зручно для опціональних полів форми або станів із можливістю скидання.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також