how i can take variable from function for another function SWIFT 3 - ios

I wont print in console currentCity, for the use city name in another function! Please Help me.
var locationManager = CLLocationManager()
var currentCity: String?
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
print(currentCity) //----error not printing CITY NAME
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0]
CLGeocoder().reverseGeocodeLocation(userLocation) { (placemarks, error) in
if error != nil {
print(error?.localizedDescription as Any)
} else {
if let placemark = placemarks?[0] {
if let city = placemark.addressDictionary?["City"] as? NSString {
self.cityLbl.text = city as String
self.currentCity = city as String
}
}
}
}
}

Location manager is an asynchronous function. Which means your code will initiate the function but carry on with the next piece of code, and not wait to get the location, which will eventually finish later.
So even though you are calling your update location before the print function, by the time the code is executing the print it has not gotten the location yet and your current city is nil.
Print the name from the location manager function or add a completion closure. That should do the trick.

You need to print city in didUpdateLocations.
if let city = placemark.addressDictionary?["City"]
as? NSString {
self.cityLbl.text = city as String
self.currentCity = city as String
print(currentCity) // here you get string
// call that function here
self.myfunction(cityName: currentCity)
}
Function
func myfunction(cityName : String) -> Void {
// your string with city name
}

func viewDidLoad() is called first and at the time the location is not fetched. Try printing after func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) is called.

Related

Getting location on real device not working

I'm trying to get the user location, running on the simulator, I get the default address, but atleast I know it is working.
I tried to run it on my device but it didn't work.
I try to look for a solution before writing this question but couldn't find something that work for me.
This is my code:
LocationManager:
class LocationManager: NSObject, CLLocationManagerDelegate {
static let shared = LocationManager()
var locationManager: CLLocationManager!
var geoCoder = CLGeocoder()
var callBack:((String)->())?
override init() {
super.init()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .other
locationManager.requestWhenInUseAuthorization()
}
func checkIfLocationIsEnabled() -> Bool{
return CLLocationManager.locationServicesEnabled()
}
func getUserLocation(){
locationManager.requestLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let userLocation: CLLocation = locations[0] as CLLocation
geoCoder.reverseGeocodeLocation(userLocation) { (placemarks, err) in
if let place = placemarks?.last{
self.callBack?(place.name!)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}
This is my getLocation (just calling the getUserLocation and setting the address I get from the callback):
func getLocation(_ label: UILabel) -> String{
guard let comment = self.mView.addCommentTextField.text else { return ""}
LocationManager.shared.getUserLocation()
var addressString = ""
LocationManager.shared.callBack = { address in
DispatchQueue.main.async {
label.text = "\(address), \(comment)"
addressString = address
}
}
return addressString
}
This is how I call getLocation:
self.mView.inLabel.isHidden = false
self.getLocation(self.mView.inLabel)
Actually looking closer at your code, I see that you are asking permissions like this:
locationManager.requestWhenInUseAuthorization()
But requestWhenInUseAuthorization() is asynchronous call, you need to wait for user response before you can use any location services:
When the current authorization status is CLAuthorizationStatus.notDetermined, this method runs asynchronously and prompts the user to grant permission to the app to use location services.
(source)
Also notice that it will only work if status is notDetermined. Any other status would not trigger it. So firstly:
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
// already authorized, can use location services right away
}
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestWhenInUseAuthorization()
// wait, don't call any location-related functions until you get a response
}
If location permissions are set to anything else, no point to ask for them.
And then your class is already CLLocationManagerDelegate, so:
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
// do something with new status, e.g.
if status == .authorizedWhenInUse {
// good, now you can start accessing location data
}
// otherwise, you can't

Can't update label after getting location

I have a simple button, when I press the button, I'm making a call to another class, my Location class to get the user's current location.
After getting the location, I want to update a label text I have to show the location.
This is my location class:
class LocationManager: NSObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
var geoCoder = CLGeocoder()
var userAddress: String?
override init() {
super.init()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.activityType = .other
locationManager.requestWhenInUseAuthorization()
}
func getUserLocation(completion: #escaping(_ result: String) -> ()){
if CLLocationManager.locationServicesEnabled(){
locationManager.requestLocation()
}
guard let myResult = self.userAddress else { return }
completion(myResult)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let userLocation: CLLocation = locations[0] as CLLocation
geoCoder.reverseGeocodeLocation(userLocation) { (placemarks, err) in
if let place = placemarks?.last{
self.userAddress = place.name!
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}
and this is where I call the method and updating the label:
func handleEnter() {
mView.inLabel.isHidden = false
location.getUserLocation { (theAddress) in
print(theAddress)
self.mView.inLabel.text = "\(theAddress)"
}
}
My problem is that when I click my button (and firing handleEnter()), nothing happens, like it won't register the tap. only after tapping it the second time, I get the address and the labels update's.
I tried to add printing and to use breakpoint to see if the first tap registers, and it does.
I know the location may take a few seconds to return an answer with the address and I waited, but still, nothing, only after the second tap it shows.
It seems like in the first tap, It just didn't get the address yet. How can I "notify" when I got the address and just then try to update the label?
Since didUpdateLocations & reverseGeocodeLocation methods are called asynchronously, this guard may return as of nil address
guard let myResult = self.userAddress else { return }
completion(myResult)
Which won't trigger the completion needed to update the label , instead you need
var callBack:((String)->())?
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
let userLocation: CLLocation = locations[0] as CLLocation
geoCoder.reverseGeocodeLocation(userLocation) { (placemarks, err) in
if let place = placemarks?.last{
callBack?(place.name!)
}
}
}
Then use
location.callBack = { [weak self] str in
print(str)
DispatchQueue.main.async { // reverseGeocodeLocation callback is in a background thread
// any ui
}
}

Why is Swift/Core Location showing my lat/long location as being in San Francisco -- even tho I'm in New York?

Hey everyone — I have written some code that identifies location via CLLocation, then converts that lat/long value to a city with CLGeoCoder().
It's working in that the code runs and id's a location -- except I am in New York, and it keeps identifying me as being in San Francisco!
Can you spot any clear mistakes I'm making in the code below? Thank you!
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[locations.count - 1]
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
locationManager.delegate = nil
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(location, completionHandler:
{
placemarks, error -> Void in
guard let placeMark = placemarks?.first else { return }
if let locationName = placeMark.location {
print(locationName)
}
if let street = placeMark.thoroughfare {
print(street)
}
if let city = placeMark.subAdministrativeArea {
print(city)
}
if let zip = placeMark.isoCountryCode {
print(zip)
}
if let country = placeMark.country {
print(country)
}
})
}
}
}
Run your app on a device (such as an actual iPhone) if you want to use location services to detect your actual location. Testing such code on a Simulator is pretty much pointless.

How to use locationManager() in multiple ViewControllers

I need to get the zipCode and the city in multiple viewControllers.
Here is how I'm currently doing it...
import CoreLocation
let locationManager = CLLocationManager()
class MyViewController: UIViewController, CLLocationManagerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)-> Void in
if error != nil {
//AlertView to show the ERROR message
}
if placemarks!.count > 0 {
let placemark = placemarks![0]
self.locationManager.stopUpdatingLocation()
let zipCode = placemark.postalCode ?? ""
let city:String = placemark.locality ?? ""
// Do something with zipCode
// Do something with city
}else{
print("No placemarks found.")
}
})
}
func someFunction() {
locationManager.startUpdatingLocation()
}
Everything works fine but as you can see doing it this way in multiple viewController leads to a lot of code repetition (of course, I'm not showing the whole code).
What would be the most common way to retrieve the zipCode and city from CLLocationManager() in a more practical way from multiple viewControllers?
What I'm thinking is something like...
MyLocationManager.zipCode() // returns zipCode as a string
MyLocationManager.city() // returns city as a string
The usual thing is to have just one location manager in one persistent place that you can always get to from anywhere, like the app delegate or the root view controller.
I tried to implement a singleton CLLocationManager class, I think you can modify the following class to implement some additional methods.
import Foundation
class LocationSingleton: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var latitude = 0.0
private var longitude = 0.0
static let shared = LocationSingleton()
private override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLLocationAccuracyHundredMeters
locationManager.requestAlwaysAuthorization() // you might replace this with whenInuse
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
latitude = location.coordinate.latitude
longitude = location.coordinate.longitude
}
}
private func getLatitude() -> CLLocationDegrees {
return latitude
}
private func getLongitude() -> CLLocationDegrees {
return longitude
}
private func zipCode() {
// I think you can figure way out to implemet this method
}
private func city() {
// I think you can figure way out to implemet this method
}
}

MKMapItem.forCurrentLocation() returns "Unknown Location"

Inside my ViewController's class viewDidLoad method I have:
override func viewDidLoad() {
super.viewDidLoad()
requestAuthorization()
locationManager.delegate = self
print(MKMapItem.forCurrentLocation())
}
and here is requestAuthorization() function:
private func requestAuthorization(){
if CLLocationManager.authorizationStatus() == .notDetermined{
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
else if CLLocationManager.authorizationStatus() == .denied{
//TODO
}
}
the problem is that forCurrentLocation() function never returns actual user location instead returned coordinates of MKMapItem are: (latitude: 0, longitude: 0). Here is the result of the print function.
MKMapItem: 0x60000014e020
isCurrentLocation = 1;
name = "Unknown Location";
What am I doing wrong?
Edit:
The reason I wanted to use MKMapItem.forCurrentLocation() was that I planned to get user coordinates in prepareForSegue. And I thought it would be easier than using didUpdateLocations delegate method. See the code:
if segue.identifier == "categories",
let destinationVC = segue.destination as? CategoriesGroupViewController{
if let cell = sender as? UICollectionViewCell{
let indexPath = categoriesCollectionView.indexPath(for: cell)
destinationVC.groupOfDestinations = destinationsByCategory[indexPath!.row]
// 0 is index for Near Me cell
if indexPath?.row == 0 {
destinationVC.groupOfDestinations = getNearMeDestinations()
}
}
private func getNearMeDestinations() -> GroupOfDestinations{
let userCoordinates = MKMapItem.forCurrentLocation().placemark.coordinate
let nearMeCircularRegion: CLCircularRegion = CLCircularRegion(center: userCoordinates, radius: 10000, identifier: "nearMe")
...
return nearMeDestinations
}
You´re not doing anything wrong. The MKMapItem.forCurrentLocation() Creates and returns a singleton map item object representing the device’s current location.
MKMapItem.forCurrentLocation().isCurrentLocation is a Boolean value indicating whether the map item represents the user’s current location. In your case true.
MKMapItem.forCurrentLocation().name The descriptive name associated with the map item. If this map item represents the user’s current location, the value in property is set to a localized version of “Current Location”.
And that it returns Unknown Location is weird. But it´s enough for you to keep track of the MKMapItem.forCurrentLocation().isCurrentLocation value.
Update:
To get the user locations coordinate do the following:
var location = CLLocation()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let latitude = manager.location?.coordinate.latitude, let longitude = manager.location?.coordinate.longitude else { return }
location = CLLocation(latitude: latitude, longitude: longitude)
}
And then use location which always will be up to date when the user moves.
locationManager.desiredAccuracy = kCLLocationAccuracyBest
Put above line outside if else block, Like
var locationManager: CLLocationManager?
private func requestAuthorization(){
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined{
locationManager.requestWhenInUseAuthorization()
}
else if CLLocationManager.authorizationStatus() == .denied{
//TODO
}
}
There is delegate method for map, Which gives you current location
// CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
Print(location)// This is your current location
}

Resources