【问题标题】:Creating an array from stuct data SwiftUI从结构数据 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

谢谢!

【问题讨论】:

标签: arrays swift xcode struct swiftui


【解决方案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 数组

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-03
    • 2022-08-17
    • 1970-01-01
    相关资源
    最近更新 更多