ios - 如何让 segue 在按下按钮时等待函数完成?

标签 ios swift uibutton segue uistoryboardsegue

我有一个 swift 应用程序,它可以在一个 View Controller 上获取一些 GPS 数据,然后将该数据传递到另一个 View Controller 。

我当前的方法涉及一个按钮,用户将在其中输入一些数据,点击此按钮,然后该按钮应执行两个操作,计算 GPS 路线,并执行到下一个 View Controller 的 segue。

但是,我似乎无法在进入下一个屏幕之前等待计算完成。如果我降落在第二个 View 上,然后按返回,然后再次按按钮,数据将显示在第二个屏幕上,但我似乎无法立即使其工作。

我关注了这个问题: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 直接链接到 Storyboard 中的 calculateButton

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

为此,请按住 Control 键并单击第一个 View Controller 中内部有白色方 block 的黄色小圆圈,然后拖动到第二个 View Controller 。给它一个标识符,一切就都准备好了。

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

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

如何修复它取决于特定的异步代码,但很可能您需要查找“完成”回调并将您的调用放在其中的 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 上运行并不是 super 安全(如果在 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这里,它很简洁,但如果您不熟悉它,可能会感到困惑。

关于ios - 如何让 segue 在按下按钮时等待函数完成?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44057328/

相关文章:

swift - 在 Swift 4 中解码小写和大写 JSON 键

ios - 将 Sprite Kit Physics Body 移动到用户触摸的位置而不通过

javascript - 单击状态栏不会滚动到应用程序顶部 (Trigger.io)

ios - RestKit删除孤立对象规则

ios - GPUImage:继续录制影片崩溃 'Unable to create an OpenGL ES 2.0 context'

ios - 如何设置按钮的背景颜色 - xcode

ios - UIButton 未接收触摸事件

ios - 将 3 个 UIButtons 对齐到 UIViewController 的底部中心

iphone - 是否可以在 IOS 上第二次请求推送通知?

javascript - 替代 javascript 事件中的 `touchmove`