从结构数据 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 的设计为strIngredient1
、strIngredient2
等不是一个好主意。您应该将所有这些成分都放在一个数组中,因为它可以是任意长度。
成分可能是 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 创建一个数组的主要内容,如果未能解决你的问题,请参考以下文章
如何将字符串数组从视图模型传递到 swiftUI 中播放歌曲