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

Creating an array from stuct data SwiftUI

首先,对于菜鸟问题​​,我感到非常抱歉,但我似乎无法弄清楚。

我是编码的新手,刚开始接触 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

谢谢!

将结构更改为

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

无论你想在何处遍历成分,都可以使用 drink.strIngredients 数组

您可以使用这种方法将所有成分放入数组中并使用 它在列表中。这个想法是使用一个函数将所有成分收集到一个数组中 Ingredient 个对象。您还可以使用计算 属性。 最好使用 Ingredient 对象并声明它 Identifiable 这样当你在 list 和 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{![=10=].name.isEmpty}
  }

}

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

首先,你的设计无法工作,因为你忽略了根对象,一个带有键的结构 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)
                    }
                }
            }
        }
    }
}