Swift - Location Manager not available right away - swift

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)
}

Related

Swift 4 CLLocationManager question: Can I use the location, which is got from CLLocationManager, in ViewDidLoad function?

Followed the instructions online, I was able to get my current location using CLLocationManager. And the code structure is as below:
var myCurCoordinate:String!
override func viewDidLoad() {
super.viewDidLoad()
getLocation()
print(myCurCoordinate) // HERE I GOT "nil". In my original code, I am not really printing it. Instead, I have another function here that needs to use myCurCoordinate
}
func getLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let loc:CLLocation = locations[0] as CLLocation
myCurCoordinate="\(loc.coordinate.latitude),\(loc.coordinate.longitude)"
print(myCurCoordinate) // HERE print 3 times because of its async
}
Can anyone help figure out how can I obtain the location information and be able to print it in the viewDidLoad() ?
Thank you!
You can't print your location in viewDidLoad because it is an asynchronous method you need to wait until it is fetched. You need to stop updating your location using stopUpdatingLocation() when you get your location. To do it I prefer to use following.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
initialLocation = CLLocation(latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude)
print(initialLocation)
getLocation?(initialLocation)
}
manager.stopUpdatingLocation()
}
If you are insistent to print it inside viewDidLoad() you can do it with closures.
First, you need to define a closure.
var getLocation: ((_ location: CLLocation) -> (Void))?
Then in your viewDidLoad() method you need to specify what to do when that closure is triggered.
getLocation = { location in
print(location)
}
Then you need to trigger closure inside didUpdateLocations using
getLocation?(initialLocation)

Trying to assign Lat and Long coordinates to variables, comes back nil

I am trying to assign the value from the function that gets the current location. The print statement prints the lat and long coordinates but the variables come back nil.
I have tried moving this to view will appear but still the same results.
override func viewDidLoad() {
super.viewDidLoad()
// Ask for Authorisation from the User.
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
currentLat = locValue.latitude
currentLong = locValue.longitude
print("locations = \(locValue.latitude) \(locValue.longitude)")
}
//getWeather()
}
I moved this function to another view controller in the application because it was not setting the coordinates in time when my original view would load.
I also used:
locations.last?.coordinate.longitude
As opposed to:
guard let locValue: CLLocationCoordinate2D = manager.location?.coordinate else { return }
currentLong = locValue.longitude
Im sure there is a better way to do this but this works.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
currentLong = locations.last?.coordinate.longitude
currentLat = locations.last?.coordinate.latitude
print("locations = \(currentLong!) \(currentLat!)")
}

CLLocation found nil after using CLLocationManagerDelegate

I want to get longitude and latitude, and I add CLLocationManagerDelegate in the controller.
here is my code
let locationManager = CLLocationManager()
func getWeatherInformation(){
var currentLocation: CLLocation!
locationManager.requestWhenInUseAuthorization()
if( CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways){
currentLocation = locationManager.location
}
let longitude = "\(currentLocation.coordinate.longitude)"
let latitude = "\(currentLocation.coordinate.latitude)"
}
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
getWeatherInformation()
}
}
I also add location items in the info.plist, please see the screenshot
screenshot
The problem is both longitude and latitude, found nil after run the app and crash it.
What the problem come from?
Many Thanks
Location maybe not available right after request.
Please implement delegate function
optional func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
to get location.

CLLocation + Weather(Alamofire) issues

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 am having difficulty receiving a user's current location with Swift 2.0

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