首页 > 解决方案 > 如何在 SwiftUI 中等待函数结束?

问题描述

基本上,前两个函数 (FetchOriginCoordinatesFetchDestCoordinates) 将街道名称作为输入并返回其坐标。然后使用坐标在函数FetchOriginID和中查找附近的公交车站FetchDestID。这些函数返回巴士站 ID,然后在最后一个函数 ( FetchTrip) 中使用。在该函数中使用的 URL 中有四个可修改的参数:originExtIddestExtIddatetime。originExtId和destExtId是从和获取的值。日期时间参数对应于变量和。FetchOriginIDFetchDestIDarrivalTimetravelDate

我只在按下inFetchTrip时调用该函数。第一次调用函数时的变量:, , , ,和都是nil。第二次和都是nil。第三次函数工作(返回正确的值)。Buttonbodyself.originLatself.originLonself.destLatself.destLonself.originIDself.destIDself.originIDself.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
    }

标签: jsonswiftxcodeapiswiftui

解决方案


一种方法是通过,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)")
                }
            }
        }
    }
}

推荐阅读