Как обновить EnvironmentObject из дочернего представления при завершении асинхронной функции ViewModel

#mvvm #swiftui

#mvvm #swiftui

Вопрос:

В моем приложении SwiftUI у меня есть точка входа следующим образом

 @main
struct SomeApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            AppContainerView()
        }
    }
}
  
 struct AppContainerView: View {
    @StateObject var appState = AppState()
    var body: some View {
        if appState.isLoggedIn amp;amp; appState.hasSeenOnboarding {
            TabContainerView()
        } else {
            LandingView().environmentObject(appState)
        }
    }
}
  

Я могу обновить isLoggedIn вот так…

 class AppState: ObservableObject {
    var cancellables: [AnyCancellable] = []
    private let userService: UserServiceProtocol
    init(userService: UserServiceProtocol = UserService()) {
        self.userService = userService
        subscribeForAuthChanges()
    }
    
    func subscribeForAuthChanges() {
        userService.authChangeSubject.sink(receiveValue: { [weak self] auth, user in
            print(auth)
            if user != nil {
                self?.isLoggedIn = true
            }
        }).store(in: amp;cancellables)
    }
    
    @Published var isLoggedIn = false
    @Published var hasSeenOnboarding = false
}
  

Но когда дело доходит до hasSeenOnboarding этого, необходимо установить значение true после завершения какого-либо сетевого вызова в ViewModel в одном из LandingView дочерних представлений. Я могу сделать что-то вроде этого…

 struct ChildView: View {
  @EnvironmentObject var appState: AppState
  @StateObject var viewModel = ChildViewModel()

  var body: some View {
    VStack {
      //...
    }.onReceive($viewModel.networkCallCompleted) { completed in
       self.appState.hasSeenOnboarding = completed
    }
  }
}
  

Но это не ощущается right…is здесь есть лучший вариант для изменения AppState из дочернего представления / ViewModel при использовании MVVM? Или, возможно, лучший подход?

Ответ №1:

Возможной альтернативой является передача AppState в ChildViewModel и выполнение AppState обновлений непосредственно в модели представления.

 struct ParentView: View {
  @EnvironmentObject var appState: AppState

  var body: some View {
    ChildView(viewModel: ChildViewModel(appState: appState))
  }
}
  
 struct ChildView: View {
  @EnvironmentObject var appState: AppState
  @StateObject var viewModel: ChildViewModel

  var body: some View {
    ...
  }
}
  

Примечание

Возможно, вы захотите взглянуть на внедрение зависимостей, чтобы сделать его более чистым. Вот возможный пример: Простое внедрение зависимости с использованием @propertyWrapper.

Комментарии:

1. Спасибо, это работает — хотя, когда я изменяю свойство @Published hasSeenOnboarding на @AppStorage (AppState.hasSeenOnboardingKey) var hasSeenOnboarding: Bool = false, оно по какой-то причине не перезагружает представление

2. @jeh @AppStorage следует использовать только в представлении (точно так же, как @State ).