0

I am currently building a public transportation app in SwiftUI Xcode 12 beta, where you input origin, destination, time of arrival, and date. The idea is to then convert the origin and destination input (private func FetchOriginCoordinates and private func FetchDestCoordinates) into coordinates using an API. This API will then give that data to another API that plans the route.

The problem is that the route planner API doesn't give any output. I have tried printing self.Trips.append(...) but the list is completely empty. I believe this is because the code doesn't reach that part of the code. I have put some print statements in the code in order to understand where the error is occurring. When I call the API my output in the console is this:

Test 2021-04-16 22:33:19.271246+0200 OnTrack2.0[6592:428359] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed Optional(http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=40892db48b394d3a86b2439f9f3800fd&originExtId=300101115&destExtId=300101426&Date=2021-04-18&Time=09:20&searchForArrival=1) [] 10767 bytes Level 1 Level 1.5

This means that the code only reaches the print("Level 1.5") statement. But never the following print statements such as print("Level 2") or print(self.trips). Why is that and how do I solve it?

import Foundation
 
struct NominationStructure: Decodable {
    var lat: String
    var lon: String
    
    enum CodingKeys: String, CodingKey {
        case lat = "lat"
        case lon = "lon"
    }
}
 
 
struct NearbyStopsStructure: Decodable {
    var stopLocationOrCoordLocation: [stopLocationOrCoordLocationStructure]
}
 
struct stopLocationOrCoordLocationStructure: Decodable {
    var StopLocation: StopLocationStructure
}
 
struct StopLocationStructure: Decodable {
    var mainMastExtId: String
}
 
 
 
struct JSONStructure: Decodable {
    var Trip: [TripStructure]
}
 
struct TripStructure: Decodable {
    var LegList: LegListStructure
}
 
struct LegListStructure: Decodable {
    var Leg: [LegStructure]
}
 
struct LegStructure: Decodable {
    var Origin: StationStructure
    var Destination: StationStructure
    var Product: ProductStructure
    var name: String
    var type: String
    var dist: String
}
 
struct StationStructure: Decodable {
    var time: String
    var name: String
    var date: String
}
 
struct ProductStructure: Decodable {
    var catIn: String
}
 
 
struct LocationInfo {
    var iD = String()
    var input = String()
    var lat = String()
    var lon = String()
    var name = String()
    var time = String()
    var date = String()
    var vehicleType = String()
    var transportType = String()
    var dist = String()
    var legName = 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 PlanTripViewModel: 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:
                print("Test")
                FetchOriginCoordinates { (cors) in
                    self.origin.lat = cors[0].lat
                    self.origin.lon = cors[0].lon
                    self.fetchStatus = .fetchedOriginCoordinates
                }
            case .fetchedOriginCoordinates:
                self.FetchOriginID { (stops) in
                    self.origin.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
                    self.fetchStatus = .fetchedOriginId
                }
            case .fetchedOriginId:
                FetchDestCoordinates { (cors) in
                    self.dest.lat = cors[0].lat
                    self.dest.lon = cors[0].lon
                    self.fetchStatus = .fetchedDestinationCoordinates
                }
            case .fetchedDestinationCoordinates:
                self.FetchDestID { (stops) in
                    self.dest.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
                    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")
                
            }
        }
    }
    
    @Published var trip: LocationInfo = LocationInfo()
    @Published var dest: LocationInfo = LocationInfo()
    @Published var origin: LocationInfo = LocationInfo()
    @Published var arrivalTime = String()
    @Published var travelDate = String()
    @Published var tripIndex = Int()
    @Published var Trips: [Dictionary<String, String>] = []
 
    
    
    public func FetchTrip() {
        Trips.removeAll()
        
        let tripKey = "40892db48b394d3a86b2439f9f3800fd"
        let tripUrl = URL(string: "http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=\(tripKey)&originExtId=\(self.origin.iD)&destExtId=\(self.dest.iD)&Date=\(self.travelDate)&Time=\(self.arrivalTime)&searchForArrival=1")
        print(tripUrl)
        
        URLSession.shared.dataTask(with: tripUrl!) {data, response, error in
            if let data = data {
                print(data)
                print("Level 1")
                do {
                    print("Level 1.5")

                    if let decodedJson = try? JSONDecoder().decode(JSONStructure.self, from: data) {
                        self.tripIndex = decodedJson.Trip.count - 1
                        print("Level 2")
                        
                        for i in 0..<decodedJson.Trip[self.tripIndex].LegList.Leg.count {
                            
                            self.trip.transportType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].type
 
                            if self.trip.transportType == "WALK" {
                                
                                self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
                                self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
                                self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
 
                                self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
                                self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
                                self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
                                
                                self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
                                self.trip.dist = decodedJson.Trip[self.tripIndex].LegList.Leg[i].dist
                                
                                print(self.origin.name)
                                print(self.dest.name)

                                self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "Distance": self.trip.dist])
                            }
                            
                            else {
                                self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
                                self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
                                self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
 
                                self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
                                self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
                                self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
                                
                                self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
                                self.trip.legName = decodedJson.Trip[self.tripIndex].LegList.Leg[i].name
                                
                                print(self.origin.name)
                                print(self.dest.name)
                                
                                self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "LegName": self.trip.legName])
                            }
                        }
                    }
                } catch {
                    print(error)
                }
            }
        }.resume()
        print(self.Trips)
    }
    
    //Simple version just to replicate put your code within
    private func FetchOriginCoordinates(completion: @escaping ([NominationStructure]) -> ()) {
        let scheme = "https"
        let host = "nominatim.openstreetmap.org"
        let path = "/search"
        let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
        let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
        let queryItemStreet = URLQueryItem(name: "street", value: self.origin.input)
        let queryItemFormat = URLQueryItem(name: "format", value: "json")
        
        var urlComponents = URLComponents()
        urlComponents.scheme = scheme
        urlComponents.host = host
        urlComponents.path = path
        urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
        
        URLSession.shared.dataTask(with: urlComponents.url!) {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()
    }
    
    
    private func FetchDestCoordinates(completion: @escaping ([NominationStructure]) -> ()) {
        let scheme = "https"
        let host = "nominatim.openstreetmap.org"
        let path = "/search"
        let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
        let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
        let queryItemStreet = URLQueryItem(name: "street", value: self.dest.input)
        let queryItemFormat = URLQueryItem(name: "format", value: "json")
        
        var urlComponents = URLComponents()
        urlComponents.scheme = scheme
        urlComponents.host = host
        urlComponents.path = path
        urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
        
        URLSession.shared.dataTask(with: urlComponents.url!) {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()
    }
    
    private func FetchOriginID(completion: @escaping (NearbyStopsStructure) -> ()) {
        let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
        let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.origin.lat)&originCoordLong=\(self.origin.lon)&maxNo=1")
        
        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()
    }
    
    private func FetchDestID(completion: @escaping (NearbyStopsStructure) -> ()) {
        let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
        let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.dest.lat)&originCoordLong=\(self.dest.lon)&maxNo=1")
        
        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()
    }
}

  • `dataTask` is an *asynchronous* function -- code placed right after its completion handler will not execute right away. Code that depends on its output should go *inside* it's `{ }` closure. Instead of printing after the closure, try printing inside of it. Or, use `didSet` on `self.Trips`. Unrelated, but in Swift, variable names are lowercased by convention. See https://stackoverflow.com/q/67111796/560942 – jnpdx Apr 16 '21 at 21:13
  • I moved up the `print(self.Trips)` statement right under the `self.Trips.append(...)` function but no difference was made. The `print("Level 2")` was not reached either but it was still inside the `{ }` closure. Do you know why? – Elliot Sylvén Apr 16 '21 at 21:41
  • 1
    That looks like it would mean that your JSON decode is failing. You're using `try?` and not `do/try/catch`, so you're missing out on seeing the error if it fails. – jnpdx Apr 16 '21 at 21:43
  • I don't get any errors and as you say it may be something wrong with the fetching of the API, but I have written `do` `try?`and `catch`, but maybe not in the right way? On the line where I have my `catch` block where it says `print(error)` I receive a warning that says: `'catch' block is unreachable because no errors are thrown in 'do' block`, why? – Elliot Sylvén Apr 17 '21 at 20:03
  • If you are with a `do { }` block, you shouldn't be using a `?` after `try`. Read the "Handling Errors Using Do-Catch" section of this: https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html – jnpdx Apr 17 '21 at 20:25

0 Answers0