You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift iOS实现1米级区域进入通知及近程定位精度问题

Swift iOS实现1米范围位置触发提示的精度优化方案

问题分析

你当前的实现在长距离场景下正常,但10米内短距离定位精度不足,核心原因包括:

  • 未配置最高精度的定位参数
  • 未过滤低精度的位置数据
  • 区域监测的半径设置错误(误用了maximumRegionMonitoringDistance)
  • 未利用位置的horizontalAccuracy判断数据可信度

解决方案

1. 配置高精度定位参数

在LocationManager初始化时,设置最高精度的定位模式和最小更新距离,确保获取最精确的位置数据:

override init() {
    super.init()
    manager.delegate = self
    // 设置最高导航精度,适合短距离精准定位
    manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
    // 仅当位置变化超过1米时更新,减少不必要的计算
    manager.distanceFilter = 1.0
    // 允许后台定位(如果需要后台触发提示)
    manager.allowsBackgroundLocationUpdates = true
    manager.pausesLocationUpdatesAutomatically = false
}

2. 过滤低精度位置数据

在didUpdateLocations回调中,只处理水平精度符合要求的位置(比如精度≤2米,确保数据足够可靠):

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let location = locations.last, 
          location.horizontalAccuracy > 0, 
          location.horizontalAccuracy <= 2.0 else {
        // 过滤掉无效或精度不足的位置
        return
    }
    
    let latitude = location.coordinate.latitude
    let longitude = location.coordinate.longitude
    let altitude = location.altitude
    
    UserDefaults.standard.set(latitude, forKey: "lat")
    UserDefaults.standard.set(longitude, forKey: "long")
    UserDefaults.standard.set(altitude, forKey: "alt")
    
    coordinates = location.coordinate
    alt = altitude
    
    // 实时计算到目标点的距离,辅助区域监测
    distance = calculateDistanceWithHarvensin(crrLat: latitude, crrLong: longitude)
    if distance <= 1.0 && !entered {
        entered = true
        // 这里添加屏幕提示逻辑,比如发送通知或更新UI
        print("已进入1米范围")
    } else if distance > 1.0 && entered {
        entered = false
        print("已离开1米范围")
    }
}

3. 修正区域监测实现

将区域监测的半径设置为目标的1米,同时确保正确启动监测:

func createCircularRegion() {
    let radius: CLLocationDistance = 1.0
    let regionIdentifier = "target_region"
    
    // 先停止已有监测(如果存在)
    if let existingRegions = manager.monitoredRegions as? [CLCircularRegion] {
        for region in existingRegions where region.identifier == regionIdentifier {
            manager.stopMonitoring(for: region)
        }
    }
    
    let circularRegion = CLCircularRegion(center: targetCoordinate, radius: radius, identifier: regionIdentifier)
    circularRegion.notifyOnEntry = true
    circularRegion.notifyOnExit = true
    
    // 启动区域监测
    if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
        manager.startMonitoring(for: circularRegion)
    }
    
    UserDefaults.standard.set(targetCoordinate.latitude, forKey: "centerLatitude")
    UserDefaults.standard.set(targetCoordinate.longitude, forKey: "centerLongitude")
    
    print("已创建并启动1米范围区域监测")
}

// 移除原有的monitoRegionAtLocation方法,整合到createCircularRegion中

4. 完善权限处理

在didChangeAuthorization中根据权限状态启动定位和监测:

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    switch status {
    case .authorizedAlways, .authorizedWhenInUse:
        manager.startUpdatingLocation()
        createCircularRegion() // 权限获取后启动区域监测
    case .denied, .restricted:
        // 提示用户开启定位权限
        print("定位权限已被拒绝,请在设置中开启")
    case .notDetermined:
        manager.requestAlwaysAuthorization() // 根据需求选择Always或WhenInUse
    @unknown default:
        break
    }
}

5. 优化距离计算逻辑

保留Haversine公式计算,但确保目标坐标统一(避免代码中多个不同的目标坐标):

// 统一目标坐标为常量
private let targetCoordinate = CLLocationCoordinate2D(latitude: 23.178193, longitude: 75.786546)

func calculateDistanceWithHarvensin(crrLat: Double, crrLong: Double) -> Double {
    let desLat = targetCoordinate.latitude
    let desLong = targetCoordinate.longitude
    let radius: Double = 6367444.7
    
    let haversin = { (angle: Double) -> Double in
        return (1 - cos(angle))/2
    }
    
    let ahaversin = { (angle: Double) -> Double in
        return 2 * asin(sqrt(angle))
    }
    
    let dToR = { (angle: Double) -> Double in
        return angle * .pi / 180 // 更简洁的角度转弧度公式
    }
    
    let lat1 = dToR(crrLat)
    let lon1 = dToR(crrLong)
    let lat2 = dToR(desLat)
    let lon2 = dToR(desLong)
    
    return radius * ahaversin(haversin(lat2 - lat1) + cos(lat1) * cos(lat2) * haversin(lon2 - lon1))
}

修改后的完整代码

import CoreLocation
import Combine

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    let manager = CLLocationManager()
    @Published var coordinates: CLLocationCoordinate2D?
    @Published var alt: CLLocationDistance?
    private let targetCoordinate = CLLocationCoordinate2D(latitude: 23.178193, longitude: 75.786546)
    @Published var distance: Double = 0
    
    @Published var entered: Bool = false
    @Published var exit: Bool = false
    
    override init() {
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        manager.distanceFilter = 1.0
        manager.allowsBackgroundLocationUpdates = true
        manager.pausesLocationUpdatesAutomatically = false
    }
    
    func createCircularRegion() {
        let radius: CLLocationDistance = 1.0
        let regionIdentifier = "target_region"
        
        if let existingRegions = manager.monitoredRegions as? [CLCircularRegion] {
            for region in existingRegions where region.identifier == regionIdentifier {
                manager.stopMonitoring(for: region)
            }
        }
        
        let circularRegion = CLCircularRegion(center: targetCoordinate, radius: radius, identifier: regionIdentifier)
        circularRegion.notifyOnEntry = true
        circularRegion.notifyOnExit = true
        
        if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
            manager.startMonitoring(for: circularRegion)
        }
        
        UserDefaults.standard.set(targetCoordinate.latitude, forKey: "centerLatitude")
        UserDefaults.standard.set(targetCoordinate.longitude, forKey: "centerLongitude")
        
        print("已创建并启动1米范围区域监测")
    }
    
    func calculateDistance(crrLat: Double, crrLong: Double) {
        let destinationLocation = CLLocation(coordinate: targetCoordinate, altitude: 0, horizontalAccuracy: 1, verticalAccuracy: 1, timestamp: Date())
        let currentLocation = CLLocation(latitude: crrLat, longitude: crrLong)
        distance = currentLocation.distance(from: destinationLocation)
        print(String(format: "到目标点距离: %.02f m", distance))
    }
    
    func calculateDistanceWithHarvensin(crrLat: Double, crrLong: Double) -> Double {
        let desLat = targetCoordinate.latitude
        let desLong = targetCoordinate.longitude
        let radius: Double = 6367444.7
        
        let haversin = { (angle: Double) -> Double in
            return (1 - cos(angle))/2
        }
        
        let ahaversin = { (angle: Double) -> Double in
            return 2 * asin(sqrt(angle))
        }
        
        let dToR = { (angle: Double) -> Double in
            return angle * .pi / 180
        }
        
        let lat1 = dToR(crrLat)
        let lon1 = dToR(crrLong)
        let lat2 = dToR(desLat)
        let lon2 = dToR(desLong)
        
        return radius * ahaversin(haversin(lat2 - lat1) + cos(lat1) * cos(lat2) * haversin(lon2 - lon1))
    }
    
    func getLocation() {
        manager.requestAlwaysAuthorization()
    }
    
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            manager.startUpdatingLocation()
            createCircularRegion()
        case .denied, .restricted:
            print("定位权限已被拒绝,请在设置中开启")
        case .notDetermined:
            manager.requestAlwaysAuthorization()
        @unknown default:
            break
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last,
              location.horizontalAccuracy > 0,
              location.horizontalAccuracy <= 2.0 else {
            return
        }
        
        let latitude = location.coordinate.latitude
        let longitude = location.coordinate.longitude
        let altitude = location.altitude
        
        UserDefaults.standard.set(latitude, forKey: "lat")
        UserDefaults.standard.set(longitude, forKey: "long")
        UserDefaults.standard.set(altitude, forKey: "alt")
        
        coordinates = location.coordinate
        alt = altitude
        
        distance = calculateDistanceWithHarvensin(crrLat: latitude, crrLong: longitude)
        if distance <= 1.0 && !entered {
            entered = true
            exit = false
            print("已进入1米范围")
            // 在这里添加屏幕提示逻辑,比如用UIAlertController或自定义提示框
        } else if distance > 1.0 && entered {
            entered = false
            exit = true
            print("已离开1米范围")
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("定位错误: \(error.localizedDescription)")
    }
    
    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        if region.identifier == "target_region" {
            entered = true
            exit = false
            print("区域监测:已进入1米范围")
        }
    }
    
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        if region.identifier == "target_region" {
            entered = false
            exit = true
            print("区域监测:已离开1米范围")
        }
    }
}

额外注意事项

  • 确保在Info.plist中添加NSLocationAlwaysAndWhenInUseUsageDescription和NSLocationWhenInUseUsageDescription权限描述
  • 后台定位需要开启项目的Background Modes中的Location updates选项
  • 实际测试时,建议在户外开阔区域进行,室内GPS精度会受影响

内容的提问来源于stack exchange,提问作者Ayan Ansari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:55:21