I'm trying to implement MapKit and CoreLocation in SwiftUI to fetch user locations in my app, check permissions, handle errors etc.. but I'm getting this error when I run the simulator and grant location permission.
Not sure what I'm doing wrong. Any help is appreciated
Here's a screenshot with where the error message shows
And here's my code which I presume is missing something that's causing the error:
import SwiftUI
import MapKit
import CoreLocation
// All Map data goes here
class MapViewModel: NSObject,ObservableObject, CLLocationManagerDelegate{
#Published var mapView = MKMapView()
// Region...
#Published var region : MKCoordinateRegion!
// Based on location it will set up...
// Alert...
#Published var permissionDenied = false
// Map Type...
#Published var mapType : MKMapType = .standard
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
// Checking permissions...
switch manager.authorizationStatus {
case .denied:
// Alert...
permissionDenied.toggle()
case .notDetermined:
// Requesting...
manager.requestWhenInUseAuthorization()
case .authorizedWhenInUse:
// If permission given...
manager.requestLocation()
default:
()
}
}
func locationManager(_manager: CLLocationManager, didFailWithError error: Error) {
// Error...
print(error.localizedDescription)
}
// Getting user Region...
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:
[CLLocation]) {
guard let location = locations.last else {return}
self.region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 10000, longitudinalMeters: 10000)
// Updating Map...
self.mapView.setRegion(self.region, animated: true)
// Smooth animation...
self.mapView.setVisibleMapRect(self.mapView.visibleMapRect, animated: true)
}
}
func locationManager(_manager: CLLocationManager,
should be
func locationManager(_ manager: CLLocationManager,
Related
So basically when the app loads it's supposed to center the map on the user location, but sometimes will get stuck at the guard let. Here is the code:
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self
locationManager.delegate = self
configureLocationServices()
addDoubleTap()
}
//Center map around user location
func centerMapOnUserLocation(){
print("In \(#function)")
guard let coordinate = locationManager.location?.coordinate else { print("Error getting coordinate"); return}
let coordinateRegion = MKCoordinateRegion.init(center: coordinate, latitudinalMeters: locationZoomRadius, longitudinalMeters: locationZoomRadius)
mapView.setRegion(coordinateRegion, animated: true)
//Setting local latitude and longitude variables to current location
latitude = "\(coordinate.latitude)"
longitude = "\(coordinate.longitude)"
previewDataOnButton()
print("Centered Map")
}
// MARK: - Location Services
//Request to enable location services
func configureLocationServices(){
if authorizationStatus == .notDetermined{
locationManager.requestAlwaysAuthorization()
} else {
return
}
}
//If authorization changes then call centerMapOnUserLocation
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("Authorization changed in \(#function). Calling centerMapOnUserLocation")
centerMapOnUserLocation()
}
}
It seems to get stuck at the guard let in centerMapOnUserLocation(). This is shown through print statements:
Authorization changed in locationManager(_:didChangeAuthorization:). Calling
centerMapOnUserLocation
In centerMapOnUserLocation()
Error getting coordinate
Im not sure how to fix this. Like I said sometimes it passes by the guard let and sometimes it gets stuck.
In you viewDidLoad add this line:
locationManager.startUpdatingLocation()
Then use this CLLocationManagerDelegate method:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
centerMapOnUserLocation()
}
You could pass in the locations into your method and move guard let before passing the location like this:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
centerMapOnUserLocation(location: location.last)
}
any one understand why Geo location won't show me where i am, also the permissions don't work over..... I'm baffled... laptop location is fine, also the app permissions are also set, also the Allow permissions tap, box for the user won't appear either
(iv explained this all above and it won't post unless i type more stuff in the box, but could some one explain why this code doesn't work norrr does it display no errors at all, i had also checked - DEBUG - locations... still nothing
import UIKit
import MapKit
import CoreLocation
class MapScreen: UIViewController {
#IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
let regionInMeters: Double = 10000
override func viewDidLoad() {
super.viewDidLoad()
checkLocationServices()
}
func setupLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func centerViewOnUserLocation() {
if let location = locationManager.location?.coordinate {
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
}
func checkLocationServices() {
if CLLocationManager.locationServicesEnabled() {
setupLocationManager()
checkLocationAuthorization()
} else {
// Show alert letting the user know they have to turn this on.
}
}
func checkLocationAuthorization() {
switch CLLocationManager.authorizationStatus() {
case .authorizedWhenInUse:
mapView.showsUserLocation = true
centerViewOnUserLocation()
locationManager.startUpdatingLocation()
break
case .denied:
// Show alert instructing them how to turn on permissions
break
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .restricted:
// Show an alert letting them know what's up
break
case .authorizedAlways:
break
}
}
}
extension MapScreen: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
let region = MKCoordinateRegion.init(center: location.coordinate, latitudinalMeters: regionInMeters, longitudinalMeters: regionInMeters)
mapView.setRegion(region, animated: true)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
checkLocationAuthorization()
}
}
Did you add the permission strings to your info.plist?
You need to add explanations for these otherwise the permission prompt won't appear, and no location updates will be send to your app.
NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription
You should also get an error for this in your console so check that as well.
I am trying to access the users coordinates using:
import MapKit
import CoreLocation
override func viewDidLoad() {
super.viewDidLoad()
let locationManager = CLLocationManager()
let userCoordinates = (locationManager.location?.coordinate)!
}
However, it crashes upon the simulator loading. I set the simulator location to Apple, and input privacy keys into info.plist, but I am not sure why this is not grabbing the user location.
There are a few things you need to do first before you can start using a device's current geolocation safely and based on the code you provided I'm going to assume that you might be missing some so here is a common set up using a Google Map, which acts like any other basically:
class YourViewController: UIViewController, CLLocationManagerDelegate {
// properties
var locationManager = CLLocationManager()
var currentCoordinate: CLLocationCoordinate2D?
// load view
override func loadView() {
addLocationManager()
}
// add location manager
func addLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
// location manager delegate: did change authorization
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .restricted:
print("LOCATION ACCESS RESTRICTED")
case .denied:
print("LOCATION ACCESS DENIED")
case .notDetermined:
print("LOCATION ACCESS NOT DETERMINED")
case .authorizedAlways:
fallthrough
case .authorizedWhenInUse:
print("LOCATION STATUS GRANTED")
}
}
// location manager delegate: did fail with error
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
print("LOCATION ACCESS ERROR: \(error)")
}
// location manager delegate: did update locations
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let lastLocation = locations.last!
// unhide map when current location is available
if mapView.isHidden {
mapView.camera = GMSCameraPosition.camera(withLatitude: lastLocation.coordinate.latitude, longitude: lastLocation.coordinate.longitude, zoom: 18, bearing: 0, viewingAngle: 0)
mapView.isHidden = false
}
// update current location properties
currentCoordinate = lastLocation.coordinate
}
}
And no need to import CoreLocation if you've imported MapKit.
I am trying to use CLLocation to capture longitude and latitude and then use the longitude and latitude in Alamofire to get weather. Every time, the longitude and latitude won't stop updating and the weather data won't print(if you wanna check it out here's an example link of the data: http://forecast.weather.gov/MapClick.php?lat=37.33233141&lon=-122.0312186&FcstType=json)
class SampleViewController: UIViewController, CLLocationManagerDelegate {
var locationManager:CLLocationManager!
var startLocation: CLLocation!
var isFetchingWeather = false
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
}
override func viewDidAppear(_ animated: Bool) {
getCurrentLocation()
}
func getCurrentLocation(){
if CLLocationManager.locationServicesEnabled(){
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var userLocation:CLLocation = locations[0]
if isFetchingWeather != false{
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
let requestLink = "http://forecast.weather.gov/MapClick.php?lat=\(userLocation.coordinate.latitude)&lon=\(userLocation.coordinate.longitude)&FcstType=json"
print(requestLink)
Alamofire.request(requestLink).validate().responseJSON
{ response in
switch response.result {
case .success(let data):
let json = JSON(data)
self.weatherData = json["data"].arrayValue
for weather in self.weatherData{
let temp = weather["weather"].stringValue
self.weatherString.append(temp)
}
print (self.weatherString)
if self.startLocation == nil {
self.startLocation = userLocation as! CLLocation
self.locationManager.stopUpdatingLocation()
}
case .failure(let error):
print(error)
}
}
}
else{
print("is fetching weather is false")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
{
print("Error \(error)")
}
}
Thanks.
You really shouldn't run your weather request inside your location delegate. Instead, get your location in the didUpdateLocations delegate and save it to a var. Next, call stopUpdatingLocation() then call a separate function to make your weather request. Something like this:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let newLocation = locations.last
//check accuracy and timestamp of location to make sure its not a cached/old location (if you don't care about accuracy or time, you can remove this check)
let timeDiff = newLocation?.timestamp.timeIntervalSinceNow
if timeDiff < 5.0 && (newLocation?.horizontalAccuracy)!<=self.accuracyNeeded{
//stop updating location
self.locationManager.stopUpdatingLocation()
//set currentUserLocation
self.myLocation=newLocation?.coordinate
//call function to get weather
//remove delegate
self.locationManager.delegate = nil
}
}
Set a flag to indicate when you start fetching weather info and do not call Alamofire to fetch weather information if that flag is set. For example, you would declare something like after the line where you declare startLocation:
var isFetchingWeather = false
Then, in locationManagerdidUpdateLocations first check if isFetchingWeather is false. If not, return. Otherwise, fetch the weather info.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if isFetchingWeather {
return
}
isFetchingWeather = true
// Do the actual weather fetching
}
Of course, you might want to do the actual fetching of the weather after you've gotten a few location updates since the initial ones might not be that accurate :)
I have been surfing the web for methods to get a user's current location in Swift 2.0, Unfortunately none have work. Here is the code I have been working on, but it cannot work. I have been playing with the info.plist file but don't know what to add? Can anyone tell me what I am doing wrong?
import CoreLocation
class ViewController: UIViewController {
let locationManager = CLLocationManager()
override func viewDidLoad(){
super.viewDidLoad()
findMyLocation()
}
func findMyLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
print("THIS IS THE LATTITUDE \(location.coordinate.latitude)")
}
locationManager.stopUpdatingLocation()
}
}
EDIT: in your didUpdateLocations function don't force stopUpdatingLocation. This function is called multiple times before your location is finalized to be shown on the map. Remove that line and you'll see that your console will print 3-5 times with your lat and long.
The recommended way to get the user location is to always check to make sure you have the permission so that if you don't you can alert the user and ask for said permission.
In your viewDidLoad()
locationManager?.requestAlwaysAuthorization()
// .AuthorizedInUse or .AuthorizedAlways depending on your app
if CLLocationManager.authorizationStatus() == .AuthorizedInUse {
locationManager?.distanceFilter = 5.0
locationManager?.requestLocation()
}
This ensures that you're requesting authorization if there is none and checking if they declined. In addition utilize this function in your CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus)
{
//Again this can be .AuthorizedAlways or .AuthorizedInUse depending
if status == .AuthorizedAlways {
locationManager?.startUpdatingLocation()
// ...
}
}
This allows the app to respond properly if they change the location permissions in the middle of using your location services after the viewDidLoad().
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
centerLocation = location
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.02, longitudeDelta: 0.02))
userLocationRegion = region
mainMapView.showAnnotations(mainMapView.annotations, animated: true)
self.mainMapView.setRegion(region, animated: true)
}
you need to add NSLocationWhenInUseUsageDescription or/and NSLocationAlwaysUsageDescription depends on your use case