如何在 iOS 16 App Intents 中使用用户位置?

问题描述 投票:0回答:2

我正在使用 iOS 16 中的新 AppIntents 框架开发应用程序快捷方式,我正在尝试获取用户的当前位置,所有内容均已启用并使用权限正确设置

func perform() async throws -> some IntentResult {
    //Request User Location
    IntentHelper.sharedInstance.getUserLocation()
    
    guard let userCoords = IntentHelper.sharedInstance.currentUserCoords else { throw IntentErrors.locationProblem }
    
    //How to wait for location??
    
    return .result(dialog: "Worked! Current coords are \(userCoords)") {
         IntentSuccesView()
     }
}

这是 IntentHelper 类

    class IntentHelper: NSObject {
    
    static let sharedInstance = IntentHelper()
    var currentUserCoords: CLLocationCoordinate2D?
    
    private override init() {}
    
    func getUserLocation() {
        DispatchQueue.main.async {
            let locationManager = CLLocationManager()
            locationManager.delegate = self
            print("FINALLY THIS IS IT")
            self.currentUserCoords = locationManager.location?.coordinate
            print(self.currentUserCoords)
        }
    }
}

extension IntentHelper: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
        manager.stopUpdatingLocation()
    }
}

问题是,有时这很少起作用,大多数时候它会打印 nil,那么你会如何等待位置呢?

swift siri ios16 appintents
2个回答
3
投票

问题是您正在尝试同步获取位置,因此只有当您要求时

locationManager.location
已经不为零时,它才有效。相反,此操作可能需要时间,因此是异步的。

所以基本流程是这样的:

  • 检查权限(是的,你必须每次都这样做,因为用户可能随时取消权限)
  • 并告诉
    CLLocationManager
    开始解析用户位置
  • 之后只需通过
    locationManager(:, didUpdateLocations:)
    CLLocationManagerDelegate
    事件监听结果,其中 你需要实现(在你的情况下在同一个类中,因为你已经 在扩展中实现了失败案例)。

最重要的是,您可能想等待

func perform()
内的位置更新(坐标或失败)。

所以我想说你需要在

func perform()
中拥有类似的东西:

// Wait for coordinates
guard let userCoords = await IntentHelper.sharedInstance.getCurrentCoordinates() else { ... }

其中

getCurrentCoordinates()
只是一个异步包装器,类似于:

func getCurrentCoordinates() async -> CLLocationCoordinate2D? {
    await withCheckedContinuation { continuation in
       getCurrentCoordinates() { coordinates in
            continuation.resume(returning: coordinates)
        }
    }
}

getCurrentCoordinates(callback:)
将类似于:

class IntentHelper {
    var callback: ((CLLocationCoordinate2D?) -> Void)?
    //...
    func getCurrentCoordinates(callback: @escaping (CLLocationCoordinate2D?) -> Void) {

        // Step 1: check permissions
        let status = CLLocationManager.authorizationStatus()
        guard status == .authorizedAlways || status == .authorizedWhenInUse else {
            // you can't ask for permissions
            callback(nil)
            return

        // Step 2: preserve callback and request location
        self.callback = callback
        locationManager?.requestLocation()
    }
}

现在您需要做的就是等待

locationManager(:, didUpdateLocations:)
locationManager(:, didFailWithError:)
发生:

extension IntentHelper: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        
        // Pass the result (no location info) back to the caller
        self.callback?(nil)
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        // Pass the result location back to the caller
        // For simplicity lets say we take the first location in list
        self.callback?(locations.first)
    }
    
}

注意:这是一个草稿代码,我没有尝试编译它,因此您可能需要修复一些编译错误。

这是整个场景的一个很好的演练(它还显示了更好的代码组织(即如何请求权限等)。


0
投票

这个方法是否达到了预期的效果?您需要什么样的权限?我尝试了“始终”和“在应用程序中”,但仍然没有在

didUpdateLocations
中接收位置。

波纹管是我的完整实现。我错过了什么吗?

import Foundation
import AppIntents
import CoreLocation

class IntentLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    var locationContinuation: CheckedContinuation<CLLocationCoordinate2D?, Error>?
    let manager = CLLocationManager()
    @Published var lastLocation: CLLocation?
    @Published var locationError: CLError?
    @Published var authorizationStatus: CLAuthorizationStatus

    override init() {
        self.authorizationStatus = manager.authorizationStatus
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        checkAuthorizationStatus()
    }

    func requestLocation() async throws -> CLLocationCoordinate2D? {
        try await withCheckedThrowingContinuation { continuation in
            locationContinuation = continuation
            manager.requestLocation()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        NSLog(String(describing: locations.first?.coordinate))
        locationContinuation?.resume(returning: locations.first?.coordinate)
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        NSLog(error.localizedDescription)
        locationContinuation?.resume(throwing: error)
    }
    
    func checkAuthorizationStatus() {
        switch manager.authorizationStatus {
        case .notDetermined, .authorizedWhenInUse:
            manager.requestAlwaysAuthorization()
        case .restricted, .denied:
            self.locationError = CLError(.denied)
        case .authorizedAlways:
            NSLog("startUpdatingLocation")
            manager.startUpdatingLocation()
        @unknown default:
            self.locationError = CLError(.locationUnknown)
        }
        self.authorizationStatus = manager.authorizationStatus
        NSLog(String(describing: manager.isAuthorizedForWidgetUpdates))
    }
}

struct AddTripRecord: AppIntent {
    static var title = LocalizedStringResource("Add a new trip record")
    
    @Parameter(title: "Trip")
    var trip: TripIntentItem
    
    @Parameter(title: "Record Type")
    var recordType: TripRecordType
    
    func perform() async throws -> some IntentResult {
        let locationManager = IntentLocationManager()
        
        let location = try await locationManager.requestLocation()
        
        guard let currentLocation = location else {
            return .result(value: "Failed to get current location")
        }
        
        let recordLocation = CLLocationCoordinate2D(latitude: currentLocation.latitude, longitude: currentLocation.longitude)
        
        let record = TripRecord(type: recordType, content: "", location: recordLocation)
//        trip.records.append(record)
//        try! await SharedDatabase.shared.database.save()
        
        return .result(value: "Trip record successfuly saved!")
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.