Core Location in MVP - ios

In my project I have a LocationService class that conforms to CLLocationManagerDelegate protocol in order to detect current user's location.
class LocationService: NSObject, CLLocationManagerDelegate {
fileprivate let locationManager = CLLocationManager()
var location: Location? // Location(lat: Double, lon: Double)
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
}
func getCurrentLocation() -> Location? {
locationManager.startUpdatingLocation()
// how can I catch a location?
return location
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
self.location = Location(lat: location.coordinate.latitude, lon: location.coordinate.latitude)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
I want my WeatherPresenter to trigger location updates in LocationService and get the result as soon as location is found. Is there any way to do this?
class WeatherPresenter {
unowned let delegate: WeatherViewDelegate
let weatherService = WeatherService()
let locationService = LocationService()
init(with delegate: WeatherViewDelegate) {
self.delegate = delegate
}
func getWeatherForCurrentLocation() {
if let location = locationService.getCurrentLocation() {
//...
}
}
}

You can use Delegate to notify WeatherPresenter on changes from LocationService
protocol LocationServiceDelegate: class { // Delegate protocol
func didUpdateLocation()
}
class LocationService: NSObject, CLLocationManagerDelegate {
weak var delegate: LocationServiceDelegate?
fileprivate let locationManager = CLLocationManager()
var location: Location? // Location(lat: Double, lon: Double)
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
}
func startUpdatingLocation() { // Start updating called from presenter
locationManager.startUpdatingLocation()
}
func getCurrentLocation() -> Location? {
// how can I catch a location?
return location
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
if location.horizontalAccuracy > 0 {
locationManager.stopUpdatingLocation()
self.location = Location(lat: location.coordinate.latitude, lon: location.coordinate.latitude)
self.delegate?.didUpdateLocation() // Notify delegate on change
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
}
class WeatherPresenter: LocationServiceDelegate {
unowned let delegate: WeatherViewDelegate
let weatherService = WeatherService()
let locationService = LocationService()
init(with delegate: WeatherViewDelegate) {
self.delegate = delegate
self.locationService.delegate = self // Set self as delegate
self.locationService.startUpdatingLocation() // Requests start updating location
}
func didUpdateLocation() { // This will be called on location change
self.getWeatherForCurrentLocation()
}
func getWeatherForCurrentLocation() {
if let location = locationService.getCurrentLocation() {
//...
}
}
}

Related

How to return the value of CLLocationManager didUpdateLocations in function?

The problem iam having is that when i call the getCityName() function in other classes it returns nil because didUpdateLocations needs more time to run, how to solve this Async problem? and How to return the value of CLLocationManager didUpdateLocations in function?
import Foundation
import CoreLocation
class userCurrentLocation: NSObject, CLLocationManagerDelegate {
var locationManager:CLLocationManager!
var cityName:String!
init(cityName:String) {
super.init()
setUp()
}
func getCityName() -> String?{
return cityName
}
func setUp() {
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locations.last!.geocode { placemark, error in
if let error = error as? CLError {
print("CLError:", error)
return
} else if let placemark = placemark?.first {
self.cityName = placemark.locality ?? ""
manager.stopUpdatingLocation()
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}
extension CLLocation {
func geocode(completion: #escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void) {
CLGeocoder().reverseGeocodeLocation(self, completionHandler: completion)
}
}
class userCurrentLocation: NSObject, CLLocationManagerDelegate {
var locationManager:CLLocationManager!
var cityName:String!
//1. Add a closure callback
var didGetCity: ((String) -> Void)?
//2. Send City Name when received
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locations.last!.geocode { placemark, error in
if let error = error as? CLError {
print("CLError:", error)
return
} else if let placemark = placemark?.first {
self.cityName = placemark.locality ?? ""
// Check for null
// Send city name in the completion block
if (self.didGetCity != nil){
self.didGetCity?(self.cityName)
}
manager.stopUpdatingLocation()
}
}
}
//3. Add code to your class where you need the city Name
var City : String?
let locationManager = userCurrentLocation (cityName: "")
locationManager.didGetCity = {
[weak self] city in
self!.City = city
}
You can use Delegate to tell the main class that everything is ready and use that value
import Foundation
import CoreLocation
protocol userCurrentLocationDelegate: class {
func userCurrentLocationDidGetLocation(_ userLocation: userCurrentLocation)
}
class userCurrentLocation: NSObject, CLLocationManagerDelegate {
var locationManager:CLLocationManager!
var cityName:String!
weak var delegate: userCurrentLocationDelegate?
init(cityName:String) {
super.init()
setUp()
}
func getCityName() -> String?{
return cityName
}
func setUp() {
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locations.last!.geocode { placemark, error in
if let error = error as? CLError {
print("CLError:", error)
return
} else if let placemark = placemark?.first {
self.cityName = placemark.locality ?? ""
manager.stopUpdatingLocation()
delegate?.userCurrentLocationDidGetLocation(self)
}
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}
extension CLLocation {
func geocode(completion: #escaping (_ placemark: [CLPlacemark]?, _ error: Error?) -> Void) {
CLGeocoder().reverseGeocodeLocation(self, completionHandler: completion)
}
}
and in Main class that calls this one use delegate from this callback to start calling the city name

Delegate method didUpdateLocations is called after trying to use data that has not yet been retrieved from itself - Xcode Swift

I have an NS object (called GoogleSearch) that I use to get the user's location data. These are some global variables created and the init function:
let locationManager = CLLocationManager()
var coordinates: CLLocationCoordinate2D?
override init() {
super.init()
print("1")
DispatchQueue.main.async {
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
if CLLocationManager.locationServicesEnabled() {
self.locationManager.startUpdatingLocation()
}
}
}
Next, here is my delegate method:
extension GoogleSearch: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("2")
let userLocation: CLLocation = locations[0] as CLLocation
self.coordinates = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
}
}
Next, I try to access the coordinates (set in the delegate method) in a later function:
func searchForPlaces(completion: #escaping ([place])->()) {
print(coordinates)
}
Finally, in the ViewController that I implement this NSobject, I have the following code in viewDidLoad:
self.googleSearch = GoogleSearch()
DispatchQueue.main.async {
self.googleSearch.searchForPlaces(completion: { (places) in
DispatchQueue.main.async {
self.places = places
self.placesCollectionView.reloadData()
}
})
}
The problem is that printing coordinates in the searchForPlaces functions prints nil because it is run before the delegate method is called. Is there anything I should change in my NSObject or perhaps my ViewController to ensure that I can access coordinates from searchForPlaces() ?
Thank you.
You can achieve in this way:
class GoogleSearch:NSObject {
let locationManager = CLLocationManager()
var coordinates: CLLocationCoordinate2D?
var completionHandler: ((CLLocationCoordinate2D) -> Void)?
override init() {
super.init()
print("1")
}
func searchForPlaces(completion: #escaping (CLLocationCoordinate2D) -> Void) {
self.completionHandler = completion
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
if CLLocationManager.locationServicesEnabled() {
self.locationManager.stopUpdatingLocation()
self.locationManager.startUpdatingLocation()
}
}
}
extension GoogleSearch: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("2")
let userLocation: CLLocation = locations[0] as CLLocation
self.coordinates = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
self.completionHandler?(self.coordinates!)
}
}

How to fetch current location when users move in swift [duplicate]

I want to get the current longitude and latitude of a location using Swift and display them via labels. I tried to do this but nothing displays on the labels.
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate{
#IBOutlet weak var longitude: UILabel!
#IBOutlet weak var latitude: UILabel!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
if (CLLocationManager.locationServicesEnabled()) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
} else {
println("Location services are not enabled");
}
}
// MARK: - CoreLocation Delegate Methods
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
locationManager.stopUpdatingLocation()
removeLoadingView()
if (error) != nil {
print(error)
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locationArray = locations as NSArray
var locationObj = locationArray.lastObject as CLLocation
var coord = locationObj.coordinate
longitude.text = coord.longitude
latitude.text = coord.latitude
longitude.text = "\(coord.longitude)"
latitude.text = "\(coord.latitude)"
}
}
IMHO, you are over complicating your code when the solution you are looking is pretty simple.
I have done it by using the following code:
First create an instance of CLLocationManager and Request Authorization
var locManager = CLLocationManager()
locManager.requestWhenInUseAuthorization()
then check if the user allowed authorization.
var currentLocation: CLLocation!
if
CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways
{
currentLocation = locManager.location
}
to use it just do this
label1.text = "\(currentLocation.coordinate.longitude)"
label2.text = "\(currentLocation.coordinate.latitude)"
Your idea of setting them to the label.text is correct, however the only reason I can think of is that the user is not giving you permission and that is why your current Location data will be nil.
However you would need to debug and tell us that.
Also the CLLocationManagerDelegate is not necessary.
Hopefully this helps. Ask away if you have doubts.
For Swift 3:
First you need to set allowance to receive User's GPS in the info.plist.
Set: NSLocationWhenInUseUsageDescription with a random String.
And/or: NSLocationAlwaysUsageDescription with a random String.
Then:
import UIKit
import MapKit
class ViewController: UIViewController {
var locManager = CLLocationManager()
var currentLocation: CLLocation!
override func viewDidLoad() {
super.viewDidLoad()
locManager.requestWhenInUseAuthorization()
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways){
guard let currentLocation = locManager.location else {
return
}
print(currentLocation.coordinate.latitude)
print(currentLocation.coordinate.longitude)
}
}
}
Done.
Despite other advice you should use the CLLocationManagerDelegate to safely retrieve a location (without using it you may get null locations when the location manager doesn't have enough time to update). I strongly recommend wrapping the location manager code within a static shared helper (something along these lines):
class Locator: NSObject, CLLocationManagerDelegate {
enum Result <T> {
case .Success(T)
case .Failure(ErrorType)
}
static let shared: Locator = Locator()
typealias Callback = (Result <Locator>) -> Void
var requests: Array <Callback> = Array <Callback>()
var location: CLLocation? { return sharedLocationManager.location }
lazy var sharedLocationManager: CLLocationManager = {
let newLocationmanager = CLLocationManager()
newLocationmanager.delegate = self
// ...
return newLocationmanager
}()
// MARK: - Authorization
class func authorize() { shared.authorize() }
func authorize() { sharedLocationManager.requestWhenInUseAuthorization() }
// MARK: - Helpers
func locate(callback: Callback) {
self.requests.append(callback)
sharedLocationManager.startUpdatingLocation()
}
func reset() {
self.requests = Array <Callback>()
sharedLocationManager.stopUpdatingLocation()
}
// MARK: - Delegate
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
for request in self.requests { request(.Failure(error)) }
self.reset()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: Array <CLLocation>) {
for request in self.requests { request(.Success(self)) }
self.reset()
}
}
Then in view did load (or anywhere else you need to get the current location) run:
Locator.shared.locate { result in
switch result {
case .Success(locator):
if let location = locator.location { /* ... */ }
case .Failure(error):
/* ... */
}
}
In Swift
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
//Labels outlets
#IBOutlet var localityTxtField: UITextField!
#IBOutlet var postalCodeTxtField: UITextField!
#IBOutlet var aAreaTxtField: UITextField!
#IBOutlet var countryTxtField: UITextField!
let locationManager = CLLocationManager()
//View Didload
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
//Button Location
#IBAction func findMyLocation(_ sender: AnyObject) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in
if (error != nil) {
print("Reverse geocoder failed with error" + (error?.localizedDescription)!)
return
}
if (placemarks?.count)! > 0 {
print("placemarks",placemarks!)
let pm = placemarks?[0]
self.displayLocationInfo(pm)
} else {
print("Problem with the data received from geocoder")
}
})
}
func displayLocationInfo(_ placemark: CLPlacemark?) {
if let containsPlacemark = placemark {
print("your location is:-",containsPlacemark)
//stop updating location to save battery life
locationManager.stopUpdatingLocation()
let locality = (containsPlacemark.locality != nil) ? containsPlacemark.locality : ""
let postalCode = (containsPlacemark.postalCode != nil) ? containsPlacemark.postalCode : ""
let administrativeArea = (containsPlacemark.administrativeArea != nil) ? containsPlacemark.administrativeArea : ""
let country = (containsPlacemark.country != nil) ? containsPlacemark.country : ""
localityTxtField.text = locality
postalCodeTxtField.text = postalCode
aAreaTxtField.text = administrativeArea
countryTxtField.text = country
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while updating location " + error.localizedDescription)
}
}
In current thread a solution was proposed without delegate but in Xcode 9.1 testing in simulator it did not work, location was nil. This code worked:
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last! as CLLocation
/* you can use these values*/
let lat = location.coordinate.latitude
let long = location.coordinate.longitude
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I am junior but I solved it in this way:
I have created extension of my class inherited CLLocationManagerDelegate and following steps:
1.Import CoreLocation to your ViewController
import CoreLocation
2.Then initialize location manager and location variables inside your ViewController.
var locManager = CLLocationManager()
var currentUserLocation: CLLocation!
Inside viewDidLoad() request location init delegate and requestUsageDescription
locManager.requestWhenInUseAuthorization()
locManager.delegate = self
locManager.requestLocation()
Then I have just created extension for my viewController inheriting CLLocationManagerDelegate
extension theNameOfYourViewController: CLLocationManagerDelegate{
func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) {
print(error)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// .requestLocation will only pass one location to the locations array
// hence we can access it by taking the first element of the array
if let location = locations.first {
print(location.coordinate.latitude)
print(location.coordinate.longitude)
}
}
}
Just remember to change the names according your needs also whenever you need location just use the function request location
locManager.requestLocation()
I agree with Kevin above, but if you're looking for less code for something simpler the following will suffice:
Make sure to use the CLLocationManagerDelegate
Swift 4:
In viewDidLoad you can add the following
locationManager.requestWhenInUseAuthorization()
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse) || (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways) {
currentLocation = locationManager.location
print(currentLocation.coordinate.latitude)
print(currentLocation.coordinate.longitude)
}
}
And for the first request respond once the user gives or denies permission:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
currentLocation = locationManager.location
print(currentLocation.coordinate.latitude)
print(currentLocation.coordinate.longitude)
//Process location information and update.
}
Make sure to add the following keys to Info.plist:
Privacy - Location When In Use Usage Description
Privacy - Location Always and When In Use Usage Description
Create User class:
import Foundation
import CoreLocation
import MapKit
class User: NSObject, ObservableObject {
#Published var position = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.requestLocation()
}
}
extension User: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print("Location services authorization request")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("User location updated")
print("Updated position: \(locations.first?.coordinate.latitude ?? 00)")
if let location = locations.first {
self.position = location.coordinate
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Failed to find user's location: \(error.localizedDescription)")
}
}
Update
Swift 5+
Xcode 13+
Add these permission in info plist
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs your location to show nearby services</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs your location to show nearby services</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs your location to show nearby services</string>
Import this in your view controller
import CoreLocation
in viewDidLoad()
override func viewDidLoad() {
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
Create extension like this
extension RegisterViewController : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error:: \(error.localizedDescription)")
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let locationSafe = locations.last {
locationManager.stopUpdatingLocation()
let latitude = locationSafe.coordinate.latitude
let longitude = locationSafe.coordinate.longitude
self.currentLatitude = latitude
self.currentLongitude = longitude
print(" Lat \(latitude) , Longitude \(longitude)")
}
if locations.first != nil {
print("location:: \(locations[0])")
}
}
}
Run and check this

Shared instance of CLLocationManager

I was trying to create simple shared instance of CLLocationManager for my app.
There was no problem creating shared instance:
import UIKit
import CoreLocation
protocol LocationHandlerDelegate: class {
func locationHandlerDidUpdateLocation(location: CLLocation?)
func locationHandlerDidFailWithError(error: NSError)
}
class LocationHandler: NSObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
var location: CLLocation?
weak var delegate: LocationHandlerDelegate?
static let sharedInstance = LocationHandler()
override init() {
super.init()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
if CLLocationManager.authorizationStatus() == .NotDetermined {
locationManager.requestWhenInUseAuthorization()
}
}
// MARK: -
// MARK: - CLLocationManagerDelegate functions
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
delegate?.locationHandlerDidUpdateLocation(locations.last)
self.location = locations.last
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
delegate?.locationHandlerDidFailWithError(error)
locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedAlways || status == .AuthorizedWhenInUse {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
}
}
}
But for some reason when I call my shared instance from another class:
class TodayViewController: UIViewController, CLLocationManagerDelegate, LocationHandlerDelegate {
super.viewDidLoad() {
LocationHandler.sharedInstance.delegate = self
if #available(iOS 9.0, *) {
LocationHandler.sharedInstance.locationManager.requestLocation()
} else {
LocationHandler.sharedInstance.locationManager.startUpdatingLocation()
}
}
...
// MARK: - LocationHandlerDelegate function
func locationHandlerDidUpdateLocation(location: CLLocation?) {
if let location = location {
print("Current lcoation: \(location)")
}
else {
// ...
}
}
func locationHandlerDidFailWithError(error: NSError) {
print("Error finding lcoation: \(error.localizedDescription)")
}
}
None of CLLocationManagerDelegate functions in my LocationHandler are called.
But if in TodayViewController I write
LocationHandler.sharedInstance.locationManager.delegate = self
instead of
LocationHandler.sharedInstance.delegate = self
and I implement CLLocationManagerDelegate functions in my TodayViewController instead of LocationHandler then those delegate functions are called. So I guess there may be some problem with instancing? Or am I missing something else?

How to get current longitude and latitude using CLLocationManager-Swift

I want to get the current longitude and latitude of a location using Swift and display them via labels. I tried to do this but nothing displays on the labels.
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate{
#IBOutlet weak var longitude: UILabel!
#IBOutlet weak var latitude: UILabel!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
if (CLLocationManager.locationServicesEnabled()) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
} else {
println("Location services are not enabled");
}
}
// MARK: - CoreLocation Delegate Methods
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
locationManager.stopUpdatingLocation()
removeLoadingView()
if (error) != nil {
print(error)
}
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locationArray = locations as NSArray
var locationObj = locationArray.lastObject as CLLocation
var coord = locationObj.coordinate
longitude.text = coord.longitude
latitude.text = coord.latitude
longitude.text = "\(coord.longitude)"
latitude.text = "\(coord.latitude)"
}
}
IMHO, you are over complicating your code when the solution you are looking is pretty simple.
I have done it by using the following code:
First create an instance of CLLocationManager and Request Authorization
var locManager = CLLocationManager()
locManager.requestWhenInUseAuthorization()
then check if the user allowed authorization.
var currentLocation: CLLocation!
if
CLLocationManager.authorizationStatus() == .authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == .authorizedAlways
{
currentLocation = locManager.location
}
to use it just do this
label1.text = "\(currentLocation.coordinate.longitude)"
label2.text = "\(currentLocation.coordinate.latitude)"
Your idea of setting them to the label.text is correct, however the only reason I can think of is that the user is not giving you permission and that is why your current Location data will be nil.
However you would need to debug and tell us that.
Also the CLLocationManagerDelegate is not necessary.
Hopefully this helps. Ask away if you have doubts.
For Swift 3:
First you need to set allowance to receive User's GPS in the info.plist.
Set: NSLocationWhenInUseUsageDescription with a random String.
And/or: NSLocationAlwaysUsageDescription with a random String.
Then:
import UIKit
import MapKit
class ViewController: UIViewController {
var locManager = CLLocationManager()
var currentLocation: CLLocation!
override func viewDidLoad() {
super.viewDidLoad()
locManager.requestWhenInUseAuthorization()
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways){
guard let currentLocation = locManager.location else {
return
}
print(currentLocation.coordinate.latitude)
print(currentLocation.coordinate.longitude)
}
}
}
Done.
Despite other advice you should use the CLLocationManagerDelegate to safely retrieve a location (without using it you may get null locations when the location manager doesn't have enough time to update). I strongly recommend wrapping the location manager code within a static shared helper (something along these lines):
class Locator: NSObject, CLLocationManagerDelegate {
enum Result <T> {
case .Success(T)
case .Failure(ErrorType)
}
static let shared: Locator = Locator()
typealias Callback = (Result <Locator>) -> Void
var requests: Array <Callback> = Array <Callback>()
var location: CLLocation? { return sharedLocationManager.location }
lazy var sharedLocationManager: CLLocationManager = {
let newLocationmanager = CLLocationManager()
newLocationmanager.delegate = self
// ...
return newLocationmanager
}()
// MARK: - Authorization
class func authorize() { shared.authorize() }
func authorize() { sharedLocationManager.requestWhenInUseAuthorization() }
// MARK: - Helpers
func locate(callback: Callback) {
self.requests.append(callback)
sharedLocationManager.startUpdatingLocation()
}
func reset() {
self.requests = Array <Callback>()
sharedLocationManager.stopUpdatingLocation()
}
// MARK: - Delegate
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
for request in self.requests { request(.Failure(error)) }
self.reset()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: Array <CLLocation>) {
for request in self.requests { request(.Success(self)) }
self.reset()
}
}
Then in view did load (or anywhere else you need to get the current location) run:
Locator.shared.locate { result in
switch result {
case .Success(locator):
if let location = locator.location { /* ... */ }
case .Failure(error):
/* ... */
}
}
In Swift
import UIKit
import CoreLocation
class ViewController: UIViewController, CLLocationManagerDelegate {
//Labels outlets
#IBOutlet var localityTxtField: UITextField!
#IBOutlet var postalCodeTxtField: UITextField!
#IBOutlet var aAreaTxtField: UITextField!
#IBOutlet var countryTxtField: UITextField!
let locationManager = CLLocationManager()
//View Didload
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
//Button Location
#IBAction func findMyLocation(_ sender: AnyObject) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in
if (error != nil) {
print("Reverse geocoder failed with error" + (error?.localizedDescription)!)
return
}
if (placemarks?.count)! > 0 {
print("placemarks",placemarks!)
let pm = placemarks?[0]
self.displayLocationInfo(pm)
} else {
print("Problem with the data received from geocoder")
}
})
}
func displayLocationInfo(_ placemark: CLPlacemark?) {
if let containsPlacemark = placemark {
print("your location is:-",containsPlacemark)
//stop updating location to save battery life
locationManager.stopUpdatingLocation()
let locality = (containsPlacemark.locality != nil) ? containsPlacemark.locality : ""
let postalCode = (containsPlacemark.postalCode != nil) ? containsPlacemark.postalCode : ""
let administrativeArea = (containsPlacemark.administrativeArea != nil) ? containsPlacemark.administrativeArea : ""
let country = (containsPlacemark.country != nil) ? containsPlacemark.country : ""
localityTxtField.text = locality
postalCodeTxtField.text = postalCode
aAreaTxtField.text = administrativeArea
countryTxtField.text = country
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error while updating location " + error.localizedDescription)
}
}
In current thread a solution was proposed without delegate but in Xcode 9.1 testing in simulator it did not work, location was nil. This code worked:
import UIKit
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
if (CLLocationManager.locationServicesEnabled())
{
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
let location = locations.last! as CLLocation
/* you can use these values*/
let lat = location.coordinate.latitude
let long = location.coordinate.longitude
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I am junior but I solved it in this way:
I have created extension of my class inherited CLLocationManagerDelegate and following steps:
1.Import CoreLocation to your ViewController
import CoreLocation
2.Then initialize location manager and location variables inside your ViewController.
var locManager = CLLocationManager()
var currentUserLocation: CLLocation!
Inside viewDidLoad() request location init delegate and requestUsageDescription
locManager.requestWhenInUseAuthorization()
locManager.delegate = self
locManager.requestLocation()
Then I have just created extension for my viewController inheriting CLLocationManagerDelegate
extension theNameOfYourViewController: CLLocationManagerDelegate{
func locationManager(_ manager: CLLocationManager, didFailWithError error: Swift.Error) {
print(error)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// .requestLocation will only pass one location to the locations array
// hence we can access it by taking the first element of the array
if let location = locations.first {
print(location.coordinate.latitude)
print(location.coordinate.longitude)
}
}
}
Just remember to change the names according your needs also whenever you need location just use the function request location
locManager.requestLocation()
I agree with Kevin above, but if you're looking for less code for something simpler the following will suffice:
Make sure to use the CLLocationManagerDelegate
Swift 4:
In viewDidLoad you can add the following
locationManager.requestWhenInUseAuthorization()
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse) || (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways) {
currentLocation = locationManager.location
print(currentLocation.coordinate.latitude)
print(currentLocation.coordinate.longitude)
}
}
And for the first request respond once the user gives or denies permission:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
currentLocation = locationManager.location
print(currentLocation.coordinate.latitude)
print(currentLocation.coordinate.longitude)
//Process location information and update.
}
Make sure to add the following keys to Info.plist:
Privacy - Location When In Use Usage Description
Privacy - Location Always and When In Use Usage Description
Create User class:
import Foundation
import CoreLocation
import MapKit
class User: NSObject, ObservableObject {
#Published var position = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.requestLocation()
}
}
extension User: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
print("Location services authorization request")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("User location updated")
print("Updated position: \(locations.first?.coordinate.latitude ?? 00)")
if let location = locations.first {
self.position = location.coordinate
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Failed to find user's location: \(error.localizedDescription)")
}
}
Update
Swift 5+
Xcode 13+
Add these permission in info plist
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs your location to show nearby services</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs your location to show nearby services</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs your location to show nearby services</string>
Import this in your view controller
import CoreLocation
in viewDidLoad()
override func viewDidLoad() {
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
Create extension like this
extension RegisterViewController : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("error:: \(error.localizedDescription)")
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let locationSafe = locations.last {
locationManager.stopUpdatingLocation()
let latitude = locationSafe.coordinate.latitude
let longitude = locationSafe.coordinate.longitude
self.currentLatitude = latitude
self.currentLongitude = longitude
print(" Lat \(latitude) , Longitude \(longitude)")
}
if locations.first != nil {
print("location:: \(locations[0])")
}
}
}
Run and check this

Resources