ios - 如何在我的 SwiftUI 应用程序中初始化我的 ViewModel

标签 ios swift xcode swiftui

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

如果鸡尾酒数据结构,这是我的 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)
            }
        }        
    }
}

如有任何帮助,我们将不胜感激。谢谢。

最佳答案

这是一种常见的模式,使用 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 init。在这种情况下,@StateObject 的 init 将通过传递到 ViewurlString 调用。因为 StateObjectwrappedValue 参数使用自动关闭并且只有在将 View 添加到层​​次结构时才会运行,所以您不必担心 View 模型会被重新- 在 View 的每个 init 上初始化。

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

关于ios - 如何在我的 SwiftUI 应用程序中初始化我的 ViewModel,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69725312/

相关文章:

ios - 识别 UIWebView 中的多个链接

ios - UIViewController 的子类所需的初始值设定项

ios - 复合主键域/swift

iphone - 解析 Twitter JSON

ios - Xcode 错误 Command/Applications/Xcode.app/Contents/Developer/usr/bin/momc 失败,退出代码 1

ios - 如何在 XCode 4.2 中设置文档类型?

javascript - WebKit 用户内容 Controller :didReceiveScriptMessage: not getting called for iOS 13

javascript - 如何控制 Cordova 中的内存使用?

ios - 使用 Swift 在 iOS 中进行低级图形处理

ios - 类型 'UIViewController' 的值没有成员 'addSubview' Swift2