В этой статье изначально планировала написать продолжение первой части статьи. А именно: показать обещанные 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 замыканий по кругу (в контроллер, в сервис класс, реализующий этот конкретный Апи и тд по кругу), только чтобы отслеживать прогресс.

Делитесь в комментариях, сталкивались ли с этим или не было необходимости в отслеживании объектов из родительского класса? Как решали?

Комментарии (0)