跟踪某个距离iOS的位置变化

时间:2018-01-17 19:22:51

标签: ios swift core-location cllocationmanager

我有一项任务是在之后跟踪用户在后台的位置,如果它的位置已经变为超过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)
    }

}

2 个答案:

答案 0 :(得分:3)

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

的LocationManager

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)
    }

}

LocationStorageManager

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
        }
    }

}

答案 1 :(得分:1)

根据Apple Docs

  

只要设备从之前的通知移动500米或更长时间,应用就会收到通知。它不应该比每五分钟更频繁地预期通知。如果设备能够从网络中检索数据,则位置管理员更有可能及时发送通知。

startMonitoringSignificantLocationChanges()是监控位置的最不准确的方法,并且无法配置在单元塔转换时触发它的频率。因此,它可以更频繁地触发位于塔(城市)更密集的区域。有关详细信息,请参阅this thread