SwiftUI: Display live map coordinates while moving - ios

I am making app where I need to get live coordinates of user and upload the newest coordinates every x minutes. I've been struggling with it for last few hours, and tried almost everything and checked all threads available and still don't have the answer.
Here is my code:
class LocationService: NSObject, ObservableObject, CLLocationManagerDelegate {
#Published var location: CLLocation?
#Published var locationManager = CLLocationManager()
init(searchCompleter: MKLocalSearchCompleter = MKLocalSearchCompleter()) {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = CLLocationDistanceMax
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
extension LocationService {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
self.location = location
print(location.coordinate)
}
}
extension MKCoordinateRegion {
static func goldenGateRegion() -> MKCoordinateRegion {
MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 37.819527098978355, longitude: -122.47854602016669), latitudinalMeters: 10, longitudinalMeters: 10)
}
func getBinding() -> Binding<MKCoordinateRegion>? {
return Binding<MKCoordinateRegion>.constant(self)
}
}
I am calling this function on main view:
.onChange(of: locationService.location) { newValue in
print(newValue?.coordinate ?? "error")
}
And I am getting same coordinates for the whole time, (I picked driveway location option in simulator).

Related

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

iOS - CLLocation Manager didUpdateLocations being called in one class but not another?

Hi I'm making a program that gets the users location and puts an according annotation on the map. I started by writing all of the code in the View Controller and it gets the location perfectly. Below is the working code in the view controller.
class MapViewController: UIViewController, CLLocationManagerDelegate {
var annotation = MKPointAnnotation()
var userLocation: CLLocation?
#IBOutlet weak var mapView: MKMapView!
var locationManager:CLLocationManager!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
determineCurrentLocation()
}
func determineCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
userLocation = locations[0] as CLLocation
print("user latitude = \(userLocation?.coordinate.latitude)")
print("user longitude = \(userLocation?.coordinate.longitude)")
annotation.coordinate = CLLocationCoordinate2D(latitude: (userLocation?.coordinate.latitude)!, longitude: (userLocation?.coordinate.longitude)!)
annotation.title = "You"
mapView.addAnnotation(annotation)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
}
However now when I try and recreate almost the exact same code in another swift file. didUpdateLocations never gets called. locationManager.startUpdatingLocation() does get called.
Below is my new swift file which I call from the View Controller. Is there any simple concept I'm missing here because I really don't see why this doesn't work.
import Foundation
import CoreLocation
class SendLocation: NSObject, CLLocationManagerDelegate {
var userLocation: CLLocation?
var locationManager:CLLocationManager!
func sendLocationPost() {
determineCurrentLocation()
}
func determineCurrentLocation() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
print("WHY")
if CLLocationManager.locationServicesEnabled(){
locationManager.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
userLocation = locations[0] as CLLocation
print("user latitude = \(userLocation?.coordinate.latitude)")
print("user longitude = \(userLocation?.coordinate.longitude)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error \(error)")
}
}
I call it using :
let location = SendLocation()
location.sendLocationPost()`
in my View Controller
This happens because you are not keeping a reference to your SendLocation object.
Make SendLocation a property of your UIViewController.
For example, calling it from a static scope will not keep a reference.
WONT WORK:
static func sendLocation() {
let location = SendLocation()
location.sendLocationPost()
}
WILL WORK
let location = SendLocation()
func sendLocation() {
location.sendLocationPost()
}

Why iOS CLLocation shows incorrect values?

I'm trying to calculate distance between 2 coordinates. For this I do:
func setupLocationManager() {
if CLLocationManager.authorizationStatus() == .NotDetermined {
locationManager.requestWhenInUseAuthorization()
}
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let latitude = locations.last?.coordinate.latitude
let longitude = locations.last?.coordinate.longitude
let myLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let targetLocation = CLLocation(latitude: 41.4381022, longitude: 46.604910)
let distance = myLocation.distanceFromLocation(targetLocation)
print(distance)
}
I'm checking in Google Maps there are 13 km distance! But my app shows me 2-3 km!
How can I improve that?
Google maps gives you the route distance between two locations, CLLocation gives you the birds-eye distance between two locations.
From the documentation:
This method measures the distance between the two locations by tracing
a line between them that follows the curvature of the Earth. The
resulting arc is a smooth curve and does not take into account
specific altitude changes between the two locations.
Here is an example based on a working GPS app.
import CoreLocation
public class SwiftLocation: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
private var latestCoord: CLLocationCoordinate2D
init(ignore:String) {
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.startUpdatingLocation()
latestCoord = locationManager.location!.coordinate
super.init()
locationManager.delegate = self
}
private func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
latestCoord = manager.location!.coordinate
}
public func getLatest() -> CLLocationCoordinate2D {
return latestCoord
}
}

Swift + CLLocationManager: How to tell if the user is in a specific city?

I use CLLocationManager to request the user's location. However, if they are outside of New York City, I want to default to certain coordinates. Is there a way to check if they are in a certain city?
import UIKit
import CoreLocation
import GoogleMaps
private let kDefaultLatitude: Double = 40.713
private let kDefaultLongitude: Double = -74.000
private let kDefaultZoomLevel: Float = 16.0
class RootMapViewController: UIViewController {
#IBOutlet weak var mapView: GMSMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
fetchLocation()
}
private func fetchLocation() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
}
// MARK: CLLocationManagerDelegate
extension RootMapViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationManager.stopUpdatingLocation()
let userCoordinates = locations[0].coordinate
// How do I check if the user is in NYC?
// if user is in nyc
centerMapOn(userCoordinates)
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
// else default to Times Square
}
}
You can use reverse geocoding. For example you can place:
geocoder:CLGeocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(locations[0],completionHandler{
if error == nil && placemarks.count > 0 {
let location = placemarks[0] as CLPlacemark
print(location.locality)
})
in func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])

Resources