@Binding и общий доступ к состоянию
@Binding позволяет дочернему вью читать и изменять состояние, которым владеет вью-родитель. Это двусторонняя связь, а не копия.
Суть урока: @State владеет данными, @Binding — лишь ссылка на чужой источник истины. Знак $ перед @State-свойством создаёт binding для передачи вниз по дереву вью.
Часто кнопке или переключателю в дочернем вью нужно изменить данные родителя. Копировать значение нельзя — копия оторвётся от оригинала. Нужна связь с тем же источником истины. Это и есть @Binding:
struct ParentView: View {
@State private var isOn = false // владелец данных
var body: some View {
VStack {
Text(isOn ? "ON" : "OFF")
ChildToggle(isOn: $isOn) // $ создаёт binding
}
}
}
struct ChildToggle: View {
@Binding var isOn: Bool // ссылка, не копия
var body: some View {
Toggle("Питание", isOn: $isOn)
}
}Главная деталь — знак $. Перед @State-свойством он превращает значение в binding: $isOn — это не сам Bool, а двусторонняя связь с ним. Дочернее вью получает @Binding var isOn и, меняя его, меняет состояние родителя. Так один источник истины остаётся единственным, а управлять им могут несколько вью.
ParentView ChildToggle
@State isOn () <-- источник истины
| $isOn (binding)
+--------------------------------> @Binding isOn
|
изменение здесь <----------------------+ меняет ТОТ ЖЕ isOnПопробуй сам ▶ — запусти код прямо в браузере (Pyodide). Здесь нет Swift, но логика та же, что под капотом мобильного кода:
# Binding как общая изменяемая ссылка на одно значение.
class Box:
def __init__(self, value):
self.value = value # единственный источник истины
def child_toggle(binding): # получает ссылку, не копию
binding.value = not binding.value
state = Box(False) # @State у родителя
child_toggle(state) # передали $state вниз
print('После переключения:', state.value) # True — оригинал изменён
child_toggle(state)
print('Ещё раз:', state.value) # FalseКак работает под капотом
Binding — это пара функций: получить значение и установить его (get/set), указывающих на чужое хранилище. Когда дочернее вью пишет в @Binding, оно вызывает setter источника истины родителя, и SwiftUI перерисовывает всех, кто зависит от этого значения. Префикс $ обращается к так называемому projected value обёртки @State, которое как раз и является Binding. Поэтому данные не дублируются — есть один владелец и сколько угодно связей.
Частые ошибки
- Передать значение вместо $значение. Без $ уйдёт копия, и связь не установится.
- Объявить @State там, где нужен @Binding. Дочернее вью не владеет данными — ему нужен binding.
- Создавать несколько источников истины для одних данных. Это рассинхронизирует интерфейс.
Best practices
- Держите один источник истины (@State) и раздавайте доступ через $-binding.
- Дочерние вью, меняющие чужие данные, объявляйте с @Binding.
- Для предпросмотра используйте
.constant(значение), чтобы быстро подставить binding.
Итоги. @Binding — двусторонняя связь с чужим источником истины, создаваемая знаком $. Она позволяет дочерним вью менять состояние родителя без дублирования данных. Это второй столп управления состоянием после @State.
Шире контекста
Binding решает фундаментальную проблему любого UI: как дать нескольким частям интерфейса работать с одними и теми же данными без дублирования. Без него каждое дочернее вью получало бы копию, копии расходились бы, и интерфейс врал бы пользователю. Binding устанавливает живую двустороннюю связь: дочернее вью читает актуальное значение и пишет прямо в источник истины родителя. Многие встроенные элементы SwiftUI — Toggle, TextField, Slider, Stepper — спроектированы именно вокруг binding: вы передаёте им $value, и они сами двусторонне синхронизируются с вашим состоянием. Это объясняет вездесущий знак доллара в коде SwiftUI. Полезный приём для предпросмотра и тестов — .constant(значение), создающий фиктивный binding с неизменным значением, когда настоящий источник истины не нужен. Освоив связку @State в родителе и @Binding в ребёнке, вы получаете надёжный паттерн распределения состояния по дереву вью без рассинхронизации.