json - 如何在 SwiftUI 中等待函数结束?
问题描述
基本上,前两个函数 (FetchOriginCoordinates
和FetchDestCoordinates
) 将街道名称作为输入并返回其坐标。然后使用坐标在函数FetchOriginID
和中查找附近的公交车站FetchDestID
。这些函数返回巴士站 ID,然后在最后一个函数 ( FetchTrip
) 中使用。在该函数中使用的 URL 中有四个可修改的参数:originExtId、destExtId、date和time。originExtId和destExtId是从和获取的值。日期和时间参数对应于变量和。FetchOriginID
FetchDestID
arrivalTime
travelDate
我只在按下inFetchTrip
时调用该函数。第一次调用函数时的变量:, , , ,和都是nil。第二次和都是nil。第三次函数工作(返回正确的值)。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
}
解决方案
一种方法是通过,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)")
}
}
}
}
}
推荐阅读
- mysql - Mysql 3 表连接和更新
- ruby - Ruby on Rails 中的小数问题
- microsoft-graph-api - 使用 MSGraph 发布或修补 EducationClass 时出错
- linux-kernel - linux中的中断调度和处理
- javascript - vuex 动作返回的承诺在 firefox 中没有 finally() 方法
- regex - 在 Promtheus 中重新标记未按预期工作
- python - Python:我可以使用 __getitem__ 代替 __next__ 和 __iter__
- node.js - 有没有一种可能的方法来解析这个地址
- javascript - kendo ui Grid 虚拟滚动 - 滚动在哪里?
- jquery - 媒体查询在不同尺寸的屏幕上效果不佳