ios - Apple Watch 应用程序 : run an MKDirectionsRequest through the parent iOS app in the background?

标签 ios swift mapkit watchkit apple-watch

我正在编写一个 Apple Watch 应用,有时我需要获取有关从用户当前位置到特定地点的步行或驾车距离的信息。

按照 Apple 在其 Apple Watch Programming Guide 中的推荐,我通过从 Apple Watch 调用 openParentApplication 并在 iOS 应用程序端实现 handleWatchKitExtensionRequest 函数,将所有艰苦的工作委托(delegate)给 iOS 应用程序。因此,iOS 应用程序负责:1) 使用 MapKit 计算到目的地的方向,以及 2) 将获取的距离和预期时间返回到 Apple Watch。

这个操作是通过 MapKit 的 MKDirectionsRequest 进行的,它往往是“慢”的(例如,1 或 2 秒)。如果我直接在 iOS 应用程序中使用相同的参数测试我的代码,一切正常:我得到了预期的时间和距离响应。但是,在 Apple Watch 应用程序内部,回调(openParentApplicationreply 参数)永远不会被调用,设备也永远不会取回其信息。

更新 1: 由更新 3 替换。

更新 2: 实际上,我一开始怀疑没有超时,但它似乎只在 iOS 应用程序在 iPhone 的前台运行时才有效。如果我尝试从 Apple Watch 应用程序运行查询而不触及 iPhone 模拟器上的任何内容(即:应用程序在后台被唤醒),那么什么也不会发生。只要我在 iPhone 模拟器上点击我的应用程序图标,将其放在最前面,Apple Watch 就会收到回复。

更新 3:根据 Duncan 的要求,下面是所涉及的完整代码,重点是执行路径丢失的地方:

(在类 WatchHelper 中)

var callback: (([NSObject : AnyObject]!) -> Void)?

func handleWatchKitExtensionRequest(userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
    // Create results and callback object for this request
    results = [NSObject: AnyObject]()
    callback = reply
    // Process request
    if let op = userInfo["op"] as String? {
        switch op {
        case AppHelper.getStationDistanceOp:
            if let uic = userInfo["uic"] as Int? {
                if let transitType = userInfo["transit_type"] as Int? {
                    let transportType: MKDirectionsTransportType = ((transitType == WTTripTransitType.Car.rawValue) ? .Automobile : .Walking)
                    if let loc = DatabaseHelper.getStationLocationFromUIC(uic) {
                        // The following API call is asynchronous, so results and reply contexts have to be saved to allow the callback to get called later
                        LocationHelper.sharedInstance.delegate = self
                        LocationHelper.sharedInstance.routeFromCurrentLocationToLocation(loc, withTransportType: transportType)
                    }
                }
            }
        case ... // Other switch cases here
        default:
            NSLog("Invalid operation specified: \(op)")
        }
    } else {
        NSLog("No operation specified")
    }
}

func didReceiveRouteToStation(distance: CLLocationDistance, expectedTime: NSTimeInterval) {
    // Route information has been been received, archive it and notify caller
    results!["results"] = ["distance": distance, "expectedTime": expectedTime]
    // Invoke the callback function with the received results
    callback!(results)
}

(在类 LocationHelper 中)

func routeFromCurrentLocationToLocation(destination: CLLocation, withTransportType transportType: MKDirectionsTransportType) {
    // Calculate directions using MapKit
    let currentLocation = MKMapItem.mapItemForCurrentLocation()
    var request = MKDirectionsRequest()
    request.setSource(currentLocation)
    request.setDestination(MKMapItem(placemark: MKPlacemark(coordinate: destination.coordinate, addressDictionary: nil)))
    request.requestsAlternateRoutes = false
    request.transportType = transportType
    let directions = MKDirections(request: request)
    directions.calculateDirectionsWithCompletionHandler({ (response, error) -> Void in
        // This is the MapKit directions calculation completion handler
        // Problem is: execution never reaches this completion block when called from the Apple Watch app
        if response != nil {
            if response.routes.count > 0 {
                self.delegate?.didReceiveRouteToStation?(response.routes[0].distance, expectedTime: response.routes[0].expectedTravelTime)
            }
        }
    })
}

更新 4:iOS 应用显然已设置为能够在后台接收位置更新,如下面的屏幕截图所示:

Location services are "Always" enabled for my app

所以现在的问题是:是否有任何方法可以“强制”在后台发生 MKDirectionsRequest

最佳答案

此代码适用于我正在开发的应用程序。它还在后台与应用程序一起工作,所以我认为可以肯定地说 MKDirectionsRequest 将在后台模式下工作。此外,这是从 AppDelegate 调用的,并包装在 beginBackgroundTaskWithName 标记中。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

                MKPlacemark *destPlacemark = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake(destLat, destLon) addressDictionary:nil];
                MKPlacemark *currentPlacemark = [[MKPlacemark alloc] initWithCoordinate:CLLocationCoordinate2DMake(currLat, currLon) addressDictionary:nil];

                NSMutableDictionary __block *routeDict=[NSMutableDictionary dictionary];
                MKRoute __block *routeDetails=nil;

                MKDirectionsRequest *directionsRequest = [[MKDirectionsRequest alloc] init];
                [directionsRequest setSource:[[MKMapItem alloc] initWithPlacemark:currentPlacemark]];
                [directionsRequest setDestination:[[MKMapItem alloc] initWithPlacemark:destPlacemark]];
                directionsRequest.transportType = MKDirectionsTransportTypeAutomobile;

                    dispatch_async(dispatch_get_main_queue(), ^(){

                        MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];

                        [directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
                            if (error) {
                                NSLog(@"Error %@", error.description);

                            } else {
                                NSLog(@"ROUTE: %@",response.routes.firstObject);
                                routeDetails = response.routes.firstObject;

                                [routeDict setObject:[NSString stringWithFormat:@"%f",routeDetails.distance] forKey:@"routeDistance"];
                                [routeDict setObject:[NSString stringWithFormat:@"%f",routeDetails.expectedTravelTime]  forKey:@"routeTravelTime"];

                                NSLog(@"Return Dictionary: %@",routeDict);

                                reply(routeDict);
                            }
                        }];

                    });
            });

从 OP 编辑​​:上面的代码可能在 ObjC 中有效,但它有效的确切原因是它没有使用 MKMapItem.mapItemForCurrentLocation()。所以我的工作代码如下所示:

func routeFromCurrentLocationToLocation(destination: CLLocation, withTransportType transportType: MKDirectionsTransportType) {
    // Calculate directions using MapKit
    let currentLocation = MKMapItem(placemark: MKPlacemark(coordinate: CLLocationCoordinate2DMake(lat, lng), addressDictionary: nil))
    var request = MKDirectionsRequest()
    // ...
}

关于ios - Apple Watch 应用程序 : run an MKDirectionsRequest through the parent iOS app in the background?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27749581/

相关文章:

ios - 类型 'MKDirectionsRequest' 的值没有成员 'setSource'

ios - 在现有上传的应用程序中启用推送通知

ios - 锁定相机旋转

swift - 为什么 Swift 将这个字素簇计为两个字符而不是一个字符?

ios - 反斜杠 N 在 swift 2.2 中不起作用

iOS 如何让 MapKit 显示自定义室内地图?

ios - didSelect for MKAnnotationView 未触发

ios - 阅读 CGFontCache.plist 会被 AppStore 拒绝吗?

iphone - GPUImageAlphaBlendFilter-在一个图像上合并两个图像

ios - 如何在ios swift中根据ID读取本地json文件?