CLLocation + Weather(Alamofire) issues - swift

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

Related

Swift - Location Manager not available right away

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

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.

How to access user coordinates

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.