I am trying to make LocationService class that will convey userCoordinate, so it will be reusable for more than one View Controller
import UIKit
import CoreLocation
class LocationService: NSObject {
let manager = CLLocationManager()
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.startUpdatingLocation()
}
func getPermission() {
// to ask permission to the user by showing an alert (the alert message is available on info.plist)
if CLLocationManager.authorizationStatus() == .notDetermined {
manager.requestWhenInUseAuthorization()
}
}
func checkLocationAuthorizationStatus() -> CLAuthorizationStatus{
return CLLocationManager.authorizationStatus()
}
}
extension LocationService : CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
manager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("location is not available: \(error.localizedDescription)")
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let userLocation = locations.first else {return}
if userLocation.horizontalAccuracy > 0 {
manager.stopUpdatingLocation()
let coordinate = Coordinate(location: userLocation)
// how to convey this coordinate for several view controller?
}
}
}
as you can see in the didUpdateLocations method that comes from CLLocationManagerDelegate, the coordinate need some time to be generated.
but I don't know how to convey that user coordinate, I think it will use completion handler but I don't know how to get that
so let say in HomeVC, I will call that LocationService to get the userCoordinate
import UIKit
class HomeVC: UIViewController {
let locationService = LocationService()
override func viewDidLoad() {
super.viewDidLoad()
// get coordinate, something like this
locationService.getCoordinate()
}
}
You can use Notification like Paulw11 said. You need to update didUpdateLocations. This is the place where you are going to post notification.
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let userLocation = locations.first else {return}
if userLocation.horizontalAccuracy > 0 {
manager.stopUpdatingLocation()
let coordinate = Coordinate(location: userLocation)
let locationDictionary: [String: Double] = ["lat": location.coordinate.latitude,
"long": location.coordinate.longitude]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "YourNotificationNameAsYouWantToNameIt"), object: nil, userInfo: locationDictionary)
}
}
Now in viewDidLoad in every view controller that you want this location you need to observe this notification:
NotificationCenter.default.addObserver(self, selector: #selector(doSomethingAboutLocation(_:)), name: NSNotification.Name(rawValue: "YourNotificationNameAsYouWantToNameIt"), object: nil)
Then access your location like this in your function called from selector:
#objc func doSomethingAboutLocation(_ notification: Notification) {
if let notificationInfo = notification.userInfo {
let coordinate = CLLocation(latitude: notificationInfo["lat"] as! Double, longitude: notificationInfo["long"] as! Double)
// use your coordinate as you want
}
}
Related
I have a view (say V) in which a user answers a few questions and their location is recorded. However, the answers only make sense with the user's location.
So what I want is that when the user clicks on a button on the parent view, it takes them to V and immediately asks them for the location permission. If they accept, they can continue on to answer the questions, but if they deny, they navigate back to the parent screen.
I know I can navigate back to the parent screen with self.presentation.wrappedValue.dismiss().
But how do I know when the user has accepted or denied the permission since requestWhenInUseAuthorization() is an asynchronous function?
I'm following this tutorial on getting a user's location on iOS with Swift.
Code for my LocationService:
import CoreLocation
protocol LocationServiceDelegate {
func didFetchCurrentLocation(_ location: GeoLocation)
func fetchCurrentLocationFailed(error: Error)
}
class LocationService: NSObject, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
var delegate: LocationServiceDelegate
init(delegate: LocationServiceDelegate) {
self.delegate = delegate
super.init()
self.setupLocationManager()
}
private func setupLocationManager() {
if canUseLocationManager() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
}
func requestLocation() {
if canUseLocationManager() {
print(CLAuthorizationStatus.self)
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
}
func requestPermission() {
locationManager.requestWhenInUseAuthorization()
}
private func canUseLocationManager() -> Bool {
return CLLocationManager.locationServicesEnabled()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
if let location = locations.last {
let geoLocation = GeoLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
delegate.didFetchCurrentLocation(geoLocation)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
delegate.fetchCurrentLocationFailed(error: error)
}
deinit {
locationManager.stopUpdatingLocation()
}
}
struct GeoLocation {
var latitude: Double
var longitude: Double
}
CLLocationManagerDelegate has also the following method:
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
}
This method is called every time the authorization status changed. I would also like to recommend you implementing your LocationService as an ObservableObject instead of using delegate approach.
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() {
//...
}
}
}
I have two view controllers - one with the mapView that is able to obtain user location coordinations through locationManager, and a second VC that I wish to be able to pull these user coordinates.
First VC: MapView
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
var coordinatesOfUser = locations.last?.coordinate
print("The value of usercoordinates are \(coordinatesOfUser)")
// here I want to be able to pull this variable, coordinatesOfUser
if let location = locations.last {
let span = MKCoordinateSpanMake(0.00775, 0.00775)
let myLocation = CLLocationCoordinate2DMake(location.coordinate.latitude,location.coordinate.longitude)
let region = MKCoordinateRegionMake(myLocation, span)
map.setRegion(region, animated: true)
}
self.map.showsUserLocation = true
locationManager.stopUpdatingLocation()
}
Second VC:
I was thinking of calling the locationManager function in this VC. Is this the most efficient way to pull the coordinates to this VC? And if so, how would I go about doing it?
Here's a couple options to solve this:
Delegation: Your secondVC could have a delegate that allows the first view controller to get coordinates from it. The advantage here is that you could receive updates as the come in.
protocol MyLocationDelegate: class {
func newLocationArrived(location: CLLocation)
}
class FirstVC: UIViewController, MyLocationDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
func newLocationArrived(location: CLLocation) {
print(location)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let dest = segue.destination as? SecondVC {
dest.delegate = self
}
}
}
class SecondVC: UIViewController, CLLocationManagerDelegate {
/// ... ...
weak var delegate: MyLocationDelegate?
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
/// do something with the location
/// provide the data via delegation
delegate?.newLocationArrived(location: CLLocation())
}
}
Notifications: Post a notification via NSNotificationCenter. Also able to receive updates as the come in, just send via notification center instead of through a delegate.
postNotificationName:object:userInfo:
Child View Controller: Depending on whether the second view controller and its view are a child of the first, this could allow direct access. Not always an option.
Singleton (CLLocationManager): If you plan to use Location Services in other places throughout the app, you can move the CLLocationManager into its own class with a Singleton. Other view controllers can reference that class for their specific needs. This can also be helpful when using the background or significant change locations as they might need to use the LaunchOptions Key to restart the location manager.
class MyLocationManager: CLLocationManager, CLLocationManagerDelegate {
static let shared = MyLocationManager()
var locations = [CLLocation]()
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
self.locations.append(location)
}
}
}
I had the same problem and I finally fixed it with a Notification, as kuhncj said.
This is how the code looks like at the end:
//Get user location
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
locationManager.startUpdatingLocation()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
} else {
initialLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.first {
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
locationManager.stopUpdatingLocation()
let userInfo: NSDictionary = ["location": location]
//Post notification
NotificationCenter.default.post(name: NSNotification.Name("UserLocationNotification"), object: self, userInfo: userInfo as [NSObject : AnyObject])
}
}
And then in the other view controller:
var userLocation: String?
override func viewDidLoad() {
super.viewDidLoad()
//Observer that receives the notification
NotificationCenter.default.addObserver(self, selector: #selector(locationUpdateNotification), name: Notification.Name("UserLocationNotification"), object: nil)
}
func locationUpdateNotification(notification: NSNotification) {
if let userInfo = notification.userInfo?["location"] as? CLLocation {
//Store user location
self.userLocation = "\(userInfo.coordinate.latitude), \(userInfo.coordinate.longitude)"
}
}
Then I was able to use the stored userLocation for another methods.
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
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