如何让 segue 等待功能在按下按钮时完成?
How to make segue wait for function to complete on button press?
我有一个 swift 应用程序,它在一个视图控制器上获取一些 gps 数据,然后将该数据传递给另一个视图控制器。
我目前的方法涉及一个按钮,用户将在其中输入一些数据,点击此按钮,然后该按钮应执行两个操作,计算 gps 路线,并执行到下一个视图控制器的 segue。
但是,我似乎无法让 segue 在进入下一个屏幕之前等待我的计算完成。如果我登陆第二个视图然后按返回,然后再次按按钮,数据将显示在第二个屏幕上,但我似乎无法立即让它工作。
我关注了这个问题:Separate Button action and segue action 但仍然遇到问题
我完全是 swift 的菜鸟,所以如果这个问题很琐碎,我深表歉意
FirstViewController.swift
:
....
@IBOutlet weak var calculateButton: UIButton!
var routeArray = Array<Array<MKRoute>>()
var distanceArray: [CLLocationDistance] = []
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue_to_table"{
if let destination = segue.destination as? SecondTableViewController{
destination.routeArray = self.routeArray
destination.distanceArray = self.distanceArray
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
....
calculateButton.addTarget(self, action: #selector(getRoutes(button:)), for: .touchUpInside)
}
func getRoutes(button: UIButton){
locationArray = set of calculated MKMapItems
calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.green)
locationArray = []
locationArray = set of other MKMapItems (different route essentially)
calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.red)
}
func calculateRoute(index:Int, distance: CLLocationDistance, routes: [MKRoute], color: UIColor){
let request: MKDirectionsRequest = MKDirectionsRequest()
request.source = locationArray[index]
request.destination = locationArray[index+1]
request.requestsAlternateRoutes = true
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate(completionHandler: {(response:MKDirectionsResponse?, error: Error?) in
if let routeResponse = response?.routes{
var distVar = distance
var routeVar = routes
routeVar.append(routeResponse[0])
distVar += routeResponse[0].distance
if index + 2 < self.locationArray.count{
self.calculateRoute(index: index+1, distance: distVar, routes: routeVar, color: color)
}
else{
self.routeArray.append(routeVar)
self.distanceArray.append(distVar)
}
}else if let _ = error{
//alert
}
})
}
你的代码看起来不错,所以问题出在你省略的部分之一。我看到两种可能性:
1) 您的 "segue_to_table" segue 直接链接到故事板中的 calculateButton
。
如果是这样,它会立即执行 segue 并且 同时调用 getRoutes()
。解决方案是删除那个 segue 并制作一个新的手动 segue。
为此,请在第一个视图控制器中按住 Control 键并单击内部带有白色方块的黄色小圆圈,然后拖动到第二个视图控制器。给它一个标识符,一切就绪。
2)省略的"long task"涉及到一些异步的东西。
如果是这种情况,getRoutes()
将启动 异步任务,然后在它完成之前立即触发 segue。
如何修复它取决于具体的异步代码,但您很可能会想要寻找一个 "completion" 回调并将您的调用放到 performSegue()
那里。
新代码更新
你肯定遇到了异步代码的问题,递归使它变得复杂。除了什么时候开始的问题,看起来你还有一个 竞争条件 :在 getRoutes()
中你开始 calculateRoutes()
两次,所以两者都是以不可预测的顺序在相同的 routeArray
和 distanceArray
上运行。
要清理它,您需要意识到 calculateRoute()
是一个异步函数,并使其表现得像一个异步函数。您希望在 calculateRoute()
完成其异步调用时发生某些事情,因此添加一个参数为其提供自己的完成回调,并在所有异步工作完成后调用它:
func calculateRoute(index:Int, distance: CLLocationDistance, routes: [MKRoute], color: UIColor, completionHandler: @escaping () -> Void) {
// ...
directions.calculate(completionHandler: {(response:MKDirectionsResponse?, error: Error?) in
if let routeResponse = response?.routes {
// ...
if index + 2 < self.locationArray.count{
self.calculateRoute(index: index+1, distance: distVar, routes: routeVar, color: color, completionHandler: completionHandler)
} else {
self.routeArray.append(routeVar)
self.distanceArray.append(distVar)
// done, call completion handler
completionHandler()
}
}
})
}
现在,当您调用 calculateRoute()
时,您会向它传递一个函数,以便在它完成时调用。它一个接一个地异步调用 calculate()
直到没有更多,然后它调用你的 completionHandler
并终止。我应该提一下,让它在外部 locationArray
上运行并不是超级安全(如果其他进程更改了该数组,而 calculateRoute()
是 运行 会发生什么?)但这是一个单独的问题。
现在您想使用 calculateRoute()
计算两条路线并将它们按顺序附加到您的 routesArray
和 distanceArray
。因为您不知道哪个会先终止,所以您不能同时调用它们。您调用第一个,然后从第一个 completionHandler
调用第二个。由于您想在 both 完成后执行 segue,因此您可以从 second 完成处理程序中调用它。所以它看起来像这样:
func getRoutes(button: UIButton) {
// ...
// start first asynchronous calculation
calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.green) {
// ...
// finished first calculation, start second one
self.calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.red, completionHandler: {
// finished second calculation, now segue
self.performSegue(withIdentifier: "segue_to_table", sender: self)
})
}
}
请注意,我们在这里使用 trailing closure syntax,它很简洁,但如果您不熟悉它,可能会造成混淆。
其实很简单,就是用 should perform segue 来防止 segue,
Return false 如果你的功能还没有准备好。
函数准备就绪后,再次调用 perform segue 和 return true
func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if (identifier == "mySegueIdentifier") {
return isComplete
}
return true
}
我有一个 swift 应用程序,它在一个视图控制器上获取一些 gps 数据,然后将该数据传递给另一个视图控制器。
我目前的方法涉及一个按钮,用户将在其中输入一些数据,点击此按钮,然后该按钮应执行两个操作,计算 gps 路线,并执行到下一个视图控制器的 segue。
但是,我似乎无法让 segue 在进入下一个屏幕之前等待我的计算完成。如果我登陆第二个视图然后按返回,然后再次按按钮,数据将显示在第二个屏幕上,但我似乎无法立即让它工作。
我关注了这个问题:Separate Button action and segue action 但仍然遇到问题
我完全是 swift 的菜鸟,所以如果这个问题很琐碎,我深表歉意
FirstViewController.swift
:
....
@IBOutlet weak var calculateButton: UIButton!
var routeArray = Array<Array<MKRoute>>()
var distanceArray: [CLLocationDistance] = []
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue_to_table"{
if let destination = segue.destination as? SecondTableViewController{
destination.routeArray = self.routeArray
destination.distanceArray = self.distanceArray
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
....
calculateButton.addTarget(self, action: #selector(getRoutes(button:)), for: .touchUpInside)
}
func getRoutes(button: UIButton){
locationArray = set of calculated MKMapItems
calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.green)
locationArray = []
locationArray = set of other MKMapItems (different route essentially)
calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.red)
}
func calculateRoute(index:Int, distance: CLLocationDistance, routes: [MKRoute], color: UIColor){
let request: MKDirectionsRequest = MKDirectionsRequest()
request.source = locationArray[index]
request.destination = locationArray[index+1]
request.requestsAlternateRoutes = true
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate(completionHandler: {(response:MKDirectionsResponse?, error: Error?) in
if let routeResponse = response?.routes{
var distVar = distance
var routeVar = routes
routeVar.append(routeResponse[0])
distVar += routeResponse[0].distance
if index + 2 < self.locationArray.count{
self.calculateRoute(index: index+1, distance: distVar, routes: routeVar, color: color)
}
else{
self.routeArray.append(routeVar)
self.distanceArray.append(distVar)
}
}else if let _ = error{
//alert
}
})
}
你的代码看起来不错,所以问题出在你省略的部分之一。我看到两种可能性:
1) 您的 "segue_to_table" segue 直接链接到故事板中的 calculateButton
。
如果是这样,它会立即执行 segue 并且 同时调用 getRoutes()
。解决方案是删除那个 segue 并制作一个新的手动 segue。
为此,请在第一个视图控制器中按住 Control 键并单击内部带有白色方块的黄色小圆圈,然后拖动到第二个视图控制器。给它一个标识符,一切就绪。
2)省略的"long task"涉及到一些异步的东西。
如果是这种情况,getRoutes()
将启动 异步任务,然后在它完成之前立即触发 segue。
如何修复它取决于具体的异步代码,但您很可能会想要寻找一个 "completion" 回调并将您的调用放到 performSegue()
那里。
新代码更新
你肯定遇到了异步代码的问题,递归使它变得复杂。除了什么时候开始的问题,看起来你还有一个 竞争条件 :在 getRoutes()
中你开始 calculateRoutes()
两次,所以两者都是以不可预测的顺序在相同的 routeArray
和 distanceArray
上运行。
要清理它,您需要意识到 calculateRoute()
是一个异步函数,并使其表现得像一个异步函数。您希望在 calculateRoute()
完成其异步调用时发生某些事情,因此添加一个参数为其提供自己的完成回调,并在所有异步工作完成后调用它:
func calculateRoute(index:Int, distance: CLLocationDistance, routes: [MKRoute], color: UIColor, completionHandler: @escaping () -> Void) {
// ...
directions.calculate(completionHandler: {(response:MKDirectionsResponse?, error: Error?) in
if let routeResponse = response?.routes {
// ...
if index + 2 < self.locationArray.count{
self.calculateRoute(index: index+1, distance: distVar, routes: routeVar, color: color, completionHandler: completionHandler)
} else {
self.routeArray.append(routeVar)
self.distanceArray.append(distVar)
// done, call completion handler
completionHandler()
}
}
})
}
现在,当您调用 calculateRoute()
时,您会向它传递一个函数,以便在它完成时调用。它一个接一个地异步调用 calculate()
直到没有更多,然后它调用你的 completionHandler
并终止。我应该提一下,让它在外部 locationArray
上运行并不是超级安全(如果其他进程更改了该数组,而 calculateRoute()
是 运行 会发生什么?)但这是一个单独的问题。
现在您想使用 calculateRoute()
计算两条路线并将它们按顺序附加到您的 routesArray
和 distanceArray
。因为您不知道哪个会先终止,所以您不能同时调用它们。您调用第一个,然后从第一个 completionHandler
调用第二个。由于您想在 both 完成后执行 segue,因此您可以从 second 完成处理程序中调用它。所以它看起来像这样:
func getRoutes(button: UIButton) {
// ...
// start first asynchronous calculation
calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.green) {
// ...
// finished first calculation, start second one
self.calculateRoute(index: 0, distance: 0, routes: [], color: UIColor.red, completionHandler: {
// finished second calculation, now segue
self.performSegue(withIdentifier: "segue_to_table", sender: self)
})
}
}
请注意,我们在这里使用 trailing closure syntax,它很简洁,但如果您不熟悉它,可能会造成混淆。
其实很简单,就是用 should perform segue 来防止 segue,
Return false 如果你的功能还没有准备好。 函数准备就绪后,再次调用 perform segue 和 return true
func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if (identifier == "mySegueIdentifier") {
return isComplete
}
return true
}