【问题标题】:Track changes in the location for a certain distance iOS跟踪一定距离的位置变化 iOS
【发布时间】:2018-01-17 19:22:51
【问题描述】:

之后我有一个任务要在后台跟踪用户的位置,如果它的位置已经改变到超过 5 英里,那么我需要在服务器上更新这个数据。我知道您可以使用 startMonitoringSignificantLocationChanges 开始跟踪用户位置。我开始测试,使用startMonitoringSignificantLocationChangesallowsBackgroundLocationUpdates = true 启动应用程序,然后从模拟器内存​​中删除应用程序,进入地图并启用 Free Way 模拟。一分钟我在服务器上得到了 8 次更新,对我来说太频繁了。我认为对我来说,最好的解决方案是如果我们询问我们希望从多远的距离接收更新。我读了几篇关于这个的帖子,但没有一篇没有解决我的问题。我还认为您可以保存以前的位置并将更改与新位置进行比较,但我认为这是一个坏主意。告诉我,如何更好地解决这个问题?

class LocationManager: NSObject {

    private override init() {
        super.init()
    }

    static let shared = LocationManager()

    private let locationManager = CLLocationManager()

    weak var delegate: LocationManagerDelegate?

    // MARK: - Flags

    private var isCallDidStartGetLocation = false

    // MARK: - Measuring properties

    private var startTimestamp = 0.0

    // MARK: - Open data

    var currentLocation: CLLocation?

    // MARK: - Managers 

    private let locationDatabaseManager = LocationDatabaseManager()

    // MARK: - Values

    private let metersPerMile = 1609.34

    func start() {
        // measuring data
        startTimestamp = Date().currentTimestamp
        FirebasePerformanceManager.shared.getUserLocation(true)

        locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        locationManager.activityType = .other
        locationManager.distanceFilter = 100 
        locationManager.delegate = self
        let status = CLLocationManager.authorizationStatus()

        switch status {
        case .authorizedAlways:
            locationManager.startUpdatingLocation()
        case .authorizedWhenInUse:
            locationManager.requestAlwaysAuthorization()
            locationManager.startUpdatingLocation()
        case .restricted, .notDetermined:
            locationManager.requestAlwaysAuthorization()
        case .denied:
            showNoPermissionsAlert()
        }
    }

    func logOut() {
        locationManager.stopUpdatingLocation()
        isCallDidStartGetLocation = false
    }

}

// MARK: - Alerts

extension LocationManager {

    private func showNoPermissionsAlert() {
        guard let topViewController = UIApplication.topViewController() else { return }
        let alertController = UIAlertController(title: "No permission",
                                                message: "In order to work, app needs your location", preferredStyle: .alert)
        let openSettings = UIAlertAction(title: "Open settings", style: .default, handler: {
            (action) -> Void in
            guard let URL = Foundation.URL(string: UIApplicationOpenSettingsURLString) else { return }
            UIApplication.shared.open(URL, options: [:], completionHandler: nil)

        })
        alertController.addAction(openSettings)
        topViewController.present(alertController, animated: true, completion: nil)
    }

}

// MARK: - CLLocationManager Delegate

extension LocationManager: CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedWhenInUse, .authorizedAlways:
            locationManager.startUpdatingLocation()
        default: break
        }

        delegate?.didChangeAuthorization?(manager: manager, didChangeAuthorization: status)
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let lastLocation = locations.last else { return }
        let timeInterval = abs(lastLocation.timestamp.timeIntervalSinceNow)

        guard timeInterval < 60 else { return }

        currentLocation = lastLocation
        locationDatabaseManager.updateUserLocation(lastLocation)
        measureGetLocationTime()
        if !isCallDidStartGetLocation {
            isCallDidStartGetLocation = true
            delegate?.didStartGetLocation?()
        }
    }

}

// MARK: - Calculation

extension LocationManager {

    func calculateDistanceFromCurrentLocation(_ venueLocation: CLLocation) -> Double {
        guard let userLocation = locationManager.location else {
            return 0.0
        }
        let distance = userLocation.distance(from: venueLocation)
        let distanceMiles = distance / DistanceConvertor.metersPerMile //1609
        return distanceMiles.roundToPlaces(places: 1)
    }

}

// MARK: - Measuring functions

extension LocationManager {

    private func measureGetLocationTime() {
        FirebasePerformanceManager.shared.getUserLocation(false)
        let endTimestamp = Date().currentTimestamp
        let resultTimestamp = endTimestamp - startTimestamp
        BugfenderManager.getFirstUserLocation(resultTimestamp)
    }

}

【问题讨论】:

  • "我还以为你可以保存以前的位置并将更改与新位置进行比较,但我认为这是个坏主意。"是什么让您认为这是一个坏主意..?
  • @viz 例如,一个用户在 Free Way 上旅行,我们的应用程序在一分钟内运行约 8 次,我认为这些计算将使我们的应用程序成为最耗能的应用程序之一用户的设备。
  • 通过网络发送数据消耗的能量最多。比较两个值不会占用太多能量。或者你可以简单地忽略一些运行,少做比较。

标签: ios swift core-location cllocationmanager


【解决方案1】:

我更改了当前的LocationManager 并为此案例创建了两个新的经理。我测试了这个应用,经过我的修改,结果如下:我开了120-130公里,两段路在城市之间,应用花费了设备1%的费用,对我们来说这是一个可以接受的结果。 App 向服务器发送了 4 次更新用户位置的请求,条件如下:上一次更新后的位置耗时 2 小时,上一次与新位置之间的距离为 5 英里或以上。您可以在下面看到实现。

位置管理器

import Foundation
import CoreLocation

class LocationManager: NSObject {

    private override init() {
        super.init()
        manager.delegate = self
    }

    static let shared = LocationManager()

    private let manager = CLLocationManager()

    weak var delegate: LocationManagerDelegate?

    // MARK: - Enums


    enum DistanceValue: Int {
        case meters, miles
    }

    // MARK: - Flags

    private var isCallDidStartGetLocation = false

    // MARK: - Measuring properties

    private var startTimestamp = 0.0

    // MARK: - Open data

    var currentLocation: CLLocation?

    // MARK: - Managers 

    private let locationDatabaseManager = LocationDatabaseManager()

    // MARK: - Values

    private let metersPerMile = 1609.34

    func start() {
        // measuring data
        startTimestamp = Date().currentTimestamp
        FirebasePerformanceManager.shared.getUserLocation(true)

        manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
        manager.activityType = .other
        manager.desiredAccuracy = 45
        manager.distanceFilter = 100

        let status = CLLocationManager.authorizationStatus()

        switch status {
        case .authorizedAlways:
            if UIApplication.shared.applicationState != .background {
                manager.startUpdatingLocation()
            }

            manager.startMonitoringSignificantLocationChanges()
            manager.allowsBackgroundLocationUpdates = true
        case .authorizedWhenInUse:
            manager.requestAlwaysAuthorization()
            manager.startUpdatingLocation()
        case .restricted, .notDetermined:
            manager.requestAlwaysAuthorization()
        case .denied:
            showNoPermissionsAlert()
        }
    }

    func logOut() {
        manager.stopUpdatingLocation()
        isCallDidStartGetLocation = false
    }

}

// MARK: - Mode managing

extension LocationManager {

    open func enterBackground() {
        manager.stopUpdatingLocation()
        manager.startMonitoringSignificantLocationChanges()
    }

    open func enterForeground() {
        manager.startUpdatingLocation()
    }

}

// MARK: - Alerts

extension LocationManager {

    private func showNoPermissionsAlert() {
        guard let topViewController = UIApplication.topViewController() else { return }
        let alertController = UIAlertController(title: "No permission",
                                                message: "In order to work, app needs your location", preferredStyle: .alert)
        let openSettings = UIAlertAction(title: "Open settings", style: .default, handler: {
            (action) -> Void in
            guard let URL = Foundation.URL(string: UIApplicationOpenSettingsURLString) else { return }
            UIApplication.shared.open(URL, options: [:], completionHandler: nil)

        })
        alertController.addAction(openSettings)
        topViewController.present(alertController, animated: true, completion: nil)
    }

}

// MARK: - CLLocationManager Delegate

extension LocationManager: CLLocationManagerDelegate {

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        switch status {
        case .authorizedWhenInUse, .authorizedAlways:
            if UIApplication.shared.applicationState != .background {
                manager.startUpdatingLocation()
            }
        default: break
        }

        delegate?.didChangeAuthorization?(manager: manager, didChangeAuthorization: status)
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let lastLocation = locations.last else { return }

        let applicationState = UIApplication.shared.applicationState

        switch applicationState {
        case .active, .inactive:
            activeAppGetLocation(lastLocation)
        case .background:
            backgroundAppGetLocation(lastLocation)
        }
    }

}

// MARK: - Gettings location functions

extension LocationManager {

    private func activeAppGetLocation(_ location: CLLocation) {
        let timeInterval = abs(location.timestamp.timeIntervalSinceNow)

        guard timeInterval < 60 else { return }

        currentLocation = location
        locationDatabaseManager.updateUserLocation(location, state: .active)
        if !isCallDidStartGetLocation {
            measureGetLocationTime()
            isCallDidStartGetLocation = true
            delegate?.didStartGetLocation?()
        }
    }

    private func backgroundAppGetLocation(_ location: CLLocation) {
        let locationBackgroundManager = LocationBackgroundManager()
        locationBackgroundManager.updateLocationInBackgroundIfNeeded(location)
    }

}

// MARK: - Calculation

extension LocationManager {

    func calculateDistanceBetweenLocations(_ firstLocation: CLLocation, secondLocation: CLLocation, valueType: DistanceValue) -> Double {
        let meters = firstLocation.distance(from: secondLocation)
        switch valueType {
        case .meters:
            return meters
        case .miles:
            let miles = meters / DistanceConvertor.metersPerMile
            return miles
        }
    }

    /// In miles
    func calculateDistanceFromCurrentLocation(_ venueLocation: CLLocation) -> Double {
        guard let userLocation = manager.location else {
            return 0.0
        }
        let distance = userLocation.distance(from: venueLocation)
        let distanceMiles = distance / DistanceConvertor.metersPerMile //1609
        return distanceMiles.roundToPlaces(places: 1)
    }

}

// MARK: - Measuring functions

extension LocationManager {

    private func measureGetLocationTime() {
        FirebasePerformanceManager.shared.getUserLocation(false)
        let endTimestamp = Date().currentTimestamp
        let resultTimestamp = endTimestamp - startTimestamp
        BugfenderManager.getFirstUserLocation(resultTimestamp)
    }

}

LocationBackgroundManager

import Foundation
import CoreLocation
import SwiftDate

class LocationBackgroundManager {

    private var backgroundLocationUpdateTimestamp: Double {
        get {
            return UserDefaults.standard.double(forKey: "backgroundLocationUpdateTimestamp")
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "backgroundLocationUpdateTimestamp")
            UserDefaults.standard.synchronize()
        }
    }

    // MARK: - Managers

    private lazy var locationStorageManager: LocationStorageManager = {
        let locationStorageManager = LocationStorageManager()
        return locationStorageManager
    }()

    open func updateLocationInBackgroundIfNeeded(_ location: CLLocation) {
        if backgroundLocationUpdateTimestamp != 0 {
            let currentLocationDate = location.timestamp

            let previousDate = Date(timeIntervalSince1970: backgroundLocationUpdateTimestamp)

            guard let hours = (currentLocationDate - previousDate).in(.hour) else { return }

            guard hours >= 2 else { return }

            if let previousLocationRealm = locationStorageManager.getCurrentUserPreviousLocation() {
                let previousLocation = CLLocation(latitude: previousLocationRealm.latitude, longitude: previousLocationRealm.longitude)
                let distance = LocationManager.shared.calculateDistanceBetweenLocations(location, secondLocation: previousLocation, valueType: .miles)
                guard distance >= 5 else { return }

                updateLocation(location)
            } else {
                updateLocation(location)
            }
        } else {
           updateLocation(location)
        }
    }

    private func updateLocation(_ location: CLLocation) {
        let locationDatabaseManager = LocationDatabaseManager()
        locationDatabaseManager.updateUserLocation(location, state: .background)
        backgroundLocationUpdateTimestamp = location.timestamp.currentTimestamp
        locationStorageManager.saveLocation(location)
    }

}

位置存储管理器

import Foundation
import CoreLocation
import RealmSwift

class LocationStorageManager {

    func saveLocation(_ location: CLLocation) {
        guard let currentUserID = RealmManager().getCurrentUser()?.id else { return }
        let altitude = location.altitude
        let latitude = location.coordinate.latitude
        let longitude = location.coordinate.longitude

        let locationRealm = LocationRealm(altitude: altitude, latitude: latitude, longitude: longitude, userID: currentUserID)

        do {
            let realm = try Realm()
            try realm.write {
                realm.add(locationRealm, update: true)
            }
        } catch {
            debugPrint(error)
            let funcName = #function
            let file = #file
            BugfenderManager.reportError(funcName, fileName: file, error: error)
        }
    }

    func getCurrentUserPreviousLocation() -> LocationRealm? {
        guard let currentUserID = RealmManager().getCurrentUser()?.id else { return nil }
        do {
            let realm = try Realm()
            let previousLocation = realm.objects(LocationRealm.self).filter("userID == %@", currentUserID).first
            return previousLocation
        } catch {
            debugPrint(error)
            let funcName = #function
            let file = #file
            BugfenderManager.reportError(funcName, fileName: file, error: error)
            return nil
        }
    }

}

【讨论】:

    【解决方案2】:

    根据Apple Docs

    只要设备从之前的通知移动 500 米或更远,应用程序就会收到通知。它不应期望通知的频率超过每五分钟一次。如果设备能够从网络中检索数据,则位置管理器更有可能及时发送通知。

    startMonitoringSignificantLocationChanges() 是最不准确的位置监控方式,并且无法配置调用频率,因为它会在信号塔转换时触发。因此,它可以在塔(城市)更密集的区域更频繁地触发。请参阅this thread 了解更多信息。

    【讨论】:

    • 是的,你是对的,但这是针对 startUpdatingLocation 的,在使用应用程序时,我们需要我指出的精度,如果应用程序不在设备的内存中,那么我们需要 5 英里的精度。来自功能描述:“此属性仅与标准位置服务结合使用,在监控重大位置变化时不使用。”
    • 我的错误,查看新答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-01
    • 2011-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多