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

Posted

技术标签:

【中文标题】如何使 segue 等待功能在按钮按下时完成?【英文标题】:How to make segue wait for function to complete on button press? 【发布时间】:2017-05-18 20:36:21 【问题描述】:

我有一个 swift 应用程序,它在一个视图控制器上抓取一些 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
        


        )


      

【问题讨论】:

不要直接从按钮触发segue;将 segue 链接到故事板中的视图控制器对象,然后在您准备好时通过 performSegue 以编程方式触发 segue。 【参考方案1】:

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

1) 您的“segue_to_table”转场直接链接到情节提要中的calculateButton

如果是这种情况,它会立即执行转场同时调用getRoutes()。解决方案是删除该 segue 并创建一个新的手动 segue。

为此,请在第一个视图控制器中按住 Control 键单击带有白色方块的黄色小圆圈,然后拖动到第二个视图控制器。给它一个标识符,一切就绪。

2) 省略的“长任务”涉及异步。

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

如何修复它取决于特定的异步代码,但很可能您会想要寻找“完成”回调并将您的调用发送到 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() 计算两条路线并将它们按顺序附加到您的routesArraydistanceArray。因为你不知道哪个会先终止,所以你不能同时调用它们。您拨打第一个电话,然后从第一个的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,这很简洁,但如果您不熟悉它可能会造成混淆。

【讨论】:

它可能是第二个,因为我已经实现了第一部分。我会尝试与您联系,看看是否是问题所在 @user3470987 很多 CoreLocation 函数都是异步的,所以如果您使用 GPS,则很有可能。如果您遇到问题,请将该代码添加到您的问题中以获得更具体的答案。 @user3470987 不看代码很难说。您可以编辑您的问题以包含来自getRoutes()calculateRoutes() 的代码吗? @user3470987 你的情况很复杂,但我想我已经解决了。查看我的更新答案。 @user3470987 我想我明白你的意思了。为了整洁,我把那条线去掉了,但那里有一个重要的变化。当您递归调用calculateRoute() 时,您只需传递completionHandler。由于它不是分叉,因此处理程序只会在序列结束时被调用一次。我用更改编辑了我的答案。【参考方案2】:

其实很简单,只要阻止segue with should perform segue,

如果您的功能尚未准备好,则返回 false。 一旦你的函数准备好了,再次调用 perform segue 并返回 true

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

【讨论】:

以上是关于如何使 segue 等待功能在按钮按下时完成?的主要内容,如果未能解决你的问题,请参考以下文章

按下时如何使按钮变大? (安卓工作室)

如何在按钮按下时通过动画进行文本转换?

按下时 Segue 不会显示 ViewController

iOS:让segue等待登录成功

按下时使按钮填充 UITableView

使 matplotlib 按钮小部件在按下时启动,而不是在释放时启动