onurgenes
onurgenes

Reputation: 137

Background Location Updates

I am building an app with location services.

I am using users current location to get objects around user. Which is currently working perfectly. The only problem is, I want to create local notifications for user with "signficantLocationChanges" on background but when app launches from AppDelegate with applicationDidFinishLaunching(_:) function, launchOptions object is nil.

I want to get background updates and make an HTTP API request and depending on response, I will create local notification.

Here is my AppDelegate class:

import UIKit
import UserNotifications
import CoreLocation

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var locationManager: LocationManager?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // Checking this because if the app is started for location updates,
        // no need to setup app for UI
        if let _ = launchOptions?[.location] {
            locationManager = LocationManager()
            locationManager?.delegate = self
            locationManager?.getCurrentLocation()
            return true
        }

        attemptToRegisterForNotifications(application: application)

        if #available(iOS 13, *) { } else {
            app.start()
        }

        return true
    }

    // MARK: UISceneSession Lifecycle
    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
    }
}

extension AppDelegate: LocatableOutputProtocol {
    func didGetCurrentLocation(latitude: Double, longitude: Double) {
        UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { (settings) in
            if settings.authorizationStatus == .authorized {
                let content = UNMutableNotificationContent()
                content.title = "\(Date().timeIntervalSince1970)"

                let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

                let request = UNNotificationRequest(identifier: "\(Date().timeIntervalSince1970)", content: content, trigger: trigger)

                UNUserNotificationCenter.current().add(request) { _ in

                }
            }
        })
    }

    func failedGetCurrentLocation(error: Error) {
        print(error)
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {

    private func attemptToRegisterForNotifications(application: UIApplication) {
        UNUserNotificationCenter.current().delegate = self

        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: { granted, error in
            if let error = error {
                print("failed to get auth", error)
                return
            }
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            } else {
                print("NO AVAIL FOR NOTIFS")
            }
        })
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler(.alert)
    }
}

Also I have an custom LocationManager class:

import CoreLocation

final class LocationManager: NSObject, Locatable {
    weak var delegate: LocatableOutputProtocol?

    var locationManager: CLLocationManager

    override init() {
        locationManager = CLLocationManager()
        super.init()

        let authStatus = CLLocationManager.authorizationStatus()
        if CLLocationManager.locationServicesEnabled() {
            if (authStatus == .authorizedAlways || authStatus == .authorizedWhenInUse) {
                locationManager.delegate = self
                locationManager.startUpdatingLocation()
                locationManager.startMonitoringSignificantLocationChanges()
                locationManager.allowsBackgroundLocationUpdates = true
                locationManager.desiredAccuracy = kCLLocationAccuracyBest
            } else {
                locationManager.requestAlwaysAuthorization()
                print("we dont have permission")
            }
        } else {

        }
    }

    func getCurrentLocation() {
        locationManager.startUpdatingLocation()
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let coordinates = locations.first?.coordinate {
            locationManager.stopUpdatingLocation()
            self.delegate?.didGetCurrentLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        self.delegate?.failedGetCurrentLocation(error: error)
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        print("status changed")
        if (status == .authorizedAlways || status == .authorizedWhenInUse) {
            print("we got permission")
        } else {
            print("nope")
        }
    }
}

I am trying to debug this with creating new schema on Xcode with Wait for executable to be launched and using Freeway Ride on Debug menu of Simulator. Also tested with real device.

What am I missing?

Upvotes: 1

Views: 3284

Answers (2)

rubik
rubik

Reputation: 602

@onurgenes, If you add a real code from your project, first of all, how you can start any location updates from here?

    if let _ = launchOptions?[.location] {
        locationManager = LocationManager()
        locationManager?.delegate = self
        locationManager?.getCurrentLocation()
        return true
    }

When app start at for the first time launchOptions will be nil, and your LocationManager() even not started, so your any location monitoring and updates will not work (maybe you have the some code at app.start() but now it looks like an error).

The second thing - at your sample, you are using bought of locating monitoring:

    locationManager.startUpdatingLocation()
    locationManager.startMonitoringSignificantLocationChanges()

so here your location manager handles only significantLocationChanges(). If you want to use both of them - you should toggle it (at didBecomeActiveNotification and didEnterBackgroundNotification) or create different instances of the location manager as Apple recommend.

The third - your problem. Let's look for this part more detailed:

locationManager = LocationManager()
locationManager?.delegate = self
locationManager?.getCurrentLocation()

As I mention up - at LocationManager() you start monitoring location:

locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()

and this is what you need - significant location changes. But after you call getCurrentLocation() with locationManager.startUpdatingLocation() so you 'rewrite' your monitoring, and that's the reason why you did not get any updates from it.

Also, take in mind:

  1. Significant locations deliver updates only when there has been a significant change in the device’s location, (experimentally established 500 meters or more)
  2. Significant locations very inaccurate (for me sometimes it up to 900 meters). Very often significant location use just for wake up an application and restart location service.
  3. After your app wake up from location changes notification, you are given a small amount of time (around 10 seconds), so if you need more time to send location to the server you should request more time with beginBackgroundTask(withName:expirationHandler:)

Hope my answer was helpful. Happy coding!

Upvotes: 2

Ram
Ram

Reputation: 804

@onurgenes You need to use NotificationCenter on your location manager initialize section like below,

import CoreLocation

    final class LocationManager: NSObject, Locatable {
        weak var delegate: LocatableOutputProtocol?

        var locationManager: CLLocationManager

        override init() {

            NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackgroundActive(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)

            NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForegroundActive(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)

            locationManager = CLLocationManager()
            super.init()

            let authStatus = CLLocationManager.authorizationStatus()
            if CLLocationManager.locationServicesEnabled() {
                if (authStatus == .authorizedAlways || authStatus == .authorizedWhenInUse) {
                    locationManager.delegate = self
                    locationManager.startUpdatingLocation()
                    locationManager.startMonitoringSignificantLocationChanges()
                    locationManager.allowsBackgroundLocationUpdates = true
                    locationManager.desiredAccuracy = kCLLocationAccuracyBest
                } else {
                    locationManager.requestAlwaysAuthorization()
                    print("we dont have permission")
                }
            } else {

            }
        }

        func getCurrentLocation() {
            locationManager.startUpdatingLocation()
        }
    }

    extension LocationManager: CLLocationManagerDelegate {
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            if let coordinates = locations.first?.coordinate {
                locationManager.stopUpdatingLocation()
                self.delegate?.didGetCurrentLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
            }
        }

        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            self.delegate?.failedGetCurrentLocation(error: error)
            self.locationManager.stopMonitoringSignificantLocationChanges()
        }

        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            print("status changed")
            if (status == .authorizedAlways || status == .authorizedWhenInUse) {
                print("we got permission")
            self.locationManager.startMonitoringSignificantLocationChanges()
            } else {
                print("nope")
            }
        }
    }


        @objc private func applicationDidEnterBackgroundActive (_ notification: Notification) {
                self.locationManager.startMonitoringSignificantLocationChanges()
        }

        @objc private func applicationWillEnterForegroundActive (_ notification: Notification) {
            self.locationManager.startUpdatingLocation()
        }

You need to use this LocationManager class on your AppDelegate class for it's initialize. I hope it will help to achieve your required output.

Upvotes: 2

Related Questions