Home > front end >  How do I display an array from this local JSON file as a SwiftUI list in a view?
How do I display an array from this local JSON file as a SwiftUI list in a view?

Time:11-18

For example, how do I display the descriptors for each record in a list, such as:

  • NUTTY, FRUITY
  • FATTY
  • FRUITY

(Note that I made descriptors optional in my model because the array might be empty)

Here is my JSON file code from file named flavors.json:

[
    {
        "id": "U45773",
        "flavorGroup": "CASHEW",
        "name": "NATURAL CASHEW FLAVORING",
        "isBeer": true,
        "isSeltzer": true,
        "isNatural": true,
        "descriptors": ["NUTTY", "FRUITY"],
        "keywords": ["aromatic", "fattt-buttery", "brown", "nutty", "roasted", "creamy"]
    },
    {
        "id": "U63639",
        "flavorGroup": "BLACK WALNUT",
        "name": "NATURAL AND ARTIFICIAL WALNUT FLAVOR",
        "isBeer": true,
        "isSeltzer": false,
        "isNatural": true,
        "descriptors": ["FATTY"],
        "keywords": ["sweet", "molasses", "woody", "slight dried fruit (amber ale)"]
    },
    {
        "id": "562811",
        "flavorGroup": "APRICOT",
        "name": "NATURAL AND ARTIFICIAL APRICOT FLAVOR",
        "isBeer": true,
        "isSeltzer": false,
        "isNatural": true,
        "descriptors": ["FRUITY"],
        "keywords": ["juicy", "skunky", "peach", "floral", "slight green (sierra nevada pale ale)"]
    }
]

Here is my model code:

struct Flavor: Codable, Identifiable {
    enum CodingKeys: CodingKey {
        case id
        case flavorGroup
        case name
        case isBeer
        case isSeltzer
        case isNatural
        case descriptors
        case keywords
    }
    let id, flavorGroup, name: String
    let isBeer, isSeltzer, isNatural: Bool
    let descriptors, keywords: [String]?
}

Here is my view model code:

class ReadData: ObservableObject  {
    @Published var flavors = [Flavor]()
    init(){
        loadData()
    }
    func loadData()  {
        guard let url = Bundle.main.url(forResource: "flavors", withExtension: "json")
            else {
                print("Json file not found")
                return
            }
        let data = try? Data(contentsOf: url)
        let flavors = try? JSONDecoder().decode([Flavor].self, from: data!)
        self.flavors = flavors!   
    }    
}

This is my best attempt at the view code:

struct DescriptorListView: View {
    @ObservedObject var datas = ReadData()
    var body: some View {
        List(datas.flavors) { item in
            ForEach(item.descriptors, id: \.self) { descriptor in
                Text("- \(descriptor)")
            }
        }
    }
}

It produces these compiler errors that I do not understand how to fix:

Value of optional type '[String]?' must be unwrapped to a value of type '[String]'

Coalesce using '??' to provide a default when the optional value contains 'nil'

Force-unwrap using '!' to abort execution if the optional value contains 'nil'

CodePudding user response:

You were very close with your attempt. The major issue is that descriptors is an Optional. That means that you have to somehow unwrap that optional value -- I've used if let, which is a technique called "optional binding".

The other issue is that your current code would list each descriptor on a different line. I've joined the descriptors together using joined instead and presented them on one line.

struct DescriptorListView: View {
    @ObservedObject var datas = ReadData()
    var body: some View {
        List(datas.flavors) { item in
            HStack {
                if let descriptors = item.descriptors {
                    Text(descriptors.joined(separator: ", "))
                } else {
                    Text("(no data)")
                }
            }
        }
    }
}
  • Related