如何在 SwiftUI 应用程序中初始化 ViewModel

sjl*_*nth 4 xcode ios swift swiftui

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

\n

这是我的 swift 文件 ifcocktail 数据结构;

\n
struct Drinks: Decodable {\n    var cocktails: [Cocktail]\n}\n\nstruct Cocktail: Decodable, Identifiable {\n    var id: String {\n        return idDrink\n    }\n    let idDrink: String\n    let strDrink: String\n    let strDrinkThumb: String\n    let strAlcoholic: String\n    let strGlass: String\n    let strInstructions: String\n    let strIngredient1: String?\n    let strIngredient2: String?\n    let strIngredient3: String?\n    let strIngredient4: String?\n    let strIngredient5: String?\n    let strIngredient6: String?\n    let strIngredient7: String?\n    let strIngredient8: String?\n    let strIngredient9: String?\n    let strIngredient10: String?\n    let strIngredient11: String?\n    let strIngredient12: String?\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是我的 NetworkManager 类;

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

这是我的 ViewModel 类;

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

这是我的 DetailsView 结构;

\n
struct DetailsView: View {\n    \n    @StateObject var viewModel = ViewModel()\n        \n    var body: some View {\n        \n        List(viewModel.drinks.cocktails) { cocktail in\n\n            VStack(alignment: .center) {\n                HStack(alignment: .center) {\n                    Text(cocktail.strDrink + "  -")\n                        .navigationTitle("Cocktail by first letter")\n                        .frame(alignment: .center)\n                    Text(cocktail.strAlcoholic)\n                        .frame(alignment: .center)\n                }\n                \n                WebImage(url: URL(string: cocktail.strDrinkThumb))\n                    .resizable()\n                    .frame(width: UIScreen.main.bounds.width - 20.0, height: UIScreen.main.bounds.width - 20.0, alignment: .center)\n                    \n                \n                Text("~ Ingredients List ~\\n").frame(alignment: .center)\n                \n                ForEach(viewModel.buildIngredients(cocktail), id: \\.self) { ingredient in\n                    Text(ingredient)\n                }\n                \n                Text("\\n~ Recipe Instructions ~\\n\\n")\n                \n                Text(cocktail.strInstructions + "\\n").fixedSize(horizontal: false, vertical: true)\n            }\n        }        \n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

jn_*_*pdx 15

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

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用您的View's init。在这种情况下,@StateObject调用 的 init 并将其urlString传递到View. 因为StateObject'swrappedValue参数使用自动闭包,并且仅在将视图添加到层​​次结构中时才运行,因此您不必担心视图模型将在init每个View.

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
    }
}

Run Code Online (Sandbox Code Playgroud)