@Binding — що це, як працює та приклади в SwiftUI

Автор: IT Sectr Опубліковано: 2026-06-19 Час читання: 7 хв

@Binding — це Property Wrapper у SwiftUI, який створює посилання на дані, якими володіє інший компонент. Binding не зберігає значення самостійно — він лише надає доступ до існуючого джерела істини через проекцію $. За даними Apple Developer Documentation (2025), Binding забезпечує реактивний двосторонній зв'язок між батьківським і дочірнім view без прямого володіння даними. @Binding — ключовий механізм для передачі змінюваного стану вниз по ієрархії.

Головне

  • @Binding — Property Wrapper для створення посилання на стан батьківського view
  • Без володіння — Binding не зберігає дані, а надає доступ до джерела істини
  • Проекція $ — $stateValue створює Binding із @State або @StateObject
  • Двосторонній зв'язок — зміни в дочірньому view негайно відображаються в батьку
  • Binding.constant — фіксоване значення для прототипування без зворотного зв'язку

Що таке @Binding у SwiftUI?

@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

Механізм двостороннього зв'язку через @Binding будується на проекціях Property Wrappers. Коли батько оголошує @State var value: T, SwiftUI автоматично генерує проекцію $value типу Binding<T>. Передаючи $value в дочірній компонент із @Binding var value: T, ви з'єднуєте обидва view з однією коміркою пам'яті. Будь-який запис через Binding у дочірньому view викликає перемальовку обох компонентів.

swift
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 без передачі всього об'єкта. Такий підхід забезпечує більш вузьке прив'язування та запобігає зайвим перемальовкам.

@Binding vs колбеки: що вибрати

До SwiftUI стандартним способом передачі змін вгору по ієрархії були колбеки та делегати: батько передавав замикання, дочірній компонент викликав його при зміні. @Binding пропонує альтернативу з меншим обсягом коду та більш декларативним синтаксисом. Замість передачі completion-замикання достатньо передати $stateValue.

Критерій@BindingКолбеки
КодОдна анотація + $Замикання + виклик
БагаторівневістьАвтоматичнаЛанцюг замикань
ТестуванняBinding(value:constant)Mock-замикання
ЧитабельністьВисокаСередня
ГнучкістьТільки даніБудь-яка логіка

Використовуйте @Binding, коли дочірньому view потрібно тільки читати та змінювати значення. Якщо потрібно виконати побічні дії при зміні (валідація, логування, мережевий запит), комбінуйте Binding з колбеком: передавайте Binding для даних і замикання для подій. Наприклад, TextField може прив'язатися до Binding, а onChange запускатиме валідацію.

Паттерни використання @Binding у проектах

@Binding застосовується в кількох типових сценаріях. Перший — кастомні контроли: перемикачі, повзунки, колірні пікери та інші інтерактивні елементи приймають Binding для двосторонньої синхронізації. Другий — модальні вікна: прапорець показу sheet передається як Binding, дозволяючи дочірньому view закрити себе через presentationMode або пряме встановлення.

Третій паттерн — форми з розділенням. Якщо форма складається з багатьох полів, кожне поле можна винести в окремий компонент, який приймає Binding для свого значення. Це спрощує тестування та перевикористання полів між різними формами. Батьківський компонент залишається єдиним власником усієї моделі форми.

swift
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 для кожного поля. Це знижує дублювання розмітки та централізує стилізацію текстових полів.

Створення кастомних Binding

SwiftUI дозволяє створювати Binding вручну через ініціалізатор Binding(get:set:). Це корисно, коли потрібно додати логіку при читанні або записі значення. Наприклад, можна створити Binding, який форматує число перед збереженням, або Binding, який синхронізує значення з віддаленим сервером при кожній зміні.

swift
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. Якщо дочірній компонент оголошує @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:) із контрольованою поведінкою.

Часті запитання

У чому різниця між @Binding і @State?

@State володіє даними та керує їх зберіганням у купі. @Binding тільки посилається на існуючий стан без володіння. @State завжди private, @Binding — вхідний параметр дочірнього view.

Чи можна створити Binding без @State?

Так, через ініціалізатор Binding(get:set:) або Binding.constant(value). Також Binding можна отримати з @StateObject через проекцію $object.$property і з Publisher через Binding(get:set:) всередині Subscribe.

Як передати Binding через кілька рівнів вкладеності?

@Binding передається по ланцюгу: кожен проміжний компонент оголошує @Binding і передає його далі через $. Усі рівні посилаються на одне джерело даних у кореневому view.

Чому Binding.constant не оновлює інтерфейс?

Binding.constant створює німу обгортку — сеттер ігнорує нові значення. Він призначений тільки для прототипування та SwiftUI Previews, де не потрібен зворотний зв'язок від дочірнього компонента.

Чи може @Binding бути опціональним?

Так, Binding<T?> підтримується. Якщо передати Binding<String?>, дочірнє view зможе встановити nil. Це зручно для опціональних полів форми або станів із можливістю скидання.

Підсумки

  • @Binding — Property Wrapper для двостороннього зв'язку з даними батьківського view
  • Не володіє даними — тільки надає доступ до джерела істини
  • Проекція $ перетворює @State, @StateObject на Binding для передачі дочірнім view
  • Кастомний Binding створюється через Binding(get:set:) з додатковою логікою
  • Binding.constant — тільки для прев'ю та прототипів
  • Багаторівнева передача — Binding проходить через будь-яку глибину вкладеності
  • Альтернатива колбекам — декларативний спосіб модифікації стану з дочірніх компонентів

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також