1

I'm pretty new in programming and this is my first app, so sorry if the approach is very shabby.

I created a helper method to get the user location, because I need to call it from different view controllers so I thought this was a cleaner way to do it. But I don't know why is not working now (no errors, it just show the general view of Europe). But when it was inside the view controller it worked perfectly fine.

I got this new approach from the course I'm doing and I've been researching in many sources. I've also checked this question but I didn't find any solution yet.

Here is the method I created in the GMSClient file. It will get the user location, but if the user disables this option, it will show the default position (centred in Berlin):

    extension GMSClient: CLLocationManagerDelegate {

    //MARK: Initial Location: Berlin

    func setDefaultInitialLocation(_ map: GMSMapView) {
        let camera = GMSCameraPosition.camera(withLatitude: 52.520736, longitude: 13.409423, zoom: 8)
        map.camera = camera

        let initialLocation = CLLocationCoordinate2DMake(52.520736, 13.409423)
        let marker = GMSMarker(position: initialLocation)
        marker.title = "Berlin"
        marker.map = map

    }

    //MARK: Get user location

    func getUserLocation(_ map: GMSMapView,_ locationManager: CLLocationManager) {

        var userLocation: String?

        locationManager.requestWhenInUseAuthorization()

        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

            if status == .authorizedWhenInUse {
                locationManager.startUpdatingLocation()

                map.isMyLocationEnabled = true
                map.settings.myLocationButton = true

            } else {
                setDefaultInitialLocation(map)
            }
        }

        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

            if let location = locations.first {

                map.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)

                locationManager.stopUpdatingLocation()

                //Store User Location
                userLocation = "\(location.coordinate.latitude), \(location.coordinate.longitude)"
                print("userLocation is: \((userLocation) ?? "No user Location")")

            }
        }
    }
}

This file has also this singelton:

// MARK: Shared Instance

    class func sharedInstance() -> GMSClient {
        struct Singleton {
            static var sharedInstance = GMSClient()
        }
        return Singleton.sharedInstance
    }

And then I call it in my view controller like this:

class MapViewController: UIViewController, CLLocationManagerDelegate {

    // MARK: Outlets

    @IBOutlet weak var mapView: GMSMapView!


    // MARK: Properties

    let locationManager = CLLocationManager()
    var userLocation: String?
    let locationManagerDelegate = GMSClient()


    // MARK: Life Cycle

    override func viewDidLoad() {

        super.viewDidLoad()

        self.locationManager.delegate = locationManagerDelegate

        GMSClient.sharedInstance().getUserLocation(mapView, locationManager)

    }

Anyone has an idea of what can be wrong?

Thanks!

celiux
  • 613
  • 6
  • 10
  • 1
    You have nested your delegate functions inside your `getUserLocation` function. This won't work. Move them out. – Paulw11 Jul 08 '17 at 10:04
  • I tried that before and did it worked, the thing is I would like to create later a completion handler in order to get the user location for a google map web search request. Something like... `func getUserLocation(_ map: GMSMapView,_ locationManager: CLLocationManager, completionHandlerForUserLocation: @escaping (_ userLocation: String?, _ error: NSError?) -> Void)) {` so I didn't know how to do it using the delegate methods... – celiux Jul 08 '17 at 10:15
  • 1
    To be honest a completion handler probably isn't the right pattern here. You could use NSNotification or you could set a delegate or store a closure in a property. I would probably use an NSNotification since that allows you to easily notify multiple observers – Paulw11 Jul 08 '17 at 10:18
  • Oh I see, I never used NSNotifications. I will check! Thanks to your answer I found this [thread](https://stackoverflow.com/questions/11513259/ios-cllocationmanager-in-a-separate-class), it's quite old but I think it can solve my problem. I'll give updates later! – celiux Jul 08 '17 at 10:37

2 Answers2

2

Following what Paulw11 said, I found the faster solution using Notifications.

  1. Send notification from the LocationManager delegate method inside the first view Controller:

    class MapViewController: CLLocationManagerDelegate {
    
        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    
                    if status == .authorizedWhenInUse {
                        locationManager.startUpdatingLocation()
    
                        mapView.isMyLocationEnabled = true
                        mapView.settings.myLocationButton = true
                    } else {
                        initialLocation()
                    }
                }
    
                func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    
                    if let location = locations.first {
    
                        mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
    
                        locationManager.stopUpdatingLocation()
    
                        let userInfo : NSDictionary = ["location" : location]
    
                        NotificationCenter.default.post(name: NSNotification.Name("UserLocationNotification"), object: self, userInfo: userInfo as [NSObject : AnyObject])
    
                    }
                }
         }
    
  2. Set the second view controller as observer. This way I can store the userLocation and use it later for the search request:

    class NeighbourhoodPickerViewController: UIViewController, UITextFieldDelegate {
    
    var userLocation: String?
    var currentLocation: CLLocation!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        NotificationCenter.default.addObserver(self, selector: #selector(locationUpdateNotification), name: Notification.Name("UserLocationNotification"), object: nil)
    
    }
    
    func locationUpdateNotification(notification: NSNotification) {
        if let userInfo = notification.userInfo?["location"] as? CLLocation {
            self.currentLocation = userInfo
            self.userLocation = "\(userInfo.coordinate.latitude), \(userInfo.coordinate.longitude)"
        }
    }
    
celiux
  • 613
  • 6
  • 10
1

I guess the problem is here,

self.locationManager.delegate = locationManagerDelegate

You have created a new instance of GMSClient, and saved it in the stored property and that instance is set as the delegate property of CLLocationManager.

You need to do this instead,

self.locationManager.delegate = GMSClient.sharedInstance()

You need to do this because you would want singleton instance of GMSClient to be the delegate for CLLocationManager and not a new instance. That way your singleton class would recieve the callbacks from CLLocationManager class.

To understand more about why your code was not working, I would suggest you read more about Objects, Instances, Instance variables, Singletons, Delegate design pattern.

Puneet Sharma
  • 9,369
  • 1
  • 27
  • 33
  • Thanks for your reply. Do you mean to better do this? locationManager.delegate = self. But it doesn't work neither. I also tryied to delete it and still the same... – celiux Jul 08 '17 at 09:41
  • Set the singleton instance as delegate as I mentioned in the answer. – Puneet Sharma Jul 08 '17 at 09:50
  • 1
    I made what you said but still the same. Maybe is not a problem of the delegation? Yep, I know... I spend hours reading about that programming foundations but still very difficult to aprehend, I guess it will come with time once I start doing real stuff. But I will research today more specially about that singelton/shared instances and delegates. – celiux Jul 08 '17 at 10:06