В этой статье изначально планировала написать продолжение первой части статьи. А именно: показать обещанные ProgressView и SkeletonView. Но тут на моём пути возникло неожиданное препятствие.
Обо всём по порядку.
Мы же понимаем, что просто так оставить View со всем функционалом внутри — такое себе. Обычно я накидываю быстрый функционал и UI в одном классе, а затем уже разделяю и усложняю. Использую MVVM архитектуру. И модальное окно не стало исключением. Проверив, что всё работает во View, я создала ViewModel, сделала её ObservableObject
class ReportsViewModel: ObservableObject { @Published var ReportData: [Report]? @Published var currentItem: Int ... }
Сама View соответственно теперь отвечает только за UI и единственное о чем она знает — это о своей модели.
struct ReportsView: View { @Environment(\.presentationMode) var presentationMode @ObservedObject var viewModel: ReportsViewModel ... }
В очередной раз казалось бы, что может пойти не так? Вот тут-то меня и ждало великое разочарование.
Когда я собирала весь функционал во View — я назначила все переменные как @ObservedObject. Соответственно, внутри View мы отслеживаем состояние ReportData через @ObservedObject. А так же несколько переменных обновлялись через делегаты от апи методов (например удаление статьи или лайк/дизлайк).
При переносе модели в ReportsViewModel мы так же следим за всей моделью и за каждой переменной внутри. Но, как оказалось, Combine не умеет отслеживать изменение состояния через отслеживание модели, ни через переменные, ни через делегаты. То есть когда мы храним всё внутри View — мы прекрасно отслеживаем изменения из родительского контроллера. Если же View инициирована с ViewModel — то всё, мы не имеем доступа к родительским переменным.
Это было довольно сильным разочарованием для меня. При том что Chat GPT упорно утверждал, что надо поставить везде @Published и будет мне счастье (аж выбесил!). Пришлось гуглить по старинке. Из этой статьи узнала что проблема не нова, а тянется аж с 2021 года. Забегая вперед, скажу, что предложенный в статье вариант решения не сработал. Более того, я подумала, что раз на данный момент это не работает, то возможно такую реализацию просто «починили», а это значит…
Ну что же, надо как-то решать проблему. Вот как не очень элегантно я сделала. При инициализации модального окна мы передаём туда замыкание, которое срабатывает уже внутри View. В это замыкание кладём обновление наших данных.
На примере ReportsView:
let viewModel = ViewModel( ... здесь обычные переменные) viewModel.onProgressComplete = { viewModel.progress = self.progress // Сюда кладём обновленный прогресс с апи } let swiftUIView = ReportsView(viewModel: viewModel) let hostingController = UIHostingController(rootView: swiftUIView) hostingController.modalPresentationStyle = .automatic hostingController.view.backgroundColor = .clear hostingController.hidesBottomBarWhenPushed = true DispatchQueue.main { [weak self] in self.present(hostingController, animated: true, completion: nil) }
ну и в модели это выглядит так:
class ReportsViewModel: ObservableObject { var onProgressComplete: (() -> Void)? // Вызываем при нажатии на кнопку или где необходимо ... }
Проблема в том, что по итогу пришлось воткнуть штук 5 замыканий по кругу (в контроллер, в сервис класс, реализующий этот конкретный Апи и тд по кругу), только чтобы отслеживать прогресс.
Делитесь в комментариях, сталкивались ли с этим или не было необходимости в отслеживании объектов из родительского класса? Как решали?
ссылка на оригинал статьи https://habr.com/ru/articles/851786/
Добавить комментарий