【问题标题】:How do I initialise my ViewModel in my SwiftUI app如何在我的 SwiftUI 应用程序中初始化我的 ViewModel
【发布时间】:2021-10-26 14:48:40
【问题描述】:

我有一个 SwiftUI 应用程序,它使用公共 API 按名称下载鸡尾酒数据,但我对 SwiftUI 不是很熟悉,并且在我的 DetailsView 文件中看不到初始化视图模型的方法。

如果鸡尾酒数据结构,这是我的 swift 文件;

struct Drinks: Decodable {
    var cocktails: [Cocktail]
}

struct Cocktail: Decodable, Identifiable {
    var id: String {
        return idDrink
    }
    let idDrink: String
    let strDrink: String
    let strDrinkThumb: String
    let strAlcoholic: String
    let strGlass: String
    let strInstructions: String
    let strIngredient1: String?
    let strIngredient2: String?
    let strIngredient3: String?
    let strIngredient4: String?
    let strIngredient5: String?
    let strIngredient6: String?
    let strIngredient7: String?
    let strIngredient8: String?
    let strIngredient9: String?
    let strIngredient10: String?
    let strIngredient11: String?
    let strIngredient12: String?
}

这是我的 NetworkManager 类;

class NetworkManager {
        
    func fetchData(_ urlString: String, completion: @escaping (Drinks, Bool) -> Void) {
        guard let url = URL(string: urlString) else { return }
        var drinks: Drinks?
        let session = URLSession(configuration: .default)
        let task = session.dataTask(with: url) { data, response, error in
            if error == nil {
                let decoder = JSONDecoder()
                guard let safeData = data else { return }
                do {
                    drinks = try decoder.decode(Drinks.self, from: safeData)
                        completion(drinks!, false)
                } catch let error {
                    print(error.localizedDescription)
                    if error.localizedDescription == "The data couldn’t be read because it is missing." {
                            completion(drinks ?? Drinks(cocktails: [Cocktail]()), true)
                    } else {
                        print(error.localizedDescription)
                    }
                }
            }
        }
        task.resume()
    }
}

这是我的 ViewModel 类;

class ViewModel: ObservableObject {
    
    let networkManager = NetworkManager()
    var urlString: String
    @Published var drinks: Drinks = Drinks(cocktails: [Cocktail]())
    @Published var dataIsFound: Bool = true
    
    init(urlString: String) {
        self.urlString = urlString
        FetchData()
    }
    
    func FetchData() {
    
        networkManager.fetchData(urlString) { results, error in
            DispatchQueue.main.async {
            self.drinks = results
            self.dataIsFound = !error
            }
        }
    }

这是我的 DetailsView 结构;

struct DetailsView: View {
    
    @StateObject var viewModel = ViewModel()
        
    var body: some View {
        
        List(viewModel.drinks.cocktails) { cocktail in

            VStack(alignment: .center) {
                HStack(alignment: .center) {
                    Text(cocktail.strDrink + "  -")
                        .navigationTitle("Cocktail by first letter")
                        .frame(alignment: .center)
                    Text(cocktail.strAlcoholic)
                        .frame(alignment: .center)
                }
                
                WebImage(url: URL(string: cocktail.strDrinkThumb))
                    .resizable()
                    .frame(width: UIScreen.main.bounds.width - 20.0, height: UIScreen.main.bounds.width - 20.0, alignment: .center)
                    
                
                Text("~ Ingredients List ~\n").frame(alignment: .center)
                
                ForEach(viewModel.buildIngredients(cocktail), id: \.self) { ingredient in
                    Text(ingredient)
                }
                
                Text("\n~ Recipe Instructions ~\n\n")
                
                Text(cocktail.strInstructions + "\n").fixedSize(horizontal: false, vertical: true)
            }
        }        
    }
}

任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 如果你想详细初始化它们,它来自父级,请使用 StateObject 代替
  • @loremipsum 我已经尝试过@StateObject var viewModel = ViewModel(urlString: urlString),但该项目无法构建,因为self 在属性初始化程序运行之前不可用
  • 用空初始化器初始化stateobject,使urlString成为Published变量,添加didSet并在didSet与init中调用FetchData,并在onAppear中设置urlString
  • 那是因为你试图用你单独传入的urlString 来初始化它。在您的父母中,拨打电话为DetailsView(viewModel: ViewModel(urlString: urlString)。将您的@ObservedObject 设为@StateObject 并从DetailsView() 中删除let urlString: String
  • @loremipsum 我已经完成了所有这些,但 urlString 初始化值需要来自 DetailsView 类之外。这就是我使用 urlString 参数初始化 DetailsView 的原因

标签: ios swift xcode swiftui


【解决方案1】:

这是一种常见的模式,使用ViewModel()进行初始化,然后在onAppear上调用fetchData

class ViewModel: ObservableObject {
    
    let networkManager = NetworkManager()
    @Published var drinks: Drinks = Drinks(cocktails: [Cocktail]())
    @Published var dataIsFound: Bool = true
    
    
    func fetchData(urlString: String) {
        //call fetchData on network manager
    }
}

struct DetailsView: View {
    
    var urlString : String
    @StateObject private var viewModel = ViewModel()
        
    var body: some View {
        
        List(viewModel.drinks.cocktails) { cocktail in
            //list content
        }
        .onAppear {
            viewModel.fetchData(urlString: urlString)
        }
    }
}

另一种选择是使用您的View's init。在这种情况下,@StateObject 的 init 被调用,urlString 被传递到 View。因为StateObjectwrappedValue 参数使用和自动关闭,并且只有在视图添加到层​​次结构时才会运行,所以您不必担心视图模型将在@ 的每个init 上重新初始化987654333@.

class ViewModel: ObservableObject {
    
    let networkManager = NetworkManager()
    @Published var drinks: Drinks = Drinks(cocktails: [Cocktail]())
    @Published var dataIsFound: Bool = true
    
    init(urlString: String) {
        fetchData(urlString: urlString)
    }
    
    func fetchData(urlString: String) {
        //call fetchData on network manager
    }
}

struct DetailsView: View {
    
    @StateObject private var viewModel : ViewModel
    
    init(urlString: String) {
        _viewModel = StateObject(wrappedValue: ViewModel(urlString: urlString))
    }
    
    var body: some View {
        //body content
    }
}

【讨论】:

  • 这回答了你的问题吗?
猜你喜欢
  • 2021-07-14
  • 2021-12-20
  • 2011-08-27
  • 1970-01-01
  • 2013-01-27
  • 1970-01-01
  • 2021-07-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多