Совместное использование состояния между компонентами
Рассмотрим следующий сценарий: у вас есть ParentView
с двумя дочерними представлениями, ChildView1
и ChildView2
. На ChildView1
у вас есть кнопка, которая должна запускать действие в ChildView2
.
Теперь, нажав эту кнопку, мы хотим изменить текст в этом текстовом поле на что-то более подходящее. Начнем с определения псевдонима для нашего закрытия. Если вы не знаете, что такое закрытие, это, по сути, метод. Подробнее о замыканиях вы можете прочитать в документации.
Давайте добавим следующие псевдонимы типов над нашим объявлением ParentView
:
typealias OnClickHandler = (() -> Void)
Так получается:
typealias OnClickHandler = (() -> Void) struct ParentView: View { ... }
И инициализируем его как свойство @State
в нашем ParentView
:
struct ParentView: View { @State var onClick: OnClickHandler = { } ... }
Идея здесь в том, что этот onClick
, определенный в ParentView
, является нашим единственным источником истины. Мы не хотим, чтобы где-то в стеке вызовов инициализировалось другое замыкание. Мы хотим, чтобы это было передано в оба наших ChildView.
В ChildView2
, где находится наша кнопка, мы добавляем ее как @Binding
, поскольку она уже инициализирована в нашем ParentView
, а ChildView2
на данный момент только управляет ею. Затем мы добавляем его как действие к нашей кнопке:
Вы заметите, что мы удалили старое замыкание, в котором мы печатали наше сообщение, и просто передали наше в качестве параметра. Это не обязательно, но короче и чище.
На этом этапе ваш ParentView
уведомляет вас о том, что вам не хватает параметра onClick
при инициализации ChildView2
, поэтому давайте просто добавим это:
Вы заметите, что мы передали $onClick
в ChildView2
, поскольку мы определили наше свойство как @Binding
, поэтому мы используем $
для передачи привязки, а не значения.
Теперь мы собираемся сделать то же самое с нашим ChildView1
- добавить свойство привязки - но на этот раз мы также собираемся написать функцию, которая вызывается при нажатии кнопки, и мы собираемся назначить эту функцию для наше пройденное закрытие:
Магия здесь вызывает onAppear
элемента Text. Это означает, что когда появится это поле (подумайте о viewDidAppear
в UIKit), мы собираемся запустить следующий блок кода. В этом блоке кода мы назначаем нашему закрытию onClick
функцию, которая изменяет значение нашей строки.
Если вы хотите выглядеть необычно или ваш метод больше, вы даже можете извлечь весь блок кода в другой метод и назначить его onClick
:
Теперь, благодаря волшебству SwiftUI и Combine, вам удалось связать два представления, которые ничего не знают друг о друге. Поздравляю!
Бонус
«Что, если я хочу сделать это с помощью представления и UIViewRepresentable, где у меня нет onAppear
?»
Отличный вопрос, Алекс!
В этом случае мы воспользуемся функцией:
Как видите, я отправил его в фоновый поток. Это связано с тем, что компилятор уведомит нас во время выполнения, что «изменение состояния во время обновления представления приведет к неопределенному поведению».
Это способ Apple сказать, что мы обновляем состояние во время перерисовки представления.
Вот и все. Полный код доступен на GitHub. Удачного кодирования!