По сути, первые две функции (FetchOriginCoordinates и FetchDestCoordinates) принимают название улицы в качестве входных данных и возвращают для него координаты. Затем координаты используются для поиска ближайшей автобусной остановки в функциях FetchOriginID и FetchDestID. Эти функции возвращают идентификатор автобусной остановки, который затем используется в последней функции (FetchTrip). В URL, используемом в этой функции, есть четыре изменяемых параметра: originExtId , destExtId , date и time . originExtId и destExtId - это значения, полученные из FetchOriginID и FetchDestID. Параметры дата и время соответствуют переменным arrivalTime и travelDate.

Я вызываю функцию FetchTrip только при нажатии Button в body. При первом вызове функции переменные: self.originLat, self.originLon, self.destLat, self.destLon, self.originID и self.destID все ноль . Во второй раз self.originID и self.destID равны ноль . И в третий раз функция работает (возвращает правильное значение).

Я новичок в Swift, поэтому не совсем уверен, почему это вызвано. Но я верю, что это потому, что вложенная функция продолжает работать, не дожидаясь завершения других функций. Поэтому я думаю, что это как-то связано с Closures , но я не совсем уверен, как это применить. Кто-нибудь может мне помочь?

@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
    }
1
Elliot Sylvén 8 Апр 2021 в 14:49

2 ответа

Лучший ответ

Один из способов сделать это - использовать DispatchGroup, но, поскольку у вас довольно много разных шагов, может быть проще, если вы просто активируете следующий шаг, когда закончите с тем, над чем работаете.

//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)")
                }
            }
        }
    }
}
2
lorem ipsum 8 Апр 2021 в 18:29

Спасибо, что нашли время. Он отлично работает, и качество кода просто потрясающее.

0
Elliot Sylvén 8 Апр 2021 в 18:46