Разбираемся в State и Binding
Мы наконец-то добрались до одних из самых важных тем связанных с управлением данными, включая их модификацию, слежение за ними и передачу. Мы начнем с самых простых вещей таких как State и Binding
Давайте создадим новый проект и назовем его SUIStateBinding
Пока Xcode генерирует для нас файлы, давайте разберемся с теоретической основной вопроса связанного c State.
State в SwiftUI представляет собой свойство, которое хранит источник истины (source of truth) для представления. Это означает, что State определяет данные, которые используются в представлении, и контролирует их изменения. Когда значение State изменяется, SwiftUI автоматически обновляет представление, отражая эти изменения. Таким образом, State является ключевым элементом для реализации реактивности в SwiftUI.
С этим разобрались, давайте смотреть на практике, представим что мы хотим менять цвет кнопки по нажатию на нее, давайте попробуем реализовать это в коде, вставьте следующий фрагмент в свой файл ContentView.
struct ContentView: View {
private var buttonColor = Color.blue
var body: some View {
VStack {
Button("Change Color") {
// Изменяем цвет кнопки при нажатии
buttonColor = (buttonColor == Color.blue) ? Color.red : Color.blue
}
.padding()
.background(buttonColor)
.foregroundColor(.white)
.cornerRadius(10)
.animation(.easeInOut, value: buttonColor) // Добавляем анимацию при изменении цвета
}
}
}
И мы сразу же сталкиваемся с ошибкой, почему immutable если мы указали наш buttonColor как var?
В SwiftUI каждое представление (View) является неизменяемым (immutable), что означает, что однажды созданное представление не может быть изменено в процессе его жизненного цикла. Даже если вы используете var для переменных внутри body, они все равно не могут быть изменены после того, как представление было создано. Еще это можно объяснить следующим образом — в Swift struct являются значениями (value types), а не ссылками (reference types), и по умолчанию они immutable. Когда вы объявляете переменную типа struct, даже с ключевым словом var, вы можете изменять ее свойства (properties), но вы не можете изменить само значение переменной.
В случае с нашим примером, buttonColor является экземпляром структуры Color, и когда вы пытаетесь изменить его внутри body, вы фактически пытаетесь изменить само значение переменной buttonColor, что невозможно, потому что struct immutable по умолчанию.
Использование @State решает эту проблему, потому что аннотация @State создает специальное хранилище (storage), которое может изменяться внутри body, и SwiftUI автоматически обрабатывает изменения этого хранилища, перестраивая представление при необходимости, поэтому давайте добавим @State нашему buttonColor
struct ContentView: View {
@State private var buttonColor = Color.blue
var body: some View {
VStack {
Button("Change Color") {
// Изменяем цвет кнопки при нажатии
buttonColor = (buttonColor == Color.blue) ? Color.red : Color.blue
}
.padding()
.background(buttonColor)
.foregroundColor(.white)
.cornerRadius(10)
.animation(.easeInOut, value: buttonColor) // Добавляем анимацию при изменении цвета
}
}
}
Попробуйте понажимать на кнопку, как вы видите она анимировано меняет цвет, анимацию мы определили на 24-ой строчке, анимации не являются темой это части статей про SUI, так что это разберем позже, но в общем и целом теперь вы знаете как можно следить за состояниями свойств которые могут меняться в ходе работы приложения.
Внутри SwiftUI фреймворк использует механизм наблюдателей (observers) для отслеживания изменений в состоянии, помеченном аннотацией @State. Когда значение @State изменяется, SwiftUI автоматически запускает процесс перестроения представления, чтобы отобразить новое значение состояния.
Давайте посмотрим на Binding
Binding в SwiftUI представляет собой двунаправленную связь между двумя значениями, позволяя одному значению отслеживать и автоматически обновляться на основе изменений в другом значении. Binding играет ключевую роль в реактивной архитектуре SwiftUI, позволяя представлениям и вложенным представлениям взаимодействовать с общими данными.
Давайте создадим отдельный ButtonView который будет содержать наше Binding свойство и в зависимости от его изменений менять и отображение:
struct ButtonView: View {
@Binding var counter: Int
var body: some View {
Button {
counter += 1
} label: {
Text("\(counter)")
.padding(30)
.font(.system(size: 60))
.background(.red)
.clipShape(.circle)
}
}
}
Итак мы создали свойство counter которое будет каким то образом связано с другой вью, и при изменении этого самого каунтера мы хотим чтобы менялся текст внутри лейбла, давайте добавим этот ButtonView и другой текстовый объект который тоже будет следить за таким каунтером в наш ContentView
struct ContentView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("\(counter)")
.padding(30)
.font(.system(size: 60))
.background(.green)
.clipShape(.circle)
ButtonView(counter: counter)
}
}
}
Когда мы хотели добавить наш каунтер в ButtonView возникла ошибка, Xcode подсказывает нам что мы не можем использовать значение типа Int так как от нас ожидают Binding<Int> добиться этого легко просто подставив знак $
struct ContentView: View {
@State private var counter = 0
var body: some View {
VStack {
Text("\(counter)")
.padding(30)
.font(.system(size: 60))
.background(.green)
.clipShape(.circle)
ButtonView(counter: $counter)
}
}
}
Но что все это значит?
Знак $ перед переменной в SwiftUI используется для создания Binding к значению переменной. Когда вы используете $ перед переменной, вы получаете Binding к ее значению, что позволяет вам читать и изменять это значение из других представлений или внутри замыканий.
Что касается Binding<Int> под капотом, это просто тип, который описывает Binding к значению типа Int. В SwiftUI существует множество типов Binding, каждый из которых соответствует определенному типу данных, например Binding<String>, Binding<Double>, Binding<Bool> и т.д. Эти типы позволяют вам создавать Binding к разным типам данных и работать с ними в SwiftUI.
Чтобы стало чуть более понятно я бы все же рекомендовал окунуться в описание Binding<Value>, посмотреть на его инициализатор и почитать примеры которые приводит здесь Apple
Для нас все таки больше важно, что после добавления значка доллара мы получили Binding<Int> свойство переданное в наш ButtonView и теперь любое изменение каунтера который является свойством ContentView будет происходить как в ButtonView так и в ContentView
Когда использовать State:
Локальное состояние внутри представления: State прекрасно подходит для хранения локального состояния внутри одного представления. Например, вы можете использовать State для хранения текста в текстовом поле или значения счетчика.
Управление взаимодействием с пользователем: Когда вам нужно реагировать на действия пользователя и отображать соответствующие изменения в интерфейсе, State позволяет легко отслеживать эти изменения и обновлять представление соответствующим образом.
Простые приложения или представления: State отлично подходит для простых случаев использования, когда вам не нужно передавать данные между различными представлениями или управлять сложными состояниями.
Когда использовать Binding:
Передача данных между представлениями: Binding идеально подходит для передачи данных между различными представлениями. Например, если вам нужно передать значение из одного представления в другое для отображения или редактирования, Binding обеспечивает двунаправленную связь между этими значениями.
Общее состояние между представлениями: Если вам нужно обеспечить согласованность данных между несколькими представлениями, вы можете использовать Binding для связи этих данных и обеспечения их согласованности.
В каких случаях использовать оба:
В некоторых случаях вам может понадобиться использовать и State, и Binding в одном и том же приложении. Например, вы можете использовать State для хранения локального состояния внутри представления, а Binding для передачи этого состояния в другие представления. Это позволяет вам организовать эффективное взаимодействие между различными частями вашего приложения, управляя как локальным, так и общим состоянием.
Итоги
В этой части мы разобрались с State и Binding, как в теории так и в практике, но я предлагаю вам еще попрактиковаться. Как вариант попробуйте создать три разных View, отобразите их в одном ContenView и свяжите какие нибудь данные которые считаются в них с еще одной View которая будет отображать эти самые данные
Для примера: Верхняя вью будет считать количество нажатий во всех вью которые под ней, красная вью, зеленая и синяя это будут разные вью со своими собственными каунтерами.
Как и прежде подписывайтесь на мой телеграм-канал. В ближайшее время выйдут следующие части уроков по SwiftUI.
Спасибо за прочтение!