Вопрос или проблема
Это мой код:
class MyModel: ObservableObject {
@Published var title: String = ""
init(title: String) {
self.title = title
}
}
class MyViewModel: ObservableObject {
@Published var model = MyModel(title: "Apple")
func update() {
model.title = "Banana"
}
}
struct MyView: View {
@StateObject var viewModel = MyViewModel()
var body: some View {
VStack(spacing: 20) {
Text(viewModel.model.title)
Button {
viewModel.updateToBanana()
} label: {
Text("Banana")
}
}
}
}
Как я могу обновить представление, когда просто установлю
model.title = “Banana”
Я пробовал изменить: @Published на: @ObservedObject
Все равно не работает
Причина, по которой обновление не вызывает изменения в представлении, заключается в том, что viewModel
не отправляет обновление, когда изменяется базовая модель. Это связано с тем, что MyModel
является class
(что ей должно быть, если реализует ObservableObject
), а не struct
. Тот же экземпляр класса по-прежнему удерживается MyViewModel
после обновления, даже если его базовое свойство изменилось.
Чтобы заставить изменение быть опубликованным, MyViewModel
может явно вызвать objectWillChange.send()
перед передачей обновления. Также, вероятно, нет особого смысла иметь model
как @Published
, вы можете объявить его с let
вместо:
class MyViewModel: ObservableObject {
let model = MyModel(title: "Apple") // 👈 не опубликовано
func update() {
objectWillChange.send() // 👈 добавлено
model.title = "Banana"
}
}
Другой подход заключается в том, чтобы подписаться на обновления от базовой модели в вашем представлении, например, объявив ссылку на MyModel
как @ObservedObject
. Однако, если модель в viewModel
будет заменена новым экземпляром, то представление может оказаться подписанным на неправильный экземпляр. Это еще одна причина, почему может быть лучше, если модель была бы объявлена в MyViewModel
с использованием let
вместо var
.
Ответ или решение
Вопрос, с которым вы столкнулись, касается обновления пользовательского интерфейса SwiftUI при изменении свойства модели в ViewModel
. Рассмотрим это подробнее, с учётом вашего кода и предложенных решений.
Проблема обновления представления
В вашем коде объект MyModel
является наблюдаемым через ObservableObject
, и его свойство title
помечено как @Published
. Это позволяет вашему представлению (View) отслеживать изменения в модели. Однако, когда вы изменяете model.title
в MyViewModel
, ваше представление не обновляется, так как само свойство model
не вызывает перерисовку, когда его внутреннее состояние изменяется.
Объяснение поведения SwiftUI
В SwiftUI изменения, инициируемые изменением свойств @Published
, будут отслеживаться только на уровне самого свойства, а не на уровне его содержимого. Это объясняет, почему просто изменение model.title
не вызывает обновление вашего представления.
Возможные решения
Решение 1: Явная отправка изменений
Одним из решений является явная отправка уведомления об изменении состояния из MyViewModel
. Это можно сделать с помощью вызова objectWillChange.send()
перед обновлением модели:
class MyViewModel: ObservableObject {
let model = MyModel(title: "Apple") // Убираем @Published
func update() {
objectWillChange.send() // Уведомление об изменении
model.title = "Banana" // Изменяем модель
}
}
Это заставляет SwiftUI заново отрисовать ваше представление, так как мы явно сообщаем, что что-то изменилось.
Решение 2: Использование @ObservedObject
Другим подходом является использование @ObservedObject
для модели внутри представления. Вы можете сделать MyModel
наблюдаемым и непосредственно подписаться на его обновления:
struct MyView: View {
@StateObject var viewModel = MyViewModel()
var body: some View {
VStack(spacing: 20) {
Text(viewModel.model.title)
Button {
viewModel.update()
} label: {
Text("Banana")
}
}
.environmentObject(viewModel.model) // Передаем модель как окружение
}
}
class MyModel: ObservableObject {
@Published var title: String = "" // Остается @Published
init(title: String) {
self.title = title
}
}
Этот подход позволяет вашему представлению реагировать на изменения состояния модели напрямую.
Заключение
Согласно вашему коду, оптимальным будет явная отправка обновлений из ViewModel
или же использование @ObservedObject
для модели. Оба подхода имеют свои преимущества в различных контекстах. Важно понимать, что SwiftUI построен на архитектуре реактивного программирования. Поэтому чтение и передача изменений к UI требует внимательного подхода к организации ваших классов и структур.
Выбор подхода будет зависеть от конкретных требований вашего приложения и предпочтений в архитектуре.