我正在使用 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,那么你会如何等待位置呢?
问题是您正在尝试同步获取位置,因此只有当您要求时
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)
}
}
注意:这是一个草稿代码,我没有尝试编译它,因此您可能需要修复一些编译错误。
这是整个场景的一个很好的演练(它还显示了更好的代码组织(即如何请求权限等)。
这个方法是否达到了预期的效果?您需要什么样的权限?我尝试了“始终”和“在应用程序中”,但仍然没有在
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!")
}
}