iOS CLLocationManager in a separate class
Asked Answered
J

5

17

I have multiple view controllers that need to get the user's location, so I wanted to create a separate class that the ViewControllers can call to get the user's latest location.

locationManager:didUpdateToLocation:fromLocation returns void. How do I pass the latitude and longitude data back to my ViewControllers as soon as the user's latitude and longitude is calculated?

I could try writing getters and setters in my locationManaging class, but if I do that, how do I know when to call the latitude getter and longitude getter methods from my ViewController class? How do I hold the ViewController's main thread to wait for the latitude and longitude values from the locationManaging class?

Thanks!

Joli answered 16/7, 2012 at 22:4 Comment(1)
locationManager:didUpdateToLocation:fromLocation is a delegate method. The location manager uses this method to inform your object that the location was updated with a new value - it's up to you to implement this method to do something with the value.Heim
V
16

Create a singleton class which has a latitude and longitude properties, startLocating and endLocating. In the class, create a CLLocationManager instance, and set its delegate to be the singleton. In startLocating and endLocating, call the appropriate methods of the CLLocationManager instance. Make the delegate methods update the latitude and longitude properties. In other ViewControllers, read this singleton's latitude and longitude properties.

To know when to read those properties from another ViewController, set an observer on these properties (see the NSKeyValueObserving Protocol Reference

Before doing this, look up the Internets for existing code.

After doing this, upload it to GitHub with a permissive license.

Vesica answered 16/7, 2012 at 22:18 Comment(4)
thanks! my follow up question is.. how do i know when my ViewController should call the singleton's latitude and longitude properties? In other words, how do I lock my ViewController's main thread to wait for valid lat long values in my singletonJoli
It's in the second paragraph (might've added it while you were reading my initial post)Vesica
you access the singletons properties inside the observers change notification methodTrossachs
I tried this today. But my code was not updating position until the class was declared singleton. But why ? Why we need to have a singleton. I did not even create multiple object. I was testing with single object.Emulate
G
23

As user1071136 said, a singleton location manager is probably what you want. Create a class, a subclass of NSObject, with just one property, a CLLocationManager.

LocationManagerSingleton.h:

#import <MapKit/MapKit.h>

@interface LocationManagerSingleton : NSObject <CLLocationManagerDelegate>

@property (nonatomic, strong) CLLocationManager* locationManager;

+ (LocationManagerSingleton*)sharedSingleton;

@end

LocationManagerSingleton.m:

#import "LocationManagerSingleton.h"

@implementation LocationManagerSingleton

@synthesize locationManager;

- (id)init {
    self = [super init];

    if(self) {
        self.locationManager = [CLLocationManager new];
        [self.locationManager setDelegate:self];
        [self.locationManager setDistanceFilter:kCLDistanceFilterNone];
        [self.locationManager setHeadingFilter:kCLHeadingFilterNone];
        [self.locationManager startUpdatingLocation];
        //do any more customization to your location manager
    }

    return self;
}    

+ (LocationManagerSingleton*)sharedSingleton {
    static LocationManagerSingleton* sharedSingleton;
    if(!sharedSingleton) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedSingleton = [LocationManagerSingleton new];
        }
    }

    return sharedSingleton;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    //handle your location updates here
}

- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
    //handle your heading updates here- I would suggest only handling the nth update, because they
    //come in fast and furious and it takes a lot of processing power to handle all of them
}

@end

To get the most recently received location, simply use [LocationManagerSingleton sharedSingleton].locationManager.location. It might take a few seconds to warm up the GPS to get accurate locations.

Goldsberry answered 16/7, 2012 at 22:29 Comment(3)
Might want to update your @synchronized block to a dispatch_once.Mark
@Rickay will this return result in case of Airplane mode?Boong
If you check this answer: (#7376794) you will find that in this case CLLocationManager returns the most recently acquired location, but not new location information.Goldsberry
V
16

Create a singleton class which has a latitude and longitude properties, startLocating and endLocating. In the class, create a CLLocationManager instance, and set its delegate to be the singleton. In startLocating and endLocating, call the appropriate methods of the CLLocationManager instance. Make the delegate methods update the latitude and longitude properties. In other ViewControllers, read this singleton's latitude and longitude properties.

To know when to read those properties from another ViewController, set an observer on these properties (see the NSKeyValueObserving Protocol Reference

Before doing this, look up the Internets for existing code.

After doing this, upload it to GitHub with a permissive license.

Vesica answered 16/7, 2012 at 22:18 Comment(4)
thanks! my follow up question is.. how do i know when my ViewController should call the singleton's latitude and longitude properties? In other words, how do I lock my ViewController's main thread to wait for valid lat long values in my singletonJoli
It's in the second paragraph (might've added it while you were reading my initial post)Vesica
you access the singletons properties inside the observers change notification methodTrossachs
I tried this today. But my code was not updating position until the class was declared singleton. But why ? Why we need to have a singleton. I did not even create multiple object. I was testing with single object.Emulate
T
8

Based on the above answers, here is what I did and you can find the complete example on github https://github.com/irfanlone/CLLocationManager-Singleton-Swift

Just import this file in your project, then you can either choose to implement the LocationUpdateProtocol or listen to notification for location updates

import MapKit

protocol LocationUpdateProtocol {
    func locationDidUpdateToLocation(location : CLLocation)
}

/// Notification on update of location. UserInfo contains CLLocation for key "location"
let kLocationDidChangeNotification = "LocationDidChangeNotification"

class UserLocationManager: NSObject, CLLocationManagerDelegate {

    static let SharedManager = UserLocationManager()

    private var locationManager = CLLocationManager()

    var currentLocation : CLLocation?

    var delegate : LocationUpdateProtocol!

    private override init () {
        super.init()
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.distanceFilter = kCLLocationAccuracyHundredMeters
        locationManager.requestAlwaysAuthorization()
        self.locationManager.startUpdatingLocation()
    }

    // MARK: - CLLocationManagerDelegate

    func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
        currentLocation = newLocation
        let userInfo : NSDictionary = ["location" : currentLocation!]

        dispatch_async(dispatch_get_main_queue()) { () -> Void in
            self.delegate.locationDidUpdateToLocation(self.currentLocation!)
            NSNotificationCenter.defaultCenter().postNotificationName(kLocationDidChangeNotification, object: self, userInfo: userInfo as [NSObject : AnyObject])
        }
    }

}

Usage:

class ViewController: UIViewController, LocationUpdateProtocol {

    var currentLocation : CLLocation!

    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "locationUpdateNotification:", name: kLocationDidChangeNotification, object: nil)

        let LocationMgr = UserLocationManager.SharedManager
        LocationMgr.delegate = self

    }

    // MARK: - Notifications

    func locationUpdateNotification(notification: NSNotification) {
        let userinfo = notification.userInfo
        self.currentLocation = userinfo!["location"] as! CLLocation
        print("Latitude : \(self.currentLocation.coordinate.latitude)")
        print("Longitude : \(self.currentLocation.coordinate.longitude)")

    }

    // MARK: - LocationUpdateProtocol

    func locationDidUpdateToLocation(location: CLLocation) {
        currentLocation = location
        print("Latitude : \(self.currentLocation.coordinate.latitude)")
        print("Longitude : \(self.currentLocation.coordinate.longitude)")
    }

}
Trillby answered 30/3, 2016 at 20:0 Comment(0)
H
3

Here's what I did when implementing a location manger singleton in swift. It's based on user1071136's strategy, as well as this swift pattern.

//
//  UserLocationManager.swift
//
//  Use: call SharedUserLocation.currentLocation2d from any class


import MapKit

class UserLocation: NSObject, CLLocationManagerDelegate {

    var locationManager = CLLocationManager()

    // You can access the lat and long by calling:
    // currentLocation2d.latitude, etc

    var currentLocation2d:CLLocationCoordinate2D?


    class var manager: UserLocation {
        return SharedUserLocation
    }

    init () {
        super.init()
        if self.locationManager.respondsToSelector(Selector("requestAlwaysAuthorization")) {
            self.locationManager.requestWhenInUseAuthorization()
        }
        self.locationManager.delegate = self
        self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
        self.locationManager.distanceFilter = 50
        self.locationManager.startUpdatingLocation()
    }

    func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
        self.currentLocation2d = manager.location.coordinate

    }
}

let SharedUserLocation = UserLocation()
Husted answered 16/7, 2014 at 20:17 Comment(5)
Wear oven mitts when using a device running this code.Heim
I'm currently trying to use your class, but the app never asks for authorization nor i get a location. What do i need to do to use the class?Sorehead
never forget the info.plst: #24051133Sorehead
@Heim can you explain your comment.. are you saying that this is a bad solution - for battery? I am thinking of a pattern to implement CLLocation for multiple views as well.Yogini
@Yogini this class is setting the location accuracy and distance filter to reject any location data with a precision of less than 50 meters. That and constantly turning location updates on/off like some other answers is not healthy for the device and does not help the application.Heim
D
0

In the process of learning to obtain user location in a singleton class, I downloaded this code https://github.com/irfanlone/CLLocationManager-Singleton-Swift. After foddleing with it to make in run on Xcode8.3.3 Swift 3, I cannot get any output in the debug console. In fact I can't even get this location autherization dlalog to display. I suspect the data from the singleton is not passed to the view controller, but I cannot fine the solution, can you see what is wrong? Thanks.

The corrected code for Swift 3 is:

//The singleton:
import MapKit

protocol LocationUpdateProtocol {
func locationDidUpdateToLocation(_ location : CLLocation)
}

let kLocationDidChangeNotification = "LocationDidChangeNotification"

class UserLocationManager: NSObject, CLLocationManagerDelegate {

static let SharedManager = UserLocationManager()

fileprivate var locationManager = CLLocationManager()

var currentLocation : CLLocation?

var delegate : LocationUpdateProtocol!

fileprivate override init () {
    super.init()
    self.locationManager.delegate = self
    self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
    self.locationManager.distanceFilter = kCLLocationAccuracyHundredMeters
    locationManager.requestAlwaysAuthorization()
    self.locationManager.startUpdatingLocation()
}

// MARK: - CLLocationManagerDelegate
func locationManager(manager: CLLocationManager,didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
    currentLocation = newLocation
    let userInfo : NSDictionary = ["location" : currentLocation!]


    DispatchQueue.main.async() { () -> Void in
        self.delegate.locationDidUpdateToLocation(self.currentLocation!)
        NotificationCenter.default.post(name: Notification.Name(kLocationDidChangeNotification), object: self, userInfo: userInfo as [NSObject : AnyObject])
    }
}

}



  // The ViewController
    import UIKit
    import CoreLocation

 class ViewController: UIViewController, LocationUpdateProtocol {

var currentLocation : CLLocation!

override func viewDidLoad() {
    super.viewDidLoad()

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.locationUpdateNotification(_:)), name: NSNotification.Name(rawValue: kLocationDidChangeNotification), object: nil)

    let LocationMgr = UserLocationManager.SharedManager
    LocationMgr.delegate = self

}

// MARK: - Notifications

func locationUpdateNotification(_ notification: Notification) {
    let userinfo = notification.userInfo
    self.currentLocation = userinfo!["location"] as! CLLocation
    print("Latitude : \(self.currentLocation.coordinate.latitude)")
    print("Longitude : \(self.currentLocation.coordinate.longitude)")

}

// MARK: - LocationUpdateProtocol

func locationDidUpdateToLocation(_ location: CLLocation) {
    currentLocation = location
    print("Latitude : \(self.currentLocation.coordinate.latitude)")
    print("Longitude : \(self.currentLocation.coordinate.longitude)")
}


}
Depressant answered 12/7, 2017 at 11:27 Comment(2)
You should consider asking a question yourself rather than posting your question as an answer.Bartholemy
Thanks for nothing. I f I start a new question with a similar title. you guy would brand it as duplicated!Depressant

© 2022 - 2024 — McMap. All rights reserved.