I'm making an app where I need to parse data from two APIs (
Each launch has an ID that matches the ID of one of the four rockets in the first API. In my application there are four tabs with information about each rocket and a button that leads to another View where information about all the launches of this rocket (name, date and whether the launch was successful) should be displayed.
- I do not know how to update my model so that I can parse information from two APIs at once.
- I also can't figure out how I can display information about all launches of a particular rocket on a separate View, having first checked that their IDs match to particular rocket ID from the first API.
CodePudding user response:
EDIT-1:
The best thing to do, is to restructure your code and use a
ObservableObject class
to do all fetching, processing and publishing.
Here is some example code using an ObservableObject
class.
struct ContentView: View {
@StateObject var spacex = SpacexModel() // <-- here
var body: some View {
NavigationView {
if spacex.loadingRockets || spacex.loadingLaunches { // <-- here
ProgressView()
} else {
TabView {
ForEach(spacex.rockets) { rocket in // <-- here
ScrollView(.vertical, showsIndicators: false) {
VStack {
//MARK: - HEADER IMAGE
Image(systemName: "globe") // <-- for testing
.renderingMode(.original)
.resizable()
.scaledToFill()
.frame(width: 190, height: 190, alignment: .center)
.padding(.bottom, 32)
//MARK: - INFO
VStack(spacing: 40) {
HStack {
Text(rocket.name).font(.title)
Spacer()
}
HStack {
Text("First flight")
Spacer()
Text(rocket.first_flight)
}
HStack {
Text("Country")
Spacer()
Text(rocket.country)
}
HStack {
Text("Cost per launch")
Spacer()
Text("$\(rocket.cost_per_launch / 1000000)M")
}
} //: VSTACK
.padding(.horizontal, 32)
//MARK: - LAUNCHES BUTTON
NavigationLink {
LaunchDetailView(rocket: rocket) // <-- here
} label: {
Text("Launches".uppercased())
.font(.headline)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 56, maxHeight: 56, alignment: .center)
.background(
Color(UIColor.secondarySystemFill)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
)
.foregroundColor(Color.green)
.padding(32)
}
} //: VSTACK
} //: SCROLL
} //: LOOP
} //: TAB
.tabViewStyle(.page)
.navigationBarTitleDisplayMode(.inline)
.navigationBarHidden(true)
.edgesIgnoringSafeArea(.vertical)
}
} //: NAVIGATION
.navigationViewStyle(.stack)
.environmentObject(spacex) // <-- here
.edgesIgnoringSafeArea(.vertical)
}
}
struct LaunchDetailView: View {
@EnvironmentObject var spacex: SpacexModel // <-- here
let rocket: RocketInfo // <-- here
var body: some View {
VStack {
Text("launches for \(rocket.name)").foregroundColor(.blue)
List {
ForEach(spacex.launchesFor(rocket)) { launch in
VStack {
Text(launch.name).foregroundColor(.green)
Text("launch \(launch.details ?? "no details")")
}
}
}
}
}
}
class SpacexModel: ObservableObject {
@Published var rockets = [RocketInfo]()
@Published var launches = [LaunchInfo]()
@Published var loadingRockets = false
@Published var loadingLaunches = false
init() {
getRockets()
getLaunches()
}
func getRockets() {
self.loadingRockets = true
guard let url = URL(string: "https://api.spacexdata.com/v4/rockets") else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
// todo deal with errors
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
do {
self.rockets = try JSONDecoder().decode([RocketInfo].self, from: data)
self.loadingRockets = false
} catch {
print(error)
}
}
}.resume()
}
func getLaunches() {
self.loadingLaunches = true
guard let url = URL(string: "https://api.spacexdata.com/v4/launches") else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
// todo deal with errors
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
do {
self.launches = try JSONDecoder().decode([LaunchInfo].self, from: data)
self.loadingLaunches = false
} catch {
print(error)
}
}
}.resume()
}
func launchesFor(_ rocket: RocketInfo) -> [LaunchInfo] {
return launches.filter{ $0.rocket == rocket.id }
}
}
struct RocketInfo: Codable, Identifiable {
let id: String // <-- here
let name: String
let country: String
let first_flight: String
let cost_per_launch: Int
// ...
}
struct LaunchInfo: Identifiable, Codable {
let id: String
let rocket: String // <-- here
let details: String?
let crew, ships, capsules, payloads: [String]
let name: String
// ...
}