В чем разница между property wrappers @State и @Binding в SwiftUI?

Ответ

@State и @Binding — это property wrappers для управления состоянием в декларативной парадигме SwiftUI, но они решают разные задачи: владение данными и их передача.

@State: Владение состоянием

  • Назначение: Хранит приватное, внутреннее состояние конкретного View.
  • Владение: Данные принадлежат и управляются этим View. SwiftUI создает и хранит значение.
  • Область видимости: Изменяется и используется только внутри этого View (или его дочерних вью через @Binding).
  • Жизненный цикл: SwiftUI уничтожает состояние, когда View удаляется.
  • Инициализация: Значение устанавливается при создании View.
struct ToggleView: View {
    // View ВЛАДЕЕТ этим булевым значением
    @State private var isOn: Bool = false 

    var body: some View {
        VStack {
            // Локальное состояние можно изменять напрямую
            Button("Переключить") { 
                isOn.toggle() // Изменение @State вызывает обновление View
            }
            // Для передачи дочернему вью используется $ для создания Binding
            ChildView(isOn: $isOn)
        }
    }
}

@Binding: Ссылка на состояние

  • Назначение: Создает двустороннюю связь (two-way binding) с состоянием, которым владеет другое (родительское) View.
  • Владение: Не владеет данными, только читает и записывает значение из источника (например, @State родителя).
  • Область видимости: Позволяет дочернему View изменять состояние родителя.
  • Синтаксис: Получается через префикс $ перед свойством с @State (или другим источником данных).
struct ChildView: View {
    // ChildView НЕ владеет данными, только имеет ссылку на них
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Опция", isOn: $isOn) // Toggle меняет значение в @State родителя
            .padding()
    }
}

Ключевая аналогия

  • @State — это как private переменная в классе.
  • @Binding — это как inout параметр функции, который позволяет функции изменять значение внешней переменной.

Когда что использовать?

  • Используйте @State для простых значений, принадлежащих этому View (флаги, текст поля ввода, значение слайдера).
  • Используйте @Binding когда нужно:
    • Передать состояние из родительского View в дочернее для модификации.
    • Создать общий контрол (кастомный Toggle, TextField), который должен работать с внешним состоянием.
    • Избежать подъема состояния (state hoisting) на несколько уровней вниз, передавая Binding.

Ответ 18+ 🔞

А, ну вот, опять эти ваши @State и @Binding! Слушай, давай разберёмся, а то у меня уже волнение ебать, как люди в них путаются, в рот меня чих-пых!

Представь себе, блядь, что твой View — это такая квартира. Так вот:

@State — это твой личный, ёбта, холодильник в этой квартире. Ты там хранишь свои пивко, колбаску. Ты им владеешь, ты его наполняешь, ты из него жрёшь. Данные — твои, приватные. Стоит он у тебя на кухне, и если ты съедешь с этой квартиры (View удалится), то холодильник, блядь, нахуй выкинут вместе с твоим полусъеденным салатом. Вот и весь жизненный цикл.

struct MyHumbleAbode: View {
    // Это мой личный холодильник, блядь. Я тут храню, сколько у меня бутылок пива.
    @State private var beerCount: Int = 10

    var body: some View {
        VStack {
            // Я могу сам из него взять!
            Button("Выпить пивка") {
                if beerCount > 0 { beerCount -= 1 } // Один глоток — и обновление интерфейса, красота!
            }
            // А могу дать доступ к нему соседу (дочернему виду), но через специальную ссылку!
            NeighborView(beerInMyFridge: $beerCount)
        }
    }
}

А теперь @Binding — это, сука, дубликат ключа от этого твоего холодильника! Ты даёшь этот ключ соседу (NeighborView). Сосед не владеет холодильником, овердохуища пива ему не купить — но он может прийти, открыть и взять твоё пиво! Или, что ещё хуже, подкинуть туда селёдку. Двусторонняя связь, ёпта! Он меняет — у тебя в основном холодильнике тоже меняется.

struct NeighborView: View {
    // У меня нет своего пива. Но есть ключ от твоего холодильника!
    @Binding var beerInMyFridge: Int

    var body: some View {
        HStack {
            Text("Пива в твоём холодильнике: (beerInMyFridge)")
            // И я, такой довольный, могу его опустошить!
            Button("Сосед выпил") {
                beerInMyFridge -= 1 // Ёбта, это же меняет значение в @State родителя!
            }
        }
        .padding()
    }
}

Ключевая мысль, блядь, запомни:
@State — это владение (холодильник твой).
@Binding — это ссылка, доступ (ключ от чужого холодильника).

Когда что юзать, бля?

  • @State — для всего мелкого и локального внутри одного экрана. Флажок включён/выключен, текст в поле, позиция слайдера. Всё, что живёт и умирает вместе с этим экраном.
  • @Binding — когда надо, блядь, дать возможность детям или кастомным компонентам менять состояние родителя. Кастомная кнопка, общий переключатель, поле ввода — всё, что должно пробрасывать изменения наверх.

Вот и вся философия. Не усложняй, а то сам от себя охуеешь. Главное — не давай @Binding первому встречному, а то останешься без пива и с селёдкой в холодильнике. Всё, вопрос закрыт.