Swift: Google Map code throwing nil exception - ios

I have the following code and it should basically detect location (if location services are on), then goes back to previous page and shows the coordinates (when button clicked).
#IBOutlet var mapView:GMSMapView!// Adding Google Map check on Storyboard
var locationManager: CLLocationManager!
var viewController : ViewController! //to save the object of previous controller to send the location back
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
locationManager = CLLocationManager() // Intialize the location manager
locationManager.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//Methos to change the MApType
#IBAction func changeMaptoEarthView()
{
self.mapView.mapType = kGMSTypeSatellite
}
#IBAction func changeMaptoMapView()
{
self.mapView.mapType = kGMSTypeNormal
}
//Method to get UserLocation
#IBAction func sendLocationtoPreviousView()
{
viewController.locationRecieved(locationManager.location)
self.performSegueWithIdentifier("backSegue", sender: nil)
}
#IBAction func addUserLocation() //Detect location click event
{
if(CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse) // If already have location access
{
locationManager.startUpdatingLocation()
}
else if (CLLocationManager.authorizationStatus() == .Denied)// If location access is denied in setting
{
UIAlertView(title: "Permission", message: "Please allow location services in setting", delegate: nil, cancelButtonTitle: "ok").show()
}
else
{
locationManager.requestWhenInUseAuthorization() // Ask user permission if permission not given
}
}
//CLLocation Manager Delegate methoda
func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedWhenInUse {
locationManager.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let location = locations.first as? CLLocation {
self.mapView.clear() // clear all overlay on Map
self.mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
// Adding pin on Current location
var marker = GMSMarker()
marker.position = location.coordinate
marker.snippet = "My Location"
marker.appearAnimation = kGMSMarkerAnimationPop
marker.map = mapView
locationManager.stopUpdatingLocation()
}
}
The app works in simulator until I say go back, then it throws nil error message on this line:
viewController.locationRecieved(locationManager.location)
What could be the reason? I can see the pin on the map although the map is not showing any graphics except google logo below

If you don't see the map graphic in your view (subclass of GMSMapView), this is because of the Google map API key , maybe you have not set it yet in your app delegate or its not set correctly !
make sure you have set the correct google API key with you app bundle id and your account in google . you have to set below code in your app delete , did didFinishLaunchingWithOptions function :
import GoogleMaps
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
GMSServices.provideAPIKey("your API Key")
}

The location property on CLLocationManager is an Optional - meaning that it can be nil. According to the docs, it will be nil if no location data has ever been retrieved.
You don't show how viewController.locationRecieved is defined, but my guess is that you've configured the CLLocation argument to an implicitly unwrapped optional (i.e., with a "!"). Passing nil to such an argument will cause a crash.

Related

Location coordinates are not nill yet labels don't get updated when app is loaded but get loaded if I come back from another screen tho

I have defined a global variable to store location coordinates and im updating location in Appdelegate as soon as the app launches. Here's the code for that:
var locationManager:CLLocationManager!
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
determineCurrentLocation()
self.pushToHomeScreen()
return true
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if GlobalUserManager.shared.currentLocation == nil {
GlobalUserManager.shared.currentLocation = locations.last
locationManager.stopUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error - locationManager: \(error.localizedDescription)")
}
//MARK:- Intance Methods
func determineCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
After this the Home Screen opens where I have two labels to show location data. It didn't get update when Homescreen is loaded for both cases when I tried to update by putting code in viewdidLoad() and viewWillAppear(). So I tried putting code in viewDidAppear() and its something like this:
override func viewDidAppear(_ animated: Bool) {
if GlobalUserManager.shared.currentLocation != nil {
lblLat.text = GlobalUserManager.shared.currentLocation.latitude
lblLong.text = GlobalUserManager.shared.currentLocation.longitude
}
else {
lblLat.text = "Lat not available"
lblLong.text = "Long not available"
}
}
When screen is loaded, labels are empty which means at least GlobalUserManager.shared.currentLocation != nil but I want them to get updated as soon as screen is loaded. Instead I have to click another page and come back and then they are updated.

Swift 3 - store the User Location and call it from different View Controllers

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!
Following what Paulw11 said, I found the faster solution using Notifications.
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])
}
}
}
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)"
}
}
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.

mapView.showsUserLocation = true affects the correctness of gps in my swift app. Why?

In my app I want to know user's real location. I want to show it in couple places as a plain text (as a string of longitude/latitude) and also show it on the map. I have several ui view controllers and one of them contains - now, for testing - a print to the console with current longitude and latitude, it also contains a map view.
I'm fetching the current gps position from my app delegate and put it on the screen.
In my AppDelegate.swift class I have:
var locationManager: CLLocationManager! = nil
var location: CLLocation! = nil
var longitude : Double = 0
var latitude : Double = 0
var location_fixed = false
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
initLocationManager()
}
func initLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
if CLLocationManager.authorizationStatus() == .AuthorizedAlways {
locationManager!.startUpdatingLocation()
} else {
locationManager!.requestAlwaysAuthorization()
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("Failed to initialize GPS: ", error.description)
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
location = locations.first!
print("Managed to get a fix on location")
let coord = location.coordinate
longitude = coord.longitude
latitude = coord.latitude
location_fixed = true;
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .NotDetermined:
print("NotDetermined")
case .Restricted:
print("Restricted")
case .Denied:
print("Denied")
case .AuthorizedAlways:
print("AuthorizedAlways")
locationManager!.startUpdatingLocation()
case .AuthorizedWhenInUse:
print("AuthorizedWhenInUse")
locationManager!.startUpdatingLocation()
}
}
func stopGPS() -> Void
{
print("STOP GPS")
location_fixed = false;
locationManager.stopUpdatingLocation()
}
func startGPS() -> Void
{
locationManager.startUpdatingLocation()
}
func isLocationFixed() -> Bool
{
return self.location_fixed
}
func getLongitude() -> Double
{
return self.longitude
}
func getLatitude() -> Double
{
return self.latitude
}
func getLocation() -> CLLocation
{
return self.location
}
Now in my UIViewController in viewWillAppear method I added asynchronous call to the app delegate methods for checking current gps position:
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.startGPS()
while (!appDelegate.isLocationFixed())
{
sleep(1)
}
dispatch_async(dispatch_get_main_queue()) {
self.fetchRequests(self.radius, lat: appDelegate.getLatitude(), lon: appDelegate.getLongitude())
print("gps here: \(appDelegate.getLatitude()), \(appDelegate.getLongitude())") }
}
and now when I enter this panel and waits for fetching the gps - I see the correct data. So far so good. Now, when I change the gps position in a simulator, then go to some other panel and come back to this one - I see the new data, which is fine.
And now comes the real weird problem. In the same panel I have a map view.
It's embedded in a UIViewController that I put in a container, so I cannot access it from my async call. It has a viewDidLoad() method and when I put there:
mapView.showsUserLocation = true
and run the app, I see the blue dot exactly when the simulator's gps points out, but now my data in the previous async call is not updating correctly.
When I comment out that one particular line of code, I'm getting the correct data shown as gps here:. When I leave it as a part of the code, then this line gps here: shows the same data all the time, even though I'm changing the gps position in a simulator, leaving and entering the panel several times. What's up with that?

getting user location and creating map not in sync

The below code is getting the user current location and creating a mapbox map.
problem is that the mapbox map is being created before the users location is obtain.
How may I slow or sync this process?
Thank you in advance,
import UIKit
import CoreLocation
import MapboxGL
class AViewController: UIViewController, CLLocationManagerDelegate {
var manager:CLLocationManager!
var userLocation:CLLocation = CLLocation(latitude: 25.776243, longitude: -80.136509)
override func viewDidLoad() {
super.viewDidLoad()
println("inside viewdidload")
self.getUserLocation()
}//eom
override func viewDidAppear(animated: Bool) {
println("inside viewdidappear")
self.createMapBoxMap()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*** MapBox Functions ************************************************************************/
/*Create preliminary map */
func createMapBoxMap(){
// set your access token
let mapView = MGLMapView(frame: view.bounds, accessToken: "pk.eyJ1IjoiZGFya2ZhZGVyIiwiYSI6IlplVDhfR3MifQ.pPEz732qS8g0WEScdItakg")
mapView.autoresizingMask = .FlexibleWidth | .FlexibleHeight
// set the map's center coordinate
mapView.setCenterCoordinate(CLLocationCoordinate2D(latitude: self.userLocation.coordinate.latitude, longitude: self.userLocation.coordinate.longitude),
zoomLevel: 13, animated: false)
view.addSubview(mapView)
//showing the user location on map - blue dot
mapView.showsUserLocation = true
}//eom
/*** location Functions ************************************************************************/
/*getting user current location*/
func getUserLocation(){
self.manager = CLLocationManager()
self.manager.delegate = self
self.manager.desiredAccuracy = kCLLocationAccuracyBest
self.manager.requestWhenInUseAuthorization()
self.manager.startUpdatingLocation()
}//eom
/*location manager 'didUpdateLocations' function */
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
self.manager.stopUpdatingLocation() //stop getting user location
println(locations)
self.userLocation = locations[0] as! CLLocation
}//eom
/* errors occurred */
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError) {
println("Error:" + error.localizedDescription)
}//eom
}//eoc
Location manager is running asynchronously (expected behavior), which means that it returns immediately and finishes creating the map as soon as the create map box method is called. However, it doesn't mean that the map has gotten a location yet. I'm thinking that the best way to implement this would be to move self.createMapBoxMap() to inside didUpdateLocations and try that. From my experience, you want the creation of your view to happen in a callback from an asynchronous method like this. You might want to have some type of loading view though, because the user will be confused at first.

Google Map SDK, iOS , cannot get myLocation

I'd like to show my location on iOS app by using Google Maps SDK. However, it cannot get my location. I referred the following documents, document1, document2
This is my code. It only shows the map of United Kingdom.
Please help me to solve the problem.
import UIKit
class SearchVC: UIViewController,CLLocationManagerDelegate{
///Google Map
#IBOutlet weak var mapView:GMSMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func locationManager(manager:CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus){
if status == .AuthorizedWhenInUse{
locationManager.startUpdatingLocation()
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
}
}
func locationManager(manager:CLLocationManager!, didUpdateLocations locations:[AnyObject]!){
if let location = locations.first as? CLLocation{
mapView.camera = GMSCameraPosition(target:location.coordinate, zoom:15,bearing:0, viewingAngle:0)
locationManager.stopUpdatingLocation()
}
}
}
Heres some code to parse your location... I think your just having an issue extracting the location info for the map view to load
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placemarks, error) -> Void in
if (error != nil) {
println("Error:" + error.localizedDescription)
return
}
if placemarks.count > 0 {
let pm = placemarks[0] as CLPlacemark
pm.location.coordinate;
mapView.camera = GMSCameraPosition(target:pm.location.coordinate, zoom:15,bearing:0, viewingAngle:0)
locationManager.stopUpdatingLocation()
}else {
println("Error with data")
}
}
I haven't compiled this code and I'm not swift savvy but hopefully this helps
To change your location go to Edit Scheme...
Then select whatever location you want to simulate
The problem:
func locationManager(manager:CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus)
This function is only truly called when the authorization status changes. For instance when you first run the app, it will ask to enable location services. Once you hit yes, this function will run. Next time you run the app, the authorization status won't change, because it was previously enabled.
The fix:
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.startUpdatingLocation()
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
}
}
Now everytime the view loads, it will start updating location as opposed to when the authorization status changes. Make sure that you also add the key: value pair into info.plist (NSLocationWhenInUseUsageDescription: {authorizatinon message string}).

Resources