Blank menu with external json

Hello everyone,
I'm trying to create an iOS app with Swift for the first time and I've encountered an error that I can't solve. I've searched and googled a lot but somehow I can't find anything.

So, the app displays city names and when you click on a city, it should then display categories such as restaurants or doctors. The addresses should come from a json file that is on my hosting. As soon as I write the code with the json file, the categories disappear, there is no error message from xcode. Do you have any idea what else I could try. This is the code, and the json file.

swift code:

timport SwiftUI

struct CitySelectionView: View {
    let cities = ["Hamburg", "Berlin", "Köln", "München", "Frankfurt", "Stuttgart"]
    
    var body: some View {
        NavigationView {
            List(cities, id: \.self) { city in
                NavigationLink(destination: CategorySelectionView(city: city)) {
                    Text(city)
                }
            }
            .navigationTitle("Städte")
        }
    }
}

struct CategorySelectionView: View {
    let categories = ["Restaurants", "Bars", "Ärzte", "Supermärkte", "Freizeit"]
    let city: String
    
    @State private var restaurants: [Restaurant] = []
    
    var body: some View {
        VStack {
            Text("Wähle eine Kategorie für \(city):")
                .font(.title)
                .padding()
            
            List(restaurants, id: \.name) { restaurant in
                VStack(alignment: .leading) {
                    Text(restaurant.name)
                        .font(.headline)
                    Text(restaurant.address)
                        .font(.subheadline)
                }
            }
            .onAppear {
                if city == "Hamburg" {
                    downloadRestaurantData()
                }
            }
        }
        .navigationTitle("\(city)")
    }
    
    func downloadRestaurantData() {
        guard let url = URL(string: "http://www.digi-nova.de/xmlapp/hamburg/restaurants/restaurantshamburg.json") else {
            print("Invalid URL")
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                print("Error: \(error?.localizedDescription ?? "Unknown error")")
                return
            }
            
            do {
                let decodedData = try JSONDecoder().decode([Restaurant].self, from: data)
                DispatchQueue.main.async {
                    self.restaurants = decodedData
                }
            } catch {
                print("Failed to decode JSON data: \(error.localizedDescription)")
            }
        }.resume()
    }
}

struct Restaurant: Codable, Identifiable {
    var id = UUID()
    let name: String
    let address: String
    let zip: String
    let city: String
    let phone: String
    let opening_hours: String
    let map_link: String
}

struct ContentView: View {
    var body: some View {
        CitySelectionView()
    }
}

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

in the code is the link to the json file

Are you getting the data back at all or is there a "The resource could not be loaded because the App Transport Security policy requires the use of a secure connection." message in the console? Note that by default non-SSL'ed traffic is disabled, ideally you use https and if that's not good for you then you tweak the relevant keys in the app's plist (NSAppTransportSecurity) to allow either arbitrary http requests or requests to particular sites only.

Other than that I see two mistakes. The JSON file has this:

{
   "restaurants":[
      {
         "id":1,
         "name":"Restaurant A",

so at the top level there's a dictionary and the "restaurants" field of that dictionary points to an array. The fix is easy:

struct Reply: Codable {
    let restaurants: [Restaurant]
}
...
let decodedData = try JSONDecoder().decode(Reply.self, from: data)
// was `[Restaurant].self`

Another error is using UUID() for the ID fields, which are Ints according to JSON:

struct Restaurant: Codable, Identifiable {
    let id: Int // was UUID

To get more insight about what error is I'd do this:

    print(error)
    or
    print((error as NSError).debugDescription)

as "error.localizedDescription" doesn't tell the whole story.

1 Like

Thank you for your help. I uploaded the json file to github to get an https link and made the customizations as you described. However, I still don't get any categories when I click on a city, the page just stays blank.

The code is now changed to:

import SwiftUI

struct CitySelectionView: View {
    let cities = ["Hamburg", "Berlin", "Köln", "München", "Frankfurt", "Stuttgart", "Hannover"]
    
    var body: some View {
        NavigationView {
            List(cities, id: \.self) { city in
                NavigationLink(destination: CategorySelectionView(city: city)) {
                    Text(city)
                }
            }
            .navigationTitle("Städte")
        }
    }
}

struct CategorySelectionView: View {
    let categories = ["Restaurants", "Bars", "Ärzte", "Supermärkte", "Freizeit"]
    let city: String
    
    @State private var restaurants: [Restaurant] = []
    
    var body: some View {
        VStack {
            Text("Wähle eine Kategorie für \(city):")
                .font(.title)
                .padding()
            
            List(restaurants, id: \.name) { restaurant in
                VStack(alignment: .leading) {
                    Text(restaurant.name)
                        .font(.headline)
                    Text(restaurant.address)
                        .font(.subheadline)
                }
            }
            .onAppear {
                if city == "Hamburg" {
                    downloadRestaurantData()
                }
            }
        }
        .navigationTitle("\(city)")
    }
    
    func downloadRestaurantData() {
        guard let url = URL(string: "https://github.com/donnievedro/balkangermany/blob/main/restaurantshamburg.json") else {
            print("Invalid URL")
            return
        }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil else {
                print("Error: \(error?.localizedDescription ?? "Unknown error")")
                return
            }
            
            do {
                let decodedData = try JSONDecoder().decode(Reply.self, from: data)
                DispatchQueue.main.async {
                    self.restaurants = decodedData.restaurants
                }
            } catch {
                print("Failed to decode JSON data: \(error.localizedDescription)")
            }
        }.resume()
    }
}

struct Restaurant: Codable, Identifiable {
    let id: Int // Changed from UUID
    let name: String
    let address: String
    let zip: String
    let city: String
    let phone: String
    let opening_hours: String
    let map_link: String
}

struct Reply: Codable {
    let restaurants: [Restaurant]
}

struct ContentView: View {
    var body: some View {
        CitySelectionView()
    }
}

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

The new version of the code works fine for me with your old URL:

http://www.digi-nova.de/xmlapp/hamburg/restaurants/restaurantshamburg.json

(provided I tweak the ATS settings to allow http).

I can't check your new URL as it points to a file on a private repo I have no access to.

I'd recommend to print(error) to get further insights, and also:

    print(String(data: data, encoding: .utf8))

to see what data is actually getting returned.