Obtaining user location not functioning in Xcode - swift

Good evening, I am trying to allow users to locate themselves on a map. When I run the app and access my google maps view, I get the error message "this app has attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an “NSLocationWhenInUseUsageDescription” key with a string value explaining to the user how the app uses this data" and the map is set a default state.
I have put
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs your current location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs your current location</string>
in my info.plist, but it tells me that I need to put in NSLocationWhenInUseUsageDescription in order to access location. I already have put it in and it still won't show the user location! Am I leaving something out or is this a bug? it worked fine for me before I updated to the newest version of Xcode.
My code that enables google maps is as follows,
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.delegate = self
locationManager.stopUpdatingLocation()
let location = locations.last
let lat = (location?.coordinate.latitude)!
let long = (location?.coordinate.longitude)!
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: long, zoom: 17.0)
self.myMapView.animate(to: camera)
showPartyMarkers(lat: lat, long: long)
}
func initGoogleMaps() {
let camera = GMSCameraPosition.camera(withLatitude: 40.014281, longitude: -83.030914, zoom: 17.0)
self.myMapView.camera = camera
self.myMapView.delegate = self
self.myMapView.isMyLocationEnabled = true
}
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
let lat = place.coordinate.latitude
let long = place.coordinate.longitude
showPartyMarkers(lat: lat, long: long)
let camera = GMSCameraPosition.camera(withLatitude: lat, longitude: long, zoom: 17.0)
myMapView.camera = camera
txtFieldSearch.text=place.formattedAddress

Add new records in your new InfoPlist.strings file.
<key>NSLocationAlwaysUsageDescription</key>
<string>app need your location for finding a place</string>
add lines in viewdidload
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
delete your app from simulator and run again.

Add below keys in plist.
Privacy - Location Always and When In Use Usage Description
Privacy - Location When In Use Usage Description
EDIT
I am not seeing Initialisation for locationManager.
Use below code to initialise:
/// CLLocationManager object
var locationManager: CLLocationManager = {
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
return manager
}()
After Init start updating location, for that add below lines in viewDidload
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()

I found the answer after reposting the question. If anyone is having this issue as well, what you want to do is click on your projects target build settings, click on info (Left of build settings) and you want to make sure the Custom iOS Target Properties have the same info as your regular p-list. If it does not, simply click the plus sign, and add them in. This should fix the problem because it has with mine. I hope you all find this useful.

Related

MapKit auto-zooming back to current location every time I scroll away from current location

I've built a basic app in X-code which shows the user's location and also zooms in automatically to the user's location. I'd like to be able to move around the map since I will have important annotations set up around the user's location, but every time I navigate to a different part of the map I am dragged back to my zoomed-in location. I was advised to "handle the pan gesture on the map and when the map is in pan state then don't zoom. You can control this with a simple bool flag to zoom or not." Please advise on how to fix this issue.
import UIKit
import MapKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
private let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
//delegates
locationManager.delegate = self
self.mapView.delegate = self
locationManager.requestWhenInUseAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
}
//zoom
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
let region = MKCoordinateRegion(center: mapView.userLocation.coordinate, span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
mapView.setRegion(region, animated: true)
}
}
When you set locationManager.startUpdatingLocation() the device's location hardware (GPS/radio receiver, etc) begins the job of actually trying to determine the location. But, it will continue to do that until you tell it to locationManager.stopUpdatingLocation() So your app will receive an ongoing stream of location updates that are all dealt with by your mapView delegate function mapView didUpdate userLocation Each time that method is called you are instructing the mapView to zoom to your user's location and so it is fighting and over-riding any other action the user is trying to perform at the same time, like scrolling, zooming, etc.
The stream of location updates will each have a figure for the uncertainty (.horizontalAccuracy) in the location measurement (+/- 60m or whatever) and each successive update will hopefully have a smaller uncertainty than the previous one but that isn't guaranteed depending on the local physical environment.
A common approach is, within mapView didUpdate userLocation, to keep a copy of the last update and compare it with each successive update and if the new one is more accurate than the previous one, save the new, else discard. Calculate the distance between new and old and when that distance is less than your previously decided minimum (kCLLocationAccuracyBest or 500m, 50m, 10m, whatever) then locationManager.stopUpdatingLocation()
Until you make the decision to stop updating locations, then you should avoid any kind of auto-zooming as the behaviour will frustrate your user.
This is a typical method, but would need modifying for your particular use:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last!
// We've been passed a cached result so ignore and continue.
if newLocation.timestamp.timeIntervalSinceNow < -5 {
return
}
// horizontalAccuracy < 0 indicates invalid result so ignore and continue.
if newLocation.horizontalAccuracy < 0 {
return
}
// Calculate the distance between the new and previous locations.
var distance = CLLocationDistance(Double.greatestFiniteMagnitude)
if let location = location { // location is my previously stored value.
distance = newLocation.distance(from: location)
}
// If newLocation is more accurate than the previous (if previous exists) then use it.
if location == nil || location!.horizontalAccuracy > newLocation.horizontalAccuracy {
lastLocationError = nil
location = newLocation
// When newLocation's accuracy is better than our desired accuracy then stop.
if newLocation.horizontalAccuracy <= locationManager.desiredAccuracy {
stopLocationManager()
}
} else if distance < 5 {
let timeInterval = newLocation.timestamp.timeIntervalSince(location!.timestamp)
if timeInterval > 10 {
stopLocationManager()
}
}
print(newLocation)
}
Sometimes the updates simply never result in an accuracy that satisfies our requirements so there is a timer in there to kill the location hunt after 10 seconds.
I would suggest, that rather than using an automatic zoom to the user's location, that you introduce a button that does the same thing. That way the user knows exactly when and why the behaviour occurs and if they're choosing to zoom to their location they won't be scrolling at the same time.
Not sure if you got your answer yet but here is how I took care of it.
I simply added a var called tracking which is set to true at start.
I called the locationManager.stopUpdatingLocation() inside my viewDidLoad, then set the tracking variable to false.
super.viewDidLoad()
checkLocationServices()
customers = ReadTextFile()
for customer in customers {
addCustomPin(customer:customer)
}
locationManager.stopUpdatingLocation()
tracking = false
Inside my checkLocationServices(), which calls checkLocationAuthorization()
I added
if tracking {
locationManager.startUpdatingLocation()
}
This way, the first time the app calls the checkLocationServices, my tracking var is true, so it checks for the location.
If I wanted to turn the setting back ON, I just need to turn the variable back to true.

Display current location without using CLLocationManager

I am new to iOS and I have a question about finding the current user location. I am reading in Apple documentation:
Displaying the User’s Current Location on the Map Map Kit includes
built-in support for displaying the user’s current location on the
map. To show this location, set the showsUserLocation property of your
map view object to YES. Doing so causes the map view to use Core
Location to find the user’s location and add an annotation of type
MKUserLocation to the map.
The addition of the MKUserLocation annotation object to the map is
reported by the delegate in the same way that custom annotations are.
If you want to associate a custom annotation view with the user’s
location, you should return that view from your delegate object’s
mapView:viewForAnnotation: method. If you want to use the default
annotation view, return nil from that method. To learn more about
adding annotations to a map, see Annotating Maps.
And it sounds great. But then...
import UIKit
import MapKit
class ViewController: UIViewController {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
//set initial location in Honolulu
//let initialLocation = CLLocation(latitude: 21.282778, longitude: -157.829444)
mapView.showsUserLocation = true
let initialLocation = mapView.userLocation.location
centerMapOnLocation(initialLocation)
// let artwork = Artwork(title: "King David Kalakaua", locationName: "Waikiki Gateway Park", discipline: "Sculpture", coordinate: CLLocationCoordinate2D(latitude: 21.283921, longitude: -157.831661))
// mapView.addAnnotation(artwork)
//
// mapView.delegate = self
}
let regionRadius: CLLocationDistance = 1000
func centerMapOnLocation(location: CLLocation){
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion,animated:true)
}
}
And I have a fatal error: unexpectedly found nil while unwrapping an Optional value. I don't get why. If I set the location manually - of course it is fine. But in the documentation it is written that it will add an annotation on the map. No annotation is added and it crashes. Isn't it possible to get the user coordinates without using the CLLocationManager?
Have you asked permissions to the user to let your app use the location services? The docs at Apple can help you with this. Look at the sample code below to get you started:
private func beginLocationUpdates() {
// Request location access permission
_locationManager!.requestWhenInUseAuthorization()
// Start observing location
_locationManager!.startUpdatingLocation()
}

MKLaunchOptionsMapSpanKey & Apple MapKit

What is the correct way to auto zoom an iOS MapKit MKMapItem generated map when it first displays using the option MKLaunchOptionsMapSpanKey:?
Specifically, how should the zoom factor be entered for MKLaunchOptionsMapSpanKey:??? (i.e. MKLaunchOptionsMapSpanKey:0.1).
Or, alternatively, is there another way programmatically to auto zoom the map when it first displays which I am not aware of?
#IBAction func getDirections(sender: AnyObject) {
let mapItem = MKMapItem.mapItemForCurrentLocation()
let coords = CLLocationCoordinate2DMake(latitude, longitude)
mapItem.name = "User Location"
let options = [MKLaunchOptionsMapSpanKey:???,MKLaunchOptionsMapCenterKey:location, ]
let place = MKPlacemark(coordinate: coords, addressDictionary:nil)
mapItem.openInMapsWithLaunchOptions(options)
}

MKLocalSearch in Swift

Currently i am trying to use MKLocalSearch to show the informations around user, but the problem is that there is always fatal error: unexpectedly found nil while unwrapping an Optional value. I've tried to make sure that there is no optional value, but still got the error
import UIKit
import MapKit
import CoreLocation
class MapKitViewController: UIViewController {
#IBOutlet weak var MapAround: MKMapView!
let locationManger:CLLocationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
var viewRegion = MKCoordinateRegion(center: MapAround.userLocation.location.coordinate, span: span)
var request = MKLocalSearchRequest()
request.naturalLanguageQuery = "restaurants"
request.region = MKCoordinateRegion(center: MapAround.userLocation.location.coordinate, span: span)
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler {
(response:MKLocalSearchResponse!, error:NSError!) -> Void in
if (error == nil) {
println("searched")
for item in response.mapItems as [MKMapItem]{
println("Item name = \(item.name)")
}
} else {
println(error)
}
}
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
var viewRegion = MKCoordinateRegion(center: MapAround.userLocation.location.coordinate, span: span)
MapAround.setRegion(viewRegion, animated: true)
}
}
Does anyone know where is the problem?
Thanks!!
You have two implicitly unwrapped optionals, the MKMapView and the userLocation. You might want to check to see if either of these are nil. The map view shouldn't be nil unless the the outlet was not hooked up properly or the view controller was not instantiated properly. The userLocation could conceivably be nil if location services had not yet determined the user's location.
As a general rule, when you first start location services (either through the map's "show user location" feature, or manually starting location services), it takes a little time before the user location is actually determined. As a result, one should be wary of ever trying to use the location directly in viewDidLoad.
Instead, you should implement the MKMapViewDelegate method didUpdateUserLocation (and don't forget to set the delegate of the map view). That function will be called when the user location has been updated, and you should refrain from trying to use the location until that point.
Alternatively, you can start location services of your CLLocationManager and wait for didUpdateLocations (one of the CLLocationManagerDelegate methods). But the concept is the same: Recognize that determining the user's location happens asynchronously, so wait for the OS to tell you that it determined the user location (and one of sufficient accuracy for the purposes of your app).
Having said that, I'd actually be more inclined to implement this search as part of the map view's regionDidChangeAnimated. You probably want to search the visible map, rather than the user's current location. If you happen to be using userTrackingMode of .Follow, it's equivalent, but if you ever contemplate letting the user move the map around (e.g. let the user search where they're going rather than where they current are), the regionDidChangeAnimated probably makes more sense.

Wrong Location - located in San Francisco and not in France

I am in France, and when I try my new func locationManager function, my map view locate me in San Francisco. Any idea?
override func viewDidLoad() {
super.viewDidLoad()
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
let location = locations[locations.count-1] as CLLocation
let center = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
self.map.setRegion(region, animated: true)
}
I am using the simulator.
The iOS Simulator defaults to US - San Fran, and does not give you an estimate of your Mac's actual location.
However you can simulate movement or even a specific location like so:
Under the Debug menu in the Simulator, the last entry is "Location"; this gives you a sub menu with:
None
Custom Location
Apple Stores
Apple
City Bicycle Ride
City Run
Freeway Drive
Custom Location lets you enter a Lat/Long value.
Bicycle ride, City Run, and Freeway Drive are simulation of a moving location (in Cupertino, of course).
In addition to Woodstock's answer, you can also set a default location in the scheme. It's in Run/Options. There's an option to allow Location simulation and a bunch of standard defaults you can use for testing. For even more option, you can add a GPX file to your project.