SwiftUI Foreach in a JSON file

Hi everyone,


sorry for this post, I already posted it but I can't find it anymore, I don't know why...

Anyway, here it is again.


I followed the tutorial from apple to create the Landmark app. I'm trying to go a little bit further.

This is a capture of what I would like to do:

Frame 3 will be a detailled vue of each beer. So it's a kind of menu in a menu.


This is the files I've got:


beerData.json

[
  {
        "id": 1,
  "name": "Pilot",
        "category": "Scottish beer",
        "imageName": "Pilot",
        "isSelected": true,
  "beers": [
  {
                "name": "Mochaccino Stout",
                "style": "Milk stout",
                "abv": 5.5,
                "id": 11,
                "isFavorite": true,
                "imageName": "Mochaccino_Stout",
                "description": "Winner of the Bow Bar’s 2014 Dark Beer Challenge, Mochaccino Stout is a huge-bodied, full-flavoured milk stout, flavoured with coffee roast to our exact specifications, raw and roast organic cocoa nibs and Tahitian vanilla. A rich, comforting pint of luxury."
  },
  {
                "name": "Blønd",
                "style": "Oatmeal pale",
                "abv": 4.0,
                "id": 12,
                "isFavorite": false,
                "imageName": "Blond",
                "description": "Cloudy like a wheat beer, Blønd is a (relatively) low ABV session pint that packs in all the body, bitterness and tropical fruit flavour of a much bigger beer."
  },
            {
                "name": "Vienna Pale",
                "style": "Vienna lager",
                "abv": 4.6,
                "id": 13,
                "isFavorite": false,
                "imageName": "Vienna_Pale",
                "description": "Based on the classic Vienna Lager style (though technically an ale), and annoyer of a certain type of beer geek, Vienna Pale is a sweet, malty drinking pint, with plenty of Saaz, Citra and Cascade dry-hopping to keep things interesting."
            }
  ]
  },
    {
        "id": 2,
        "name": "Fierce",
        "category": "Scottish beer",
        "imageName": "Fierce",
        "isSelected": false,
        "beers": [
            {
                "name": "Late Shift",
                "style": "IPA",
                "abv": 6.5,
                "id": 21,
                "isFavorite": false,
                "imageName": "Late_Shift",
                "description": "Late Shift is a crushable New England style IPA with low bitterness, a full on juicy profile and a lot of haze. We use Azacca and Citra hops for a tropical and citrus hop explosion."
            }
        ]
    },
  {
  "id": 3,
        "name": "Parisis",
        "category":"French beer",
        "imageName":"Parisis",
        "isSelected": false,
        "beers": [
            {
                "name": "Blonde",
                "style": "Pale ale",
                "abv": 6.5,
                "id": 21,
                "isFavorite": false,
                "imageName": "Blonde",
                "description": "Bière blonde fruitée, aux notes houblonnées délicates. Un houblonnage a cru avec variétés spécifiques lui confèrent des arômes d'agrumes et de fruit tropicaux."
            }
        ]
  }
]


I load this file like this in a specific file with the decode method (same as the one in the Landmark tutorial)

let beerData: [Beer] = load("beerData.json")


So I manage to buid the Frame 1 like this:

import SwiftUI

struct ContentView: View {
   
    var body: some View {
        NavigationView {
            List {
                ForEach(breweryData) { brewery in
                    Text(brewery.name)
                   
                }
            }
        .navigationBarTitle(Text("Brewery"))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


But I'm struggling to build the seconde frame.

My problem is how can I do the foreach only on beers from a specific brewery?


I hope I'm clear enough.

I stay available if you need more informations.


Thanks for your help.

Please show how your `Beer` is defined.

Oh yes sorry I forget to show it.. So sorry.


Here it is:

beer.swift

import SwiftUI

struct Brewery: Hashable, Codable, Identifiable {
    var id: Int
    var name: String
    fileprivate var imageName: String
    var beers: [Beer]
    var category: Category
  
    enum Category: String, CaseIterable, Codable, Hashable {
        case scottish = "Scottish beer"
        case french = "French beer"
    }
}

extension Brewery {
    var image: Image {
        ImageStore.shared.image(name: imageName)
    }
}

struct Beer: Hashable, Codable, Equatable, Identifiable {
    var id: Int
    var name: String
    fileprivate var imageName: String
    var style: String
    var abv: Float
    var isFavorite: Bool
    var description: String
}

extension Beer {
    var image: Image {
        ImageStore.shared.image(name: imageName)
    }
}


Thanks

Ok, then I assume you have

let breweryData: [Brewery] = load("beerData.json")

instead of `beerData`.


So, your initial view `ContentView` shows list of breweries, and you want to show list of beers of the breweries in your second view, right?

You can declare your second view like this:

import SwiftUI

struct BeerListView: View {
    var brewery: Brewery
    
    var body: some View {
        List {
            ForEach(brewery.beers) { beer in
                Text(beer.name)
            }
        }
        .navigationBarTitle(Text("Beers of \(brewery.name)"))
    }
}

struct BeerListView_Previews: PreviewProvider {
    static var previews: some View {
        BeerListView(brewery: breweryData[0])
    }
}

To transit from ContentView to this BeerListView, you need to pass the chosen brewery.

You need to modify your ContentView as follows:

struct ContentView: View {
     
    var body: some View {
        NavigationView {
            List {
                ForEach(breweryData) { brewery in
                    NavigationLink(destination: BeerListView(brewery: brewery)) {
                        Text(brewery.name)
                    }
                }
            }
            .navigationBarTitle(Text("Brewery"))
        }
    }
}


To go Frame 3: a detailled view of each beer, you need to create BeerView which takes `beer` and add a NavigationLink into BeerListView.

yes it was breweryData instead of beerData sorry.


Thank you soooo much OOPer !!!

It work like a charm 😉


Cheers

If I may ask a last question on this topic:


I load the image like this:

static func loadImage(name: String) -> CGImage {
        guard
            let url = Bundle.main.url(forResource: name, withExtension: "jpg"),
            let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
            let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
        else {
            fatalError("Couldn't load image \(name).jpg from main bundle.")
        }
        return image
    }


May I add the "png" format to the extension? Is it possible?


Sorry for that.


Thanks again

Please try this:

    static func loadImage(name: String) -> CGImage {
        guard
            let url = Bundle.main.url(forResource: name, withExtension: "jpg") //<- No comma here
                ?? Bundle.main.url(forResource: name, withExtension: "png"),
            let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
            let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
            else {
                fatalError("Couldn't load image \(name).jpg/png from main bundle.")
        }
        return image
    }

I'm sorry OOPer, but as you're really helpful, I ask you a lot of question..


I build the detailled vue like this:

import SwiftUI

struct BeerDetail: View {
    var beer: Beer
   
    var body: some View {
        VStack(alignment: .leading) {
            BgImage(bgimage: beer.image)
                .edgesIgnoringSafeArea(.top)
                .frame(height: 300)
           
            BeerImage(image: beer.bgimage)
                .offset(y: -150)
                .padding(.bottom, -150)
           
            VStack(alignment: .leading) {
                Text(beer.name)
                    .font(.title)
                HStack(alignment: .top) {
                    Text(beer.name)
                        .font(.subheadline)
                    Spacer()
                    Text("\(beer.abv)%")
                        .font(.subheadline)
                }
            }
            .padding()
           
            Spacer()
        }
    }
}

struct BeerDetail_Previews: PreviewProvider {
    static var previews: some View {
        BeerDetail(beer: breweryData[0])
    }
}


I've got an error on the preview with the line 36. What I should put?

And on my BeerList, what should I put?

NavigationLink(destination: BeerDetail(beer: beer))



I'm sorry probably my question is really stupid.. I'm just a beginer as you can imagine...


Thanks a lot again for your help

Think. Do not just copy the shown code.

`breweryData` is an Array of Brewery, so, `breweryData[0]` is a Brewery.


You need to pass a Beer to `BeerDetail.init(beer:)`.

Each Brewery has a property named `beers`, which is an Array of Beer.


struct BeerDetail_Previews: PreviewProvider {
    static var previews: some View {
        BeerDetail(beer: breweryData[0].beers[0])
    }
}


And on my BeerList, what should I put?

Think and try. Where and how I have modified your ContentView?


I do not have time to help people who do not think.

Thanks for your answer.


You could just be nicer...

You should better start a new thread for another issue.


And I believe nice does not mean do everything for someone.

I never asked someone to do everything for me. I'm asking something when I'm stuck, know it's your choice to help or not.

Just don't forget that what is obvious for you is probably not for someone else...

I completly see your point here:

"Think. Do not just copy the shown code."

and here

"Think and try."


but that kind of sentence :

"I do not have time to help people who do not think."

is unnecessary...


Anyway thanks for your help

Hope you can show what you thought better next time.

SwiftUI Foreach in a JSON file
 
 
Q