iOS -在SwiftUI和MVVM中跟踪同一ViewModel中不同组件的多个加载状态的最佳方法?

ovfsdjhp  于 2023-09-30  发布在  Swift
关注(0)|答案(3)|浏览(106)

所以我基本上有一个ViewModel用于整个屏幕,它有多个组件,数据来自不同的API请求/端点。
我目前正在做的是拥有一个Observable对象,我可以从View(典型的状态SwiftUI状态处理)中对该对象进行更改,并通过@Published属性发出更改。粗略示例:

class ViewModel: ObservableObject {
     @Published private(set) var isFirstComponentLoading = true
     @Published private(set) var isSecondComponentLoading = true
     @Published private(set) var isThirdComponentLoading = true

     @Published private(set) var firstComponent: FirstComponentModel?
     private var api = SomeApi()

     //Imagine the exact same for every other component
     func loadFirstComponent() {
       isfirstComponentLoading = true
       Task { 
           let response = try await api.loadFirstComponent()
           await MainActor.run { 
               firstComponentModel = response
               isFirstComponentLoading = false
           }
       }
     }
}

视图(超标准的东西):

struct ContentView: View {
    @StateObject var vm = ViewModel()
    var body: some View {
        VStack {
         if vm.isFirstComponentLoading {
             LoadingView()
        } else { 
          FirstComponentView()
        }
    }
}

虽然这似乎是完美的工作,我有两个问题:

  • 我并不为每个加载状态都有一个变量+另一个变量也包含组件的模型而感到自豪,想知道在同一个ViewModel中跟踪多个加载状态的最佳方法是什么,我觉得我在这里遗漏了一些东西?
  • 一旦所有请求都成功完成,我将如何运行自定义代码?

谢谢你,谢谢

mlnl4t2r

mlnl4t2r1#

一种选择是使用这样的枚举:

enum LoadableResource<Value> {
  case notLoaded
  case loading
  case loaded(Value)
}

这样就不需要为每个选项维护两个变量,这可能会导致非法状态(假设isPropertyOneLoading的状态为true,而propertyOne实际上有一个值)。
然后,您可以在视图中的LoadableResource的当前状态上只添加switch

4si2a6ki

4si2a6ki2#

我很想知道一个更好的方法来做到这一点,但目前我这样做是通过记录我在后台运行的请求数量,并在每个请求完成时减少计数。当RunningCount等于0时,我更新IsLoading Bool,以在视图上切换加载指示器,使所有数据都可用。

class ViewModel: ObservableObject {
    @Published private var RunningCount: Int = 0
    @Published public var IsLoading: Bool = true
    
    @Published private(set) var firstComponent: ListFirstComponentModel?
    private var api = SomeApi()
    
    init(){
        //Imagine the exact same for every other component
        self.RunningCount+=1
        Task {
            let response = try await api.loadFirstComponent()
            await MainActor.run {
                firstComponentModel = response
                self.RunningCount-=1
                if self.RunningCount <= 0 {
                    self.IsLoading = false
                }
            }
        }
    }
}

通过将FirstComponentModel提取到ListFirstComponentModel中,可以在每个单独的对象上设置单独的loadingStatus,ListFirstComponentModel将从api.loadFirstComponent()中设置

struct ListFirstComponentModel {
    // store request loading status
    var loadingStatus: Bool = false
    // store api data here
    var data: [FirstComponentModel] = []
}
5uzkadbs

5uzkadbs3#

@StateObject不是为传统视图模型对象设计的,你需要学习View结构层次,否则你基本上会尝试重新实现SwiftUI View结构已经做的事情:依赖性和变更跟踪。
要在SwiftUI中使用async/await,请不要使用Task {},使用适当的task和task(id: someChangingValue)修饰符,但要做到这一点,首先需要删除该视图模型对象,创建一个View结构层次结构,并在父视图中使用@State,并向下传递let用于读取,Binding用于读取/写入。使用计算属性将数据从丰富的模型类型转换为简单的类型。这样,您就可以使用简单类型创建预览。
您可以将API/manager/controller放在EnvironmentKey中,例如比如内置的authorizationController。

相关问题