如何让segue在按下按钮时等待功能完成?

时间:2017-05-18 20:36:22

标签: ios swift uibutton segue uistoryboardsegue

我有一个快速的应用程序,它在一个视图控制器上抓取一些gps数据,然后将该数据传递给另一个视图控制器。

我当前的方法涉及一个按钮,用户将输入一些数据,点击此按钮,然后该按钮应执行两个动作,计算gps路线,并对下一个视图控制器执行segue。

然而,在进入下一个屏幕之前,我似乎无法让segue等待我的计算完成。如果我落在第二个视图上然后按回来,然后再次按下按钮,数据将显示在第二个屏幕上,但我似乎无法立即开始工作。

我关注了这个问题:Separate Button action and segue action但仍然遇到问题

我是一个快速的完全菜鸟,所以如果这个问题很简单,我道歉

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
        }


        })


      }

2 个答案:

答案 0 :(得分:3)

您的代码看起来很好,所以问题出在您省略的部分之一。我看到两种可能性:

1)你的&#34; segue_to_table&#34; segue直接链接到故事板中的calculateButton

如果属于这种情况,则会立即执行同时调用getRoutes()。解决方案是删除该segue并制作新的手册。

要执行此操作,请在第一个视图控制器中按住Ctrl键并单击其中带有白色方块的小黄色圆圈,然后拖动到第二个视图控制器。给它一个标识符,你就可以了。

2)省略的&#34;长任务&#34;涉及异步的事情。

如果是这种情况,getRoutes()启动异步任务,然后在完成之前立即触发segue。

如何修复它取决于具体的异步代码,但很可能你会想要找到一个&#34;完成&#34;回拨并将您的电话转到performSegue()

更新新代码

您肯定遇到了异步代码的问题,这种问题因递归而变得复杂。除了什么时候出现问题,看起来你还有一个竞争条件getRoutes()你开始calculateRoutes()两次,所以两者都会运作以不可预测的顺序在同一routeArraydistanceArray上。

要清理它,你需要意识到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()计算两条路线,并将它们按顺序附加到routesArraydistanceArray。因为您不知道哪个会先终止,所以您不能同时打电话给他们。你拨打第一个电话,然后从第一个completionHandler拨打第二个电话。由于你想在两个完成后执行segue,你可以从第二个完成处理程序中调用它。所以它看起来像这样:

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,这很整洁但如果您不熟悉它可能会让您感到困惑。

答案 1 :(得分:0)

这很简单,只需要阻止segue执行segue,

如果你的功能尚未准备好,则返回false。 功能准备就绪后,再次调用perform segue并返回true

func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
    if (identifier == "mySegueIdentifier") {
        return isComplete
    }
    return true
}