Build location-aware enterprise apps
System & Services 进阶 20m

构建位置感知的企业应用

Build location-aware enterprise apps

2020年6月24日

在 Apple 官方观看视频

一句话判断

iOS 14 新增的”大致位置”选项让用户可以选择只给 App 一个模糊的位置精度,如果你的企业 App 需要精确定位(比如室内导航、资产管理),你需要引导用户授予精确定位权限。

这场 Session 讲了什么

iOS 14 对定位权限做了重大调整。之前用户只有”允许”和”不允许”两个选项。iOS 14 新增了第三种选择:“大致位置”(Approximate Location)。用户选择大致位置后,App 收到的坐标会被模糊化到约几平方公里的范围。

对企业 App 来说,这带来了新的挑战。如果你的 App 依赖精确定位(比如工厂设备定位、仓库货物追踪、办公室签到),你需要处理用户只给大致位置的情况。Session 介绍了如何检测当前的定位精度、如何引导用户切换到精确定位、以及如何使用新的 CLLocationManager API 来请求一次性精确定位。

值得深挖的点

精确定位 vs 大致定位的差异

当用户选择大致位置时,CLLocationManager 返回的坐标会在真实位置附近随机偏移。偏移范围大约在 1-5 公里之间,并且会定期变化(即使设备没有移动)。这种设计是为了防止通过定位数据追踪用户。对于需要街区级别或室内级别精度的 App,大致位置是完全不够用的。

一次性精确定位请求

iOS 14 引入了一个新的 API:requestTemporaryFullAccuracyAuthorization(withPurposeKey:)。这个 API 允许你请求一次性的精确定位权限。用户会看到一个弹窗,说明你的 App 为什么需要精确定位。如果用户同意,你的 App 会在当前会话中获得精确定位能力。下次启动时,会恢复到大致位置。

代码片段

检查当前的定位精度

场景:在 App 启动时检查用户授予的定位精度。

import CoreLocation

class LocationManager: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()
    var onLocationUpdate: ((CLLocation) -> Void)?
    
    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
    }
    
    func requestLocation() {
        // 检查定位权限状态
        let status = manager.authorizationStatus
        
        switch status {
        case .notDetermined:
            // 首次请求定位权限
            manager.requestWhenInUseAuthorization()
            
        case .authorizedWhenInUse, .authorizedAlways:
            // 检查精度级别(iOS 14 新增)
            if #available(iOS 14.0, *) {
                let accuracy = manager.accuracyAuthorization
                
                switch accuracy {
                case .fullAccuracy:
                    // 用户授予了精确定位
                    print("精确定位已授权")
                    manager.requestLocation()
                    
                case .reducedAccuracy:
                    // 用户只授予了大致定位
                    print("仅大致定位,需要引导用户开启精确定位")
                    requestFullAccuracy()
                    
                @unknown default:
                    break
                }
            }
            
        case .denied, .restricted:
            print("定位权限被拒绝")
            
        default:
            break
        }
    }
    
    private func requestFullAccuracy() {
        if #available(iOS 14.0, *) {
            // 请求一次性精确定位权限
            // purposeKey 对应 Info.plist 中 NSLocationTemporaryUsageDescriptionDictionary 里的 key
            manager.requestTemporaryFullAccuracyAuthorization(
                withPurposeKey: "PreciseAssetTracking"
            ) { error in
                if let error = error {
                    print("精确定位请求失败: \(error)")
                    // 使用大致位置继续工作,或提示用户
                } else {
                    print("获得精确定位权限")
                    self.manager.requestLocation()
                }
            }
        }
    }
    
    // CLLocationManagerDelegate
    func locationManager(_ manager: CLLocationManager,
                         didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.first else { return }
        onLocationUpdate?(location)
    }
    
    func locationManager(_ manager: CLLocationManager,
                         didFailWithError error: Error) {
        print("定位失败: \(error)")
    }
}

配置 Info.plist 的定位权限说明

场景:为精确定位请求提供用户可见的说明文本。

<!-- Info.plist 配置 -->

<!-- 标准的定位权限说明 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要访问您的位置来标记设备位置</string>

<!-- 一次性精确定位的说明字典(iOS 14 新增) -->
<key>NSLocationTemporaryUsageDescriptionDictionary</key>
<dict>
    <!-- key 对应代码中的 purposeKey -->
    <key>PreciseAssetTracking</key>
    <string>需要精确位置来定位您附近的设备资产</string>
    
    <key>IndoorNavigation</key>
    <string>需要精确位置来提供室内导航服务</string>
    
    <key>GeofenceCheckIn</key>
    <string>需要精确位置来确认您已到达办公室</string>
</dict>

企业场景:设备资产管理

场景:在企业园区内追踪设备位置。

class AssetTrackingManager {
    private let locationManager = CLLocationManager()
    private var assets: [Asset] = []
    
    struct Asset {
        let id: String
        let name: String
        var lastKnownLocation: CLLocation?
    }
    
    func startTracking(assetId: String) {
        // 先检查是否有精确定位权限
        if #available(iOS 14.0, *) {
            if locationManager.accuracyAuthorization == .reducedAccuracy {
                // 没有精确定位,请求一次性权限
                locationManager.requestTemporaryFullAccuracyAuthorization(
                    withPurposeKey: "PreciseAssetTracking"
                ) { [weak self] error in
                    if error == nil {
                        self?.beginTracking(assetId: assetId)
                    }
                }
                return
            }
        }
        
        beginTracking(assetId: assetId)
    }
    
    private func beginTracking(assetId: String) {
        locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        locationManager.startUpdatingLocation()
        
        // 持续更新设备位置
        // 将位置数据上报到资产管理服务器
    }
    
    func checkProximity(targetLocation: CLLocation,
                         threshold: Double = 10.0) -> Bool {
        guard let currentLocation = locationManager.location else { return false }
        
        let distance = currentLocation.distance(from: targetLocation)
        return distance <= threshold  // 在 10 米范围内
    }
}

处理精度变化的回调

场景:当用户在设置中切换定位精度时做出响应。

class LocationAwareViewController: UIViewController {
    let locationManager = CLLocationManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        if #available(iOS 14.0, *) {
            // 监听精度变化
            // 当用户在设置中切换精确定位/大致定位时会触发
        }
    }
}

// CLLocationManagerDelegate
extension LocationAwareViewController {
    
    @available(iOS 14.0, *)
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        let status = manager.authorizationStatus
        let accuracy = manager.accuracyAuthorization
        
        switch (status, accuracy) {
        case (.authorizedWhenInUse, .fullAccuracy):
            // 精确定位可用
            showFullFeatureSet()
            
        case (.authorizedWhenInUse, .reducedAccuracy):
            // 只有大致定位
            showAccuracyUpgradePrompt()
            
        case (.denied, _):
            // 定位被拒绝
            showLocationDeniedMessage()
            
        default:
            break
        }
    }
    
    private func showAccuracyUpgradePrompt() {
        let alert = UIAlertController(
            title: "需要精确定位",
            message: "此功能需要精确位置才能正常工作。请在弹出的选项中选择「允许一次精确定位」。",
            preferredStyle: .alert
        )
        alert.addAction(UIAlertAction(title: "开启精确定位", style: .default) { [weak self] _ in
            self?.locationManager.requestTemporaryFullAccuracyAuthorization(
                withPurposeKey: "PreciseAssetTracking"
            )
        })
        alert.addAction(UIAlertAction(title: "继续使用大致位置", style: .cancel))
        present(alert, animated: true)
    }
}

最佳实践

已有项目:如果你的 App 依赖精确定位,在 iOS 14 上必须处理 reducedAccuracy 的情况。添加 NSLocationTemporaryUsageDescriptionDictionary 到 Info.plist,在需要精确定位时通过 requestTemporaryFullAccuracyAuthorization 请求权限。

新项目:从产品设计阶段就考虑定位精度的影响。区分哪些功能需要精确定位,哪些功能大致位置就够了。对于只需要城市级别定位的功能(如天气),大致位置完全够用。只有室内导航、资产追踪等场景才需要精确定位。

还有什么值得关注

  • 用户可以在「设置 > 隐私 > 定位服务」中随时切换精确定位/大致定位。
  • CLLocationManager 新增了 accuracyAuthorization 属性来查询当前精度级别。
  • 大致位置的坐标会定期变化,不要用它来判断设备是否静止。
WWDC 2020