0

This is my Model:

class Api {
    func getRockets(completion: @escaping ([Rocket]) -> ()) {
        guard let url = URL(string: "https://api.spacexdata.com/v4/rockets") else { return }
        
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            do {
                let rockets = try JSONDecoder().decode([Rocket].self, from: data!)
                
                DispatchQueue.main.async {
                    completion(rockets)
                }
            } catch {
                print(error.localizedDescription)
            }
        }
        .resume()
    }
}

I try to make PageTabView using elements from the API array, but my app crashes with an out of range index error.

This is the View that doesn't work properly:

struct TestTabViewView: View {
    @State var rockets: [Rocket] = [] //<-- The array of items from the API I use to make tabs
    
    var body: some View {
        TabView {
            ForEach(rockets) { rocket in
                Text(rocket.name)
            }
        }
        .onAppear {
            Api().getRockets { rockets in
                self.rockets = rockets
            }
        }
        .tabViewStyle(.page)
    }
}

struct TestTabViewView_Previews: PreviewProvider {
    static var previews: some View {
        TestTabViewView()
    }
}
bowtie
  • 29
  • 6
  • I'd bet that `TabView` requires you to have at least 1 view, which you won't until the API returns. What happens if you include a placeholder view instead of the `TabView` until `rockets` has elements? – jnpdx Apr 06 '22 at 23:50
  • @jnpdx, it sounds like a great solution, but unfortunately I don't know how to do it in this particular case. I know that it's possible to add something like 'static let example = Rocket(name: "Falcon 1")' to my Model, then add 'var rocket: Rocket' at the top of the struct of the View, and then add missing argument to the Preview of that View. I've tried but it didn't work. – bowtie Apr 07 '22 at 01:30
  • 1
    Add an `if rockets.isEmpty` clause around the entire TabView and display something else if the array is empty (like a loading indicator) – jnpdx Apr 07 '22 at 01:40
  • Thank you, @jnpdx, it helped, but there was another problem I described in [this question](https://stackoverflow.com/questions/71802451/how-to-delete-navbar-in-navigationview-at-all-in-swiftui) – bowtie Apr 08 '22 at 19:49

1 Answers1

0

If you put TabView inside an if-else statement, and in turn put this statement inside NavigationView or Group, and then call the .onAppear method for an external container (for example, Group), everything will work properly and there will be no an out of range index error.

struct TestTabViewView: View {
    @State var rockets: [Rocket] = []
    
    var body: some View {
        Group {
            if rockets.isEmpty {
                ProgressView()
            } else {
                TabView {
                    ForEach(rockets) { rocket in
                        Text(rocket.name)
                    }
                }
                .tabViewStyle(.page)
            }
        }
        .onAppear {
            Api().getRockets { rockets in
                self.rockets = rockets
            }
        }
    }
}
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
bowtie
  • 29
  • 6