Reputation: 59
Basically, the first two functions (FetchOriginCoordinates
and FetchDestCoordinates
) take a street name as an input and return the coordinates for it. The coordinates are then used to find a nearby bus stop in the functions FetchOriginID
and FetchDestID
. These functions return the bus stop ID which is then used in the last function (FetchTrip
). In URL used in that functions has four modifiable parameters: originExtId, destExtId, date, and time. The originExtId and destExtId are the values fetched from FetchOriginID
and FetchDestID
. The date and time parameters correspond to the variables arrivalTime
and travelDate
.
I only call the FetchTrip
function when the Button
in the body
is pressed. The first time the function is called the variables: self.originLat
, self.originLon
, self.destLat
, self.destLon
, self.originID
and self.destID
are all nil. The second time self.originID
and self.destID
are nil. And the third time the function works (returns the correct value).
I'm new to swift so I'm not entirely sure why this is caused. But I do believe it's because the nested function continues running without waiting for the other functions to finish. I, therefore, think it has something to do with Closures, but I'm not fully sure how to apply it. Could someone help me?
@State var arrivalTime = String()
@State var travelDate = String()
@State var originInput = String()
@State var destInput = String()
@State var originLat = String()
@State var originLon = String()
@State var destLat = String()
@State var destLon = String()
@State var originID = String()
@State var destID = String()
@State var destName = String()
@State var destTime = String()
@State var originTime = String()
@State var originName = String()
@State var Trips = [String]()
@State var tripIndex = Int()
var body: some View {
VStack {
List(Trips, id: \.self) { string in
Text(string)
}
TextField("From", text: $originInput).padding()
TextField("To", text: $destInput).padding()
TextField("00:00", text: $arrivalTime).padding()
TextField("yyyy-mm-dd", text: $travelDate).padding()
Button("FetchAPI"){FetchTrip()}.padding()
}
}
func FetchOriginCoordinates(completion: @escaping ([NominationStructure]) -> ()) {
let locationUrl = URL(string: "https://nominatim.openstreetmap.org/search?country=Sweden&city=Stockholm&street=\(self.originInput)&format=json")
print(locationUrl)
URLSession.shared.dataTask(with: locationUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode([NominationStructure].self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
func FetchDestCoordinates(completion: @escaping ([NominationStructure]) -> ()) {
// Same as the function above but using self.destInput instead of self.originInput
}
func FetchOriginID(completion: @escaping (NearbyStopsStructure) -> ()) {
DispatchQueue.main.async {
FetchOriginCoordinates { (cors) in
self.originLat = cors[0].lat
self.originLon = cors[0].lon
}
}
let nearbyStopsKey = "MY API KEY"
let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.originLat)&originCoordLong=\(self.originLon)&maxNo=1")
print(stopIDUrl)
URLSession.shared.dataTask(with: stopIDUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode(NearbyStopsStructure.self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
func FetchDestID(completion: @escaping (NearbyStopsStructure) -> ()) {
// Same as the function above but using self.destLat and self.destLon instead of self.originLat and self.originLon
}
func FetchTrip() {
DispatchQueue.main.async {
FetchOriginID { (stops) in
self.originID = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
}
FetchDestID { (stops) in
self.destID = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
}
}
let tripKey = "MY API KEY"
let tripUrl = URL(string: "http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=\(tripKey)&originExtId=\(self.originID)&destExtId=\(self.destID)&Date=\(self.travelDate)&Time=\(self.arrivalTime)&searchForArrival=1")
print(tripUrl)
// Logic here
}
Upvotes: 1
Views: 905
Reputation: 29676
One way to do it is via DispatchGroup
but since you have quite a few different steps it might be simpler if you just trigger the next step when you are done with what you are working on.
//Just to condense the information /make it reusable
struct LocationInfo {
var iD = String()
var input = String()
var lat = String()
var lon = String()
var name = String()
var time = String()
}
//One way is to call your functions when you know a step is completed
enum TripFetchStatus: String {
case start
case fetchedOriginCoordinates
case fetchedOriginId
case fetchedDestinationCoordinates
case fetchedDestinationId
case fetchingTrip
case done
case stopped
}
class TripViewModel: ObservableObject {
//Apple frowns upon frezzing a screen so having a way to update the user on what is going on
//When a step is complete have it do something else
@Published var fetchStatus: TripFetchStatus = .stopped{
didSet{
switch fetchStatus {
case .start:
FetchOriginCoordinates { (cors) in
self.origin.lat = cors[0].latitude.description
self.origin.lon = cors[0].longitude.description
self.fetchStatus = .fetchedOriginCoordinates
}
case .fetchedOriginCoordinates:
self.FetchOriginID { (stops) in
self.origin.iD = stops
self.fetchStatus = .fetchedOriginId
}
case .fetchedOriginId:
FetchDestCoordinates { (cors) in
self.dest.lat = cors[0].latitude.description
self.dest.lon = cors[0].longitude.description
self.fetchStatus = .fetchedDestinationCoordinates
}
case .fetchedDestinationCoordinates:
self.FetchDestID { (stops) in
self.dest.iD = stops
self.fetchStatus = .fetchedDestinationId
}
case .fetchedDestinationId:
//Once you have everthing in place then go to the next API
FetchTrip()
case .fetchingTrip:
print("almost done")
case .done:
print("any other code you need to do")
case .stopped:
print("just a filler use this for errors or other things that deserve a halt")
}
}
}
@Published var dest: LocationInfo = LocationInfo()
@Published var origin: LocationInfo = LocationInfo()
@Published var arrivalTime = String()
@Published var travelDate = String()
func FetchTrip() {
//Timers are to mimic waiting on a URLResponse
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) {_ in
self.fetchStatus = .fetchingTrip
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) {_ in
self.fetchStatus = .done
}
}
}
//Simple version just to replicate put your code within
private func FetchOriginCoordinates(completion: @escaping ([CLLocationCoordinate2D]) -> ()) {
//Timers are to mimic waiting on a URLResponse
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) {_ in
completion([CLLocationCoordinate2D()])
}
}
private func FetchDestCoordinates(completion: @escaping ([CLLocationCoordinate2D]) -> ()) {
//Timers are to mimic waiting on a URLResponse
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) {_ in
completion([CLLocationCoordinate2D()])
}
}
private func FetchOriginID(completion: @escaping (String) -> ()) {
//Timers are to mimic waiting on a URLResponse
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) {_ in
completion(UUID().uuidString)
}
}
private func FetchDestID(completion: @escaping (String) -> ()) {
//Timers are to mimic waiting on a URLResponse
Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) {_ in
completion(UUID().uuidString)
}
}
}
struct TripView: View {
//Code that does work should never live in a View you want to be able to reuse it
@StateObject var vm: TripViewModel = TripViewModel()
@State var Trips = [String]()
@State var tripIndex = Int()
var body: some View {
ZStack{
VStack {
List(Trips, id: \.self) { string in
Text(string)
}
TextField("From", text: $vm.origin.input).padding()
TextField("To", text: $vm.dest.input).padding()
TextField("00:00", text: $vm.arrivalTime).padding()
TextField("yyyy-mm-dd", text: $vm.travelDate).padding()
Button("FetchAPI"){
vm.fetchStatus = .start
}.padding()
}
.disabled(vm.fetchStatus != .stopped && vm.fetchStatus != .done)
if vm.fetchStatus != .stopped && vm.fetchStatus != .done{
VStack{
ProgressView()
//You dont have to show the user all this
Text(vm.fetchStatus.rawValue)
Text("lat = \(vm.origin.lat) lon = \(vm.origin.lon)")
Text("id = \(vm.origin.iD)")
Text("lat = \(vm.dest.lat) lon = \(vm.dest.lon)")
Text("id = \(vm.dest.iD)")
}
}
}
}
}
Upvotes: 2