How do I get a background location update every n minutes in my iOS application?

后端 未结 14 1151
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-22 02:00

I\'m looking for a way to get a background location update every n minutes in my iOS application. I\'m using iOS 4.3 and the solution should work for non-jailbroken iPhones

相关标签:
14条回答
  • 2020-11-22 02:05

    I found a solution to implement this with the help of the Apple Developer Forums:

    • Specify location background mode
    • Create an NSTimer in the background with UIApplication:beginBackgroundTaskWithExpirationHandler:
    • When n is smaller than UIApplication:backgroundTimeRemaining it will work just fine. When n is larger, the location manager should be enabled (and disabled) again before there is no time remaining to avoid the background task being killed.

    This works because location is one of the three allowed types of background execution.

    Note: I lost some time by testing this in the simulator where it doesn't work. However, it works fine on my phone.

    0 讨论(0)
  • 2020-11-22 02:05

    There is a cocoapod APScheduledLocationManager that allows to get background location updates every n seconds with desired location accuracy.

    let manager = APScheduledLocationManager(delegate: self)
    manager.startUpdatingLocation(interval: 170, acceptableLocationAccuracy: 100)
    

    The repository also contains an example app written in Swift 3.

    0 讨论(0)
  • 2020-11-22 02:06

    Now that iOS6 is out the best way to have a forever running location services is...

    - (void)applicationWillResignActive:(UIApplication *)application
    {
    /*
     Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
     Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
     */
    
    NSLog(@"to background");
    
    app.isInBackground = TRUE;
    
    UIApplication *app = [UIApplication sharedApplication];
    
    // Request permission to run in the background. Provide an
    // expiration handler in case the task runs long.
    NSAssert(bgTask == UIBackgroundTaskInvalid, nil);
    
    bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
        // Synchronize the cleanup call on the main thread in case
        // the task actually finishes at around the same time.
        dispatch_async(dispatch_get_main_queue(), ^{
    
            if (bgTask != UIBackgroundTaskInvalid)
            {
                [app endBackgroundTask:bgTask];
                bgTask = UIBackgroundTaskInvalid;
            }
        });
    }];
    
    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        // Do the work associated with the task.
    
        locationManager.distanceFilter = 100;
        locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters;
        [locationManager startMonitoringSignificantLocationChanges];
        [locationManager startUpdatingLocation];
    
        NSLog(@"App staus: applicationDidEnterBackground");
        // Synchronize the cleanup call on the main thread in case
        // the expiration handler is fired at the same time.
        dispatch_async(dispatch_get_main_queue(), ^{
            if (bgTask != UIBackgroundTaskInvalid)
            {
                [app endBackgroundTask:bgTask];
                bgTask = UIBackgroundTaskInvalid;
            }
        });
    });
    
    NSLog(@"backgroundTimeRemaining: %.0f", [[UIApplication sharedApplication] backgroundTimeRemaining]);
    
    }
    

    Just tested it like that:

    I started the app, go background and move in the car by some minutes. Then I go home for 1 hour and start moving again (without opening again the app). Locations started again. Then stopped for two hours and started again. Everything ok again...

    DO NOT FORGET USING the new location services in iOS6

    - (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
    {   
        CLLocation *loc = [locations lastObject];
    
        // Lat/Lon
        float latitudeMe = loc.coordinate.latitude;
        float longitudeMe = loc.coordinate.longitude;
    }
    
    0 讨论(0)
  • 2020-11-22 02:07

    Attached is a Swift solution based in:

    Define App registers for location updates in the info.plist

    Keep the locationManager running all the time

    Switch kCLLocationAccuracy between BestForNavigation (for 5 secs to get the location) and ThreeKilometers for the rest of the wait period to avoid battery drainage

    This example updates location every 1 min in Foreground and every 15 mins in Background.

    The example works fine with Xcode 6 Beta 6, running in a iOS 7 device.

    In the App Delegate (mapView is an Optional pointing to the mapView Controller)

    func applicationDidBecomeActive(application: UIApplication!) {
        if appLaunched! == false { // Reference to mapView used to limit one location update per timer cycle
            appLaunched = true
            var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
            var window = appDelegate.window
            var tabBar = window?.rootViewController as UITabBarController
            var navCon = tabBar.viewControllers[0] as UINavigationController
            mapView = navCon.topViewController as? MapViewController
        }
        self.startInitialPeriodWithTimeInterval(60.0)
    }
    
    func applicationDidEnterBackground(application: UIApplication!) {
        self.startInitialPeriodWithTimeInterval(15 * 60.0)
    }
    
    func startInitialPeriodWithTimeInterval(timeInterval: NSTimeInterval) {
        timer?.invalidate() // reset timer
        locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getFirstLocationUpdate:"), userInfo: timeInterval, repeats: false)
    }
    
    func getFirstLocationUpdate(sender: NSTimer) {
        let timeInterval = sender.userInfo as Double
        timer?.invalidate()
        mapView?.canReportLocation = true
        timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval, target: self, selector: Selector("waitForTimer:"), userInfo: timeInterval, repeats: true)
    }
    
    func waitForTimer(sender: NSTimer) {
        let time = sender.userInfo as Double
        locationManager?.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        finalTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("getLocationUpdate"), userInfo: nil, repeats: false)
    }
    
    func getLocationUpdate() {
        finalTimer?.invalidate()
        mapView?.canReportLocation = true
    }
    

    In the mapView (locationManager points to the object in the AppDelegate)

    override func viewDidLoad() {
        super.viewDidLoad()
        var appDelegate = UIApplication.sharedApplication().delegate! as AppDelegate
        locationManager = appDelegate.locationManager!
        locationManager.delegate = self
        canReportLocation = true
    }
    
      func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
            if canReportLocation! {
                canReportLocation = false
                locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
            } else {
                //println("Ignore location update")
            }
        }
    
    0 讨论(0)
  • 2020-11-22 02:11

    To someone else having nightmare figure out this one. I have a simple solution.

    1. look this example from raywenderlich.com-> have sample code, this works perfectly, but unfortunately no timer during background location. this will run indefinitely.
    2. Add timer by using :

      -(void)applicationDidEnterBackground {
      [self.locationManager stopUpdatingLocation];
      
      UIApplication*    app = [UIApplication sharedApplication];
      
      bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
          [app endBackgroundTask:bgTask];
          bgTask = UIBackgroundTaskInvalid;
      }];
      
       self.timer = [NSTimer scheduledTimerWithTimeInterval:intervalBackgroundUpdate
                                                    target:self.locationManager
                                                  selector:@selector(startUpdatingLocation)
                                                  userInfo:nil
                                                   repeats:YES];
      
      }
      
    3. Just don't forget to add "App registers for location updates" in info.plist.

    0 讨论(0)
  • 2020-11-22 02:11

    Here is what I use:

    import Foundation
    import CoreLocation
    import UIKit
    
    class BackgroundLocationManager :NSObject, CLLocationManagerDelegate {
    
        static let instance = BackgroundLocationManager()
        static let BACKGROUND_TIMER = 150.0 // restart location manager every 150 seconds
        static let UPDATE_SERVER_INTERVAL = 60 * 60 // 1 hour - once every 1 hour send location to server
    
        let locationManager = CLLocationManager()
        var timer:NSTimer?
        var currentBgTaskId : UIBackgroundTaskIdentifier?
        var lastLocationDate : NSDate = NSDate()
    
        private override init(){
            super.init()
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
            locationManager.activityType = .Other;
            locationManager.distanceFilter = kCLDistanceFilterNone;
            if #available(iOS 9, *){
                locationManager.allowsBackgroundLocationUpdates = true
            }
    
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.applicationEnterBackground), name: UIApplicationDidEnterBackgroundNotification, object: nil)
        }
    
        func applicationEnterBackground(){
            FileLogger.log("applicationEnterBackground")
            start()
        }
    
        func start(){
            if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedAlways){
                if #available(iOS 9, *){
                    locationManager.requestLocation()
                } else {
                    locationManager.startUpdatingLocation()
                }
            } else {
                    locationManager.requestAlwaysAuthorization()
            }
        }
        func restart (){
            timer?.invalidate()
            timer = nil
            start()
        }
    
        func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
            switch status {
            case CLAuthorizationStatus.Restricted:
                //log("Restricted Access to location")
            case CLAuthorizationStatus.Denied:
                //log("User denied access to location")
            case CLAuthorizationStatus.NotDetermined:
                //log("Status not determined")
            default:
                //log("startUpdatintLocation")
                if #available(iOS 9, *){
                    locationManager.requestLocation()
                } else {
                    locationManager.startUpdatingLocation()
                }
            }
        }
        func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    
            if(timer==nil){
                // The locations array is sorted in chronologically ascending order, so the
                // last element is the most recent
                guard let location = locations.last else {return}
    
                beginNewBackgroundTask()
                locationManager.stopUpdatingLocation()
                let now = NSDate()
                if(isItTime(now)){
                    //TODO: Every n minutes do whatever you want with the new location. Like for example sendLocationToServer(location, now:now)
                }
            }
        }
    
        func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
            CrashReporter.recordError(error)
    
            beginNewBackgroundTask()
            locationManager.stopUpdatingLocation()
        }
    
        func isItTime(now:NSDate) -> Bool {
            let timePast = now.timeIntervalSinceDate(lastLocationDate)
            let intervalExceeded = Int(timePast) > BackgroundLocationManager.UPDATE_SERVER_INTERVAL
            return intervalExceeded;
        }
    
        func sendLocationToServer(location:CLLocation, now:NSDate){
            //TODO
        }
    
        func beginNewBackgroundTask(){
            var previousTaskId = currentBgTaskId;
            currentBgTaskId = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
                FileLogger.log("task expired: ")
            })
            if let taskId = previousTaskId{
                UIApplication.sharedApplication().endBackgroundTask(taskId)
                previousTaskId = UIBackgroundTaskInvalid
            }
    
            timer = NSTimer.scheduledTimerWithTimeInterval(BackgroundLocationManager.BACKGROUND_TIMER, target: self, selector: #selector(self.restart),userInfo: nil, repeats: false)
        }
    }
    

    I start the tracking in AppDelegate like that:

    BackgroundLocationManager.instance.start()
    
    0 讨论(0)
提交回复
热议问题