SwiftUI как обновить представление при изменении свойств модели в viewModel

Вопрос или проблема

Это мой код:


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 требует внимательного подхода к организации ваших классов и структур.

Выбор подхода будет зависеть от конкретных требований вашего приложения и предпочтений в архитектуре.

Оцените материал
Добавить комментарий

Капча загружается...