从结构数据 SwiftUI 创建一个数组

Posted

技术标签:

【中文标题】从结构数据 SwiftUI 创建一个数组【英文标题】:Creating an array from stuct data SwiftUI 【发布时间】:2022-01-18 05:28:56 【问题描述】:

首先,我很抱歉这个菜鸟问题,但我似乎无法弄清楚这一点。

我对编码非常陌生,刚开始接触 SwiftUI,学习了几门课程,并开始涉足尝试创建一些基本应用程序。

我目前正在开发一个执行 API 调用并显示数据的应用。

我的问题是,我试图将解码后的数据放入一个数组中,这听起来很简单,我想我错过了一些非常简单的东西,但对于我的生活,我似乎无法弄清楚。

下面是我拥有的可编码结构

struct Drinks: Codable, Identifiable 
    let id = UUID()
    let strDrink : String
    let strInstructions: String
    let strDrinkThumb: String?
    let strIngredient1: String?
    let strIngredient2: String?
    let strIngredient3: String?
    let strIngredient4: String?
    let strIngredient5: String?

我想将成分放入一个数组中,这样我就可以在列表等中浏览它们

import SwiftUI

struct IngredientView: View 
    let drink : Drinks
    let ingredientArray : [String] = [] // I want to append the ingredients here
    var body: some View 
        GroupBox() 
            DisclosureGroup("Drink Ingredience") 
                ForEach(0..<3)  item in
                    Divider().padding(.vertical, 2)
                    HStack 
                        Group 
                            // To use the array here
                        
                        .font(Font.system(.body).bold())
                        Spacer(minLength: 25)
                    
                
            
        
    

再次,抱歉这个菜鸟问题可能有一个简单的答案,但值得一试:D

谢谢!

【问题讨论】:

JSON 的设计为strIngredient1strIngredient2 等不是一个好主意。您应该将所有这些成分都放在一个数组中,因为它可以是任意长度。 成分可能是 api 的一部分(示例):thecocktaildb.com/api/json/v1/1/search.php?s=margarita 另见这个 SO 问题:***.com/questions/69931557/… 和他的相关 github 项目:github.com/sjlearmonth/Cocktail-Heaven 你应该能够解决从那。 【参考方案1】:

首先你的设计不能工作,因为你忽略了根对象,一个带有drinks键的结构体

struct Root : Decodable 
    let drinks : [Drink]

一个可能的解决方案是编写一个自定义的init 方法。但是这有点棘手,因为您必须注入动态 CodingKeys 才能在循环中解码成分

首先创建一个自定义的CodingKey结构

struct AnyKey: CodingKey 
    var stringValue: String
    var intValue: Int?
    
    init?(stringValue: String)   self.stringValue = stringValue  
    init?(intValue: Int)  return nil  // will never be called

Drink 结构中——顺便说一下,它应该以单数形式命名——指定第二个容器,动态创建成分键,在循环中解码成分并将结果附加到数组,直到值为nil。有人应该告诉服务的所有者,他们的 JSON 结构非常业余。由于您必须指定CodingKeys,因此我将键映射到更有意义且冗余更少的结构成员名称。

struct Drink: Decodable, Identifiable 
    
    private enum CodingKeys : String, CodingKey 
        case name = "strDrink", instructions = "strInstructions", thumbnail = "strDrinkThumb"
    
    
    let id = UUID()
    let name: String
    let instructions: String
    let thumbnail: URL
    let ingredients: [String]
    
    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.instructions = try container.decode(String.self, forKey: .instructions)
        self.thumbnail = try container.decode(URL.self, forKey: .thumbnail)
        var counter = 1
        var temp = [String]()
        let anyContainer = try decoder.container(keyedBy: AnyKey.self)
        while true 
            let ingredientKey = AnyKey(stringValue: "strIngredient\(counter)")!
            guard let ingredient = try anyContainer.decodeIfPresent(String.self, forKey: ingredientKey) else  break 
            temp.append(ingredient)
            counter += 1
        
        ingredients = temp
    

在视图中显示这样的成分

struct IngredientView: View 
    let drink : Drink
 
    var body: some View 
        GroupBox() 
            DisclosureGroup("Drink Ingredience") 
                ForEach(drink.ingredients)  ingredient in
                    Divider().padding(.vertical, 2)
                    HStack 
                        Group 
                            Text(ingredient)
                        
                        .font(Font.system(.body).bold())
                        Spacer(minLength: 25)
                    
                
            
        
    

【讨论】:

谢谢!对不起,我确实有根,只是没有意识到我应该把它贴在这里(新手)。不过,我会检查您的解决方案,谢谢!【参考方案2】:

您可以使用这种方法将所有成分放入一个数组中并使用 它在列表中。这个想法是使用一个函数将所有成分收集到一个数组中 Ingredient 对象。您还可以使用计算属性。 最好使用Ingredient 对象并声明它Identifiable 这样当您在列表和 ForEach 中使用它们时,每一个都将是 唯一的,即使名称相同。

import SwiftUI

@main
struct TestApp: App 
    var body: some Scene 
        WindowGroup 
            ContentView()
        
    


struct ContentView: View 
    @State var drinkList = [Drink]()
    
    var body: some View 
        List 
            ForEach(drinkList)  drink in
                VStack 
                    Text(drink.strDrink).foregroundColor(.blue)
                    Text(drink.strInstructions)
                    ForEach(drink.allIngredients())  ingr in
                        HStack 
                            Text(ingr.name).foregroundColor(.red)
                            Text(ingr.amount).foregroundColor(.black)
                        
                    
                
            
        
        .task 
            let theResponse: ApiResponse? = await getData(from: "https://www.thecocktaildb.com/api/json/v1/1/search.php?s=margarita")
            if let response = theResponse 
                drinkList = response.drinks
            
       
    
    
    func getData<T: Decodable>(from urlString: String) async -> T? 
        guard let url = URL(string: urlString) else 
            print(URLError(.badURL))
            return nil // <-- todo, deal with errors
        
        do 
            let (data, response) = try await URLSession.shared.data(for: URLRequest(url: url))
            guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else 
                print(URLError(.badServerResponse))
                return nil // <-- todo, deal with errors
            
            return try JSONDecoder().decode(T.self, from: data)
        
        catch 
            print("---> error: \(error)")
            return nil // <-- todo, deal with errors
        
    
    


struct ApiResponse: Decodable 
    var drinks: [Drink]

    
struct Drink: Decodable, Identifiable 
    let id = UUID()
    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?
    
    var strMeasure1: String?
    var strMeasure2: String?
    var strMeasure3: String?
    var strMeasure4: String?
    var strMeasure5: String?
    var strMeasure6: String?
    var strMeasure7: String?
    var strMeasure8: String?
    var strMeasure9: String?
    var strMeasure10: String?
    
    // --- here adjust to your needs, could also use a computed property
    func allIngredients() -> [Ingredient] 
      return [
        Ingredient(name: strIngredient1 ?? "", amount: strMeasure1 ?? ""),
        Ingredient(name: strIngredient2 ?? "", amount: strMeasure2 ?? ""),
        Ingredient(name: strIngredient3 ?? "", amount: strMeasure3 ?? ""),
        Ingredient(name: strIngredient4 ?? "", amount: strMeasure4 ?? ""),
        Ingredient(name: strIngredient5 ?? "", amount: strMeasure5 ?? ""),
        Ingredient(name: strIngredient6 ?? "", amount: strMeasure6 ?? ""),
        Ingredient(name: strIngredient7 ?? "", amount: strMeasure7 ?? ""),
        Ingredient(name: strIngredient8 ?? "", amount: strMeasure8 ?? ""),
        Ingredient(name: strIngredient9 ?? "", amount: strMeasure9 ?? ""),
        Ingredient(name: strIngredient10 ?? "", amount: strMeasure10 ?? "")
    ].filter!$0.name.isEmpty
  



struct Ingredient: Identifiable 
    let id = UUID()
    var name: String
    var amount: String

【讨论】:

非常感谢!我已经让它像我想要的那样工作了,分隔线没有出现,但在询问之前我会试着弄清楚:) 再次感谢! 很高兴它有帮助。试试这个分频器:Divider().frame(height: 2).background(Color.blue) 抱歉,尽量多学习。从您的解决方案中有“.filter!$0.name.isEmpty”这是否意味着它只是检查数组并删除“名称”为空的数组?谢谢! 是的,就这么简单。严格来说,只获取所有非空名称。【参考方案3】:

把你的结构改成

struct Drink: Codable, Identifiable 
    let id = UUID()
    let strDrink : String
    let strInstructions: String
    let strDrinkThumb: String?
    let strIngredients: [String] = []

您可以在任何地方循环使用 drink.strIngredients 数组

【讨论】:

以上是关于从结构数据 SwiftUI 创建一个数组的主要内容,如果未能解决你的问题,请参考以下文章

从嵌套结构 swift ui 创建列表

如何将字符串数组从视图模型传递到 swiftUI 中播放歌曲

检查 JSON 数组中是不是存在值,如果不存在则检查下一个数组(Swift / SwiftUI)

SwiftUI - 从嵌套数组中列出对象

如何从结构视图数组中提取状态? SwiftUI

从嵌套结构(带有其他结构的数组)创建字典 Swift