I have a situation where I have to call an API to fetch some Vehicles Locations objects in an array after getting the user current location. After fetching vehicles, I have to get the address also from Vehicles Locations data, so for 'n' Vehicles, there will be an 'n' API call and then add annotations on Map.
After that, I have to refresh the Vehicles data every 1 min. So, I created a timer but even after getting the API response, annotations are not displaying on map. Kindly look into this issue.
Below is Map View
import MapKit
class MapViewController: UIViewController, MKMapViewDelegate {
#IBOutlet private var mapView: MKMapView!
var currentLocation: CLLocation?
var user: User?
lazy var vehicleViewModel = {
VehicleViewModel()
}()
var locationUpdateTimer: Timer?
override func viewDidLoad() {
super.viewDidLoad()
configureLocationManager()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopTimer()
}
func configureLocationManager() {
LocationManager.shared().delegate = self
LocationManager.shared().initializeLocationManager()
}
func configureTimer() {
if locationUpdateTimer == nil {
locationUpdateTimer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(runLocationTimer), userInfo: nil, repeats: true)
}
}
#objc func runLocationTimer() {
fetchVehiclesLocation()
}
func resetMap() {
let annotations = mapView.annotations
mapView.removeAnnotations(annotations)
mapView = nil
}
func initializeMapView() {
mapView = MKMapView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height))
mapView.delegate = self
}
func configureMapView() {
let mapDetail = vehicleViewModel.getLatitudeLongitudeLatitudeDeltaLongitudeDelta()
if let latitude = mapDetail.0, let longitude = mapDetail.1, let latitudeDelta = mapDetail.2, let longitudeDelta = mapDetail.3 {
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), latitudinalMeters: latitudeDelta, longitudinalMeters: longitudeDelta)
let scaledRegion: MKCoordinateRegion = mapView.regionThatFits(region)
mapView.setRegion(scaledRegion, animated: true)
mapView.setCameraBoundary(
MKMapView.CameraBoundary(coordinateRegion: region),
animated: true)
let zoomRange = MKMapView.CameraZoomRange(maxCenterCoordinateDistance: 100000)
mapView.setCameraZoomRange(zoomRange, animated: true)
mapView.register(
VehicleAnnotationView.self,
forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
}
}
func fetchVehiclesLocation() {
configureTimer()
initViewModel {
DispatchQueue.main.async {
self.resetMap()
self.initializeMapView()
self.configureMapView()
}
if let user = self.user {
self.vehicleViewModel.fetchVehicleAddress(user: user, completion: { status in
if self.vehicleViewModel.vehicleAnnotationItems.count == 0 {
self.alertWithTitleAndMessageWithOK(("Alert" , "error while fetching vehicle locations"))
} else {
DispatchQueue.main.async {
self.mapView.addAnnotations(self.vehicleViewModel.vehicleAnnotationItems)
}
}
})
}
}
}
func initViewModel(completion: #escaping () -> Void) {
if let user = self.user, let userId = user.userId {
vehicleViewModel.getVehiclesLocation(userId: userId) { (vehicleApiResponse, error) in
if vehicleApiResponse != nil {
completion()
} else {
self.alertWithTitleAndMessageWithOK(("Alert" , error?.localizedDescription ?? "error while fetching vehicles"))
}
}
}
}
func stopTimer() {
if locationUpdateTimer != nil {
locationUpdateTimer!.invalidate()
locationUpdateTimer = nil
}
}
deinit {
stopTimer()
}
}
//MARK: - LocationManagerDelegate methods
extension MapViewController: LocationManagerDelegate {
func didFindCurrentLocation(_ location: CLLocation) {
currentLocation = location
if let currentLocation = currentLocation, (currentLocation.horizontalAccuracy >= 0) {
mapView.showsUserLocation = true
fetchVehiclesLocation()
}
}
}
LocationManager Extension class
import CoreLocation
protocol LocationManagerDelegate: AnyObject {
func didFindCurrentLocation(_ location: CLLocation)
func didFailedToFindCurrentLocationWithError(_ error: NSError?)
func alertLocationAccessNeeded()
}
/**
This class acts as a Singleton for getting location manager updates across the application.
*/
class LocationManager: NSObject {
var manager: CLLocationManager!
private static var sharedNetworkManager: LocationManager = {
let networkManager = LocationManager()
return networkManager
}()
private override init() {
super.init()
manager = CLLocationManager()
}
class func shared() -> LocationManager {
return sharedNetworkManager
}
weak var delegate: LocationManagerDelegate?
//Entry point to Location Manager. First the initialization has to be done
func initializeLocationManager() {
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.distanceFilter = kCLDistanceFilterNone
manager.delegate = self
manager.requestWhenInUseAuthorization()
manager.allowsBackgroundLocationUpdates = false
startUpdating()
}
//Start updating locations
func startUpdating() {
manager.startUpdatingLocation()
}
//Check for whether location services are disabled.
func locationServicesEnabled() -> Bool {
let isAllowed = CLLocationManager.locationServicesEnabled()
return isAllowed
}
}
//MARK: - CLLocation Manager delegate methods
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
manager.stopUpdatingLocation()
delegate?.didFindCurrentLocation(location)
// manager.delegate = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
delegate?.didFailedToFindCurrentLocationWithError(error as NSError?)
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .notDetermined:
self.manager.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse, .authorizedAlways:
if locationServicesEnabled() {
self.startUpdating()
}
case .restricted, .denied:
delegate?.alertLocationAccessNeeded()
#unknown default:
print("Didn't request permission for location access")
}
}
}
Your code has a number of problems.
Neither your initializeMapView() function nor your resetMap() function make any sense.
You should add an MKMapView to your Storyboard, then connect it to your mapView outlet, and then don't assign a new value to mapView. Don't set it to nil, and don't replace the map view in the outlet with a brand new map view you create (like you're doing in initializeMapView().) Both of those things will prevent your map from displaying.
You also never create a timer except in your fetchVehiclesLocation() function, which doesn't seem right.
You also don't show how you're setting up your location manager and asking for location updates. (You call a function initializeLocationManager(). I don't believe that is an Apple-provided function. I'm guessing you added it in an extension to the location manager, but you don't show that code.)
You need to ask if the user has granted permission to use the location manager, and trigger a request for permission if not.
Once you have permission to use the location manager, you need to ask it to start updating the user's location.
You don't show any of that code.
Maybe you're doing that in code you didn't show? It also looks like you don't have your CLLocationManagerDelegate methods defined correctly. I don't know of any delegate method didFindCurrentLocation(_:). You will likely need to implement one of the delegate methods locationManager(_:didUpdateLocations:) or locationManager(_:didUpdateTo:from:).
I suggest searching for a tutorial on using the location manager to display the user's location on a map. It's a little involved, and requires some study to set up.
Related
I've built an application where you can press a start button. Once the button is pressed the application will get user location every 10 second all the way till the stop button is pressed. When I leave the application or if the screen gets black, it will NOT get any more locations till I re-enter the application.
So, I'm currently trying to update the locations when the application is minimized. (I guess it's called in the background?), and also when the screen turns black. But my questions is:
Should I write this code in the AppDelegate?, if so. How can I know
if the button was pressed or not?
Where exactly in the AppDelegate should I add the code? And how can
I pass the locations back to the correct ViewController? (Since I
cannot make any prepare for segue from AppDelegate)
If you know the answers of this questions, please do not hesitate to answer them. :) I would really appreciate it!
The best way to get user's location in background is to use the Significant-Change Location Service according to apple's documentation put this func in your class:
func startReceivingSignificantLocationChanges() {
let authorizationStatus = CLLocationManager.authorizationStatus()
if authorizationStatus != .authorizedAlways {
// User has not authorized access to location information.
return
}
if !CLLocationManager.significantLocationChangeMonitoringAvailable() {
// The service is not available.
return
}
locationManager.delegate = self
locationManager.startMonitoringSignificantLocationChanges()
}
and also this func:
func locationManager(_ manager: CLLocationManager, didUpdateLocations
locations: [CLLocation]) {
let lastLocation = locations.last!
// Do something with the location.
}
so you just need to call startReceivingSignificantLocationChanges() inside your button and it will call locationManager(_ manager: CLLocationManager,didUpdateLocations locations: [CLLocation]), so do what you want with the location there.
Remember to ask permission to use location and to stop tracking with locationManager.stopMonitoringSignificantLocationChanges()
Take location permission for Always Allow
Set location manager for allowsBackgroundLocationUpdates true
from the above way you can get location in every location changes store this information and it send to server. Below is the sample code
typealias LocateMeCallback = (_ location: CLLocation?) -> Void
/*
LocationTracker to track the user in while navigating from one place to other and store new locations in locations array.
**/
class LocationTracker: NSObject {
static let shared = LocationTracker()
var lastLocation: CLLocation?
var locations: [CLLocation] = []
var previousLocation: CLLocation?
var isPreviousIsSameAsCurrent: Bool {
if let previous = previousLocation, let last = lastLocation {
return previous == last
}
return false
}
var isAggressiveModeOn = false
var locationManager: CLLocationManager = {
let locationManager = CLLocationManager()
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = true
locationManager.activityType = .automotiveNavigation
return locationManager
}()
var locateMeCallback: LocateMeCallback?
var isCurrentLocationAvailable: Bool {
if lastLocation != nil {
return true
}
return false
}
func enableLocationServices() {
locationManager.delegate = self
switch CLLocationManager.authorizationStatus() {
case .notDetermined:
// Request when-in-use authorization initially
locationManager.requestWhenInUseAuthorization()
case .restricted, .denied:
// Disable location features
print("Fail permission to get current location of user")
case .authorizedWhenInUse:
// Enable basic location features
enableMyWhenInUseFeatures()
case .authorizedAlways:
// Enable any of your app's location features
enableMyAlwaysFeatures()
}
}
func enableMyWhenInUseFeatures() {
locationManager.startUpdatingLocation()
locationManager.delegate = self
escalateLocationServiceAuthorization()
}
func escalateLocationServiceAuthorization() {
// Escalate only when the authorization is set to when-in-use
if CLLocationManager.authorizationStatus() == .authorizedWhenInUse {
locationManager.requestAlwaysAuthorization()
}
}
func enableMyAlwaysFeatures() {
enableCoarseLocationFetch()
locationManager.startUpdatingLocation()
locationManager.delegate = self
}
// Enable Rough Location Fetch
func enableCoarseLocationFetch() {
isAggressiveModeOn = false
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.distanceFilter = 100
}
// Enable Aggressive Location Fetch
func enableAggressiveLocationFetch() {
isAggressiveModeOn = true
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = 10
}
func locateMe(callback: #escaping LocateMeCallback) {
self.locateMeCallback = callback
if lastLocation == nil {
enableLocationServices()
} else {
callback(lastLocation)
}
}
func startTracking() {
enableLocationServices()
}
func stopTracking() {
locationManager.stopUpdatingLocation()
}
func resetPreviousLocation() {
previousLocation = nil
}
private override init() {}
}
extension LocationTracker: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print(locations)
guard let location = locations.first else { return }
guard -location.timestamp.timeIntervalSinceNow < 120, // Validate only location fetched recently
location.horizontalAccuracy > 0, // Validate Horizontal Accuracy - Ve means Invalid
location.horizontalAccuracy < 200 // Validate Horizontal Accuracy > 100 M
else {
print("invalid location received OR ignore old (cached) updates")
return
}
self.locations.append(location)
lastLocation = location
if let activeRide = RideManager.shared.activeRide,
let _ = AccessTokenHelper.shared.accessToken,
let activeRideId = activeRide.ride_id,
let type = activeRide.rideStatusTypeOptional,
type == .started {
//Store Location For A particular Ride after Start
LocationUpdater.shared.saveInDataBase(rideId: activeRideId, locations: [location])
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error.localizedDescription)
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
enableLocationServices()
}
}
/*
This class having responsibility of Updating the location on server after n second and update path after n second.
**/
class LocationTimer {
static let time: Double = 30
}
/*
class to update locations to server after nth second
**/
class LocationUpdater: NSObject {
static let shared = LocationUpdater(n: Double(LocationTimer.time), tracker: LocationTracker.shared)
let n: Double
private let tracker: LocationTracker
var timer: Timer! = nil
init(n: Double, tracker: LocationTracker) {
self.n = n
self.tracker = tracker
super.init()
}
func startUpdater() {
self.timer?.invalidate()
self.timer = nil
self.timer = Timer.scheduledTimer(timeInterval: n, target: self, selector: #selector(updateLocationsToServer), userInfo: nil, repeats: true)
self.timer.fire()
}
func stopUpdater() {
self.timer?.invalidate()
self.timer = nil
}
#objc func updateLocationsToServer() {
// update to server
}
}
// usage
LocationTracker.shared.startTracking()
LocationUpdater.shared.startUpdater()
I have a task to track the user's location in the background afterwards, and if its location has changed to more than 5 miles, then I need to update this data on the server. I know that you can start tracking user locations using startMonitoringSignificantLocationChanges. I started testing, launched the application with startMonitoringSignificantLocationChanges and allowsBackgroundLocationUpdates = true, then removed the application from the simulator memory, went into Maps and enabled Free Way simulation. For a minute I got 8 updates on the server, for me it's too often. I think for me, the best solution was if we ask what distance we want to receive updates from. I read a few posts about this, but not one did not solve my problem. I also thought that you can save the previous location and compare the changes with the new location, but I think this is a bad idea. Tell me, how to solve this problem better?
class LocationManager: NSObject {
private override init() {
super.init()
}
static let shared = LocationManager()
private let locationManager = CLLocationManager()
weak var delegate: LocationManagerDelegate?
// MARK: - Flags
private var isCallDidStartGetLocation = false
// MARK: - Measuring properties
private var startTimestamp = 0.0
// MARK: - Open data
var currentLocation: CLLocation?
// MARK: - Managers
private let locationDatabaseManager = LocationDatabaseManager()
// MARK: - Values
private let metersPerMile = 1609.34
func start() {
// measuring data
startTimestamp = Date().currentTimestamp
FirebasePerformanceManager.shared.getUserLocation(true)
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.activityType = .other
locationManager.distanceFilter = 100
locationManager.delegate = self
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways:
locationManager.startUpdatingLocation()
case .authorizedWhenInUse:
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
case .restricted, .notDetermined:
locationManager.requestAlwaysAuthorization()
case .denied:
showNoPermissionsAlert()
}
}
func logOut() {
locationManager.stopUpdatingLocation()
isCallDidStartGetLocation = false
}
}
// MARK: - Alerts
extension LocationManager {
private func showNoPermissionsAlert() {
guard let topViewController = UIApplication.topViewController() else { return }
let alertController = UIAlertController(title: "No permission",
message: "In order to work, app needs your location", preferredStyle: .alert)
let openSettings = UIAlertAction(title: "Open settings", style: .default, handler: {
(action) -> Void in
guard let URL = Foundation.URL(string: UIApplicationOpenSettingsURLString) else { return }
UIApplication.shared.open(URL, options: [:], completionHandler: nil)
})
alertController.addAction(openSettings)
topViewController.present(alertController, animated: true, completion: nil)
}
}
// MARK: - CLLocationManager Delegate
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .authorizedWhenInUse, .authorizedAlways:
locationManager.startUpdatingLocation()
default: break
}
delegate?.didChangeAuthorization?(manager: manager, didChangeAuthorization: status)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let lastLocation = locations.last else { return }
let timeInterval = abs(lastLocation.timestamp.timeIntervalSinceNow)
guard timeInterval < 60 else { return }
currentLocation = lastLocation
locationDatabaseManager.updateUserLocation(lastLocation)
measureGetLocationTime()
if !isCallDidStartGetLocation {
isCallDidStartGetLocation = true
delegate?.didStartGetLocation?()
}
}
}
// MARK: - Calculation
extension LocationManager {
func calculateDistanceFromCurrentLocation(_ venueLocation: CLLocation) -> Double {
guard let userLocation = locationManager.location else {
return 0.0
}
let distance = userLocation.distance(from: venueLocation)
let distanceMiles = distance / DistanceConvertor.metersPerMile //1609
return distanceMiles.roundToPlaces(places: 1)
}
}
// MARK: - Measuring functions
extension LocationManager {
private func measureGetLocationTime() {
FirebasePerformanceManager.shared.getUserLocation(false)
let endTimestamp = Date().currentTimestamp
let resultTimestamp = endTimestamp - startTimestamp
BugfenderManager.getFirstUserLocation(resultTimestamp)
}
}
I changed the current LocationManager and created two new managers for this case. I tested the application, after my changes and the results are as follows: I drove 120-130 km, two segments of the way were between cities, the application spent 1% of the device's charge, for us this is an acceptable result. The App sent 4 requests to the server with the update of the user's location, the conditions were as follows: after the previous update the location took 2 hours and the distance between the previous and the new location was 5 or more miles. You can see the implementation below.
LocationManager
import Foundation
import CoreLocation
class LocationManager: NSObject {
private override init() {
super.init()
manager.delegate = self
}
static let shared = LocationManager()
private let manager = CLLocationManager()
weak var delegate: LocationManagerDelegate?
// MARK: - Enums
enum DistanceValue: Int {
case meters, miles
}
// MARK: - Flags
private var isCallDidStartGetLocation = false
// MARK: - Measuring properties
private var startTimestamp = 0.0
// MARK: - Open data
var currentLocation: CLLocation?
// MARK: - Managers
private let locationDatabaseManager = LocationDatabaseManager()
// MARK: - Values
private let metersPerMile = 1609.34
func start() {
// measuring data
startTimestamp = Date().currentTimestamp
FirebasePerformanceManager.shared.getUserLocation(true)
manager.desiredAccuracy = kCLLocationAccuracyHundredMeters
manager.activityType = .other
manager.desiredAccuracy = 45
manager.distanceFilter = 100
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways:
if UIApplication.shared.applicationState != .background {
manager.startUpdatingLocation()
}
manager.startMonitoringSignificantLocationChanges()
manager.allowsBackgroundLocationUpdates = true
case .authorizedWhenInUse:
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
case .restricted, .notDetermined:
manager.requestAlwaysAuthorization()
case .denied:
showNoPermissionsAlert()
}
}
func logOut() {
manager.stopUpdatingLocation()
isCallDidStartGetLocation = false
}
}
// MARK: - Mode managing
extension LocationManager {
open func enterBackground() {
manager.stopUpdatingLocation()
manager.startMonitoringSignificantLocationChanges()
}
open func enterForeground() {
manager.startUpdatingLocation()
}
}
// MARK: - Alerts
extension LocationManager {
private func showNoPermissionsAlert() {
guard let topViewController = UIApplication.topViewController() else { return }
let alertController = UIAlertController(title: "No permission",
message: "In order to work, app needs your location", preferredStyle: .alert)
let openSettings = UIAlertAction(title: "Open settings", style: .default, handler: {
(action) -> Void in
guard let URL = Foundation.URL(string: UIApplicationOpenSettingsURLString) else { return }
UIApplication.shared.open(URL, options: [:], completionHandler: nil)
})
alertController.addAction(openSettings)
topViewController.present(alertController, animated: true, completion: nil)
}
}
// MARK: - CLLocationManager Delegate
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .authorizedWhenInUse, .authorizedAlways:
if UIApplication.shared.applicationState != .background {
manager.startUpdatingLocation()
}
default: break
}
delegate?.didChangeAuthorization?(manager: manager, didChangeAuthorization: status)
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let lastLocation = locations.last else { return }
let applicationState = UIApplication.shared.applicationState
switch applicationState {
case .active, .inactive:
activeAppGetLocation(lastLocation)
case .background:
backgroundAppGetLocation(lastLocation)
}
}
}
// MARK: - Gettings location functions
extension LocationManager {
private func activeAppGetLocation(_ location: CLLocation) {
let timeInterval = abs(location.timestamp.timeIntervalSinceNow)
guard timeInterval < 60 else { return }
currentLocation = location
locationDatabaseManager.updateUserLocation(location, state: .active)
if !isCallDidStartGetLocation {
measureGetLocationTime()
isCallDidStartGetLocation = true
delegate?.didStartGetLocation?()
}
}
private func backgroundAppGetLocation(_ location: CLLocation) {
let locationBackgroundManager = LocationBackgroundManager()
locationBackgroundManager.updateLocationInBackgroundIfNeeded(location)
}
}
// MARK: - Calculation
extension LocationManager {
func calculateDistanceBetweenLocations(_ firstLocation: CLLocation, secondLocation: CLLocation, valueType: DistanceValue) -> Double {
let meters = firstLocation.distance(from: secondLocation)
switch valueType {
case .meters:
return meters
case .miles:
let miles = meters / DistanceConvertor.metersPerMile
return miles
}
}
/// In miles
func calculateDistanceFromCurrentLocation(_ venueLocation: CLLocation) -> Double {
guard let userLocation = manager.location else {
return 0.0
}
let distance = userLocation.distance(from: venueLocation)
let distanceMiles = distance / DistanceConvertor.metersPerMile //1609
return distanceMiles.roundToPlaces(places: 1)
}
}
// MARK: - Measuring functions
extension LocationManager {
private func measureGetLocationTime() {
FirebasePerformanceManager.shared.getUserLocation(false)
let endTimestamp = Date().currentTimestamp
let resultTimestamp = endTimestamp - startTimestamp
BugfenderManager.getFirstUserLocation(resultTimestamp)
}
}
LocationBackgroundManager
import Foundation
import CoreLocation
import SwiftDate
class LocationBackgroundManager {
private var backgroundLocationUpdateTimestamp: Double {
get {
return UserDefaults.standard.double(forKey: "backgroundLocationUpdateTimestamp")
}
set {
UserDefaults.standard.set(newValue, forKey: "backgroundLocationUpdateTimestamp")
UserDefaults.standard.synchronize()
}
}
// MARK: - Managers
private lazy var locationStorageManager: LocationStorageManager = {
let locationStorageManager = LocationStorageManager()
return locationStorageManager
}()
open func updateLocationInBackgroundIfNeeded(_ location: CLLocation) {
if backgroundLocationUpdateTimestamp != 0 {
let currentLocationDate = location.timestamp
let previousDate = Date(timeIntervalSince1970: backgroundLocationUpdateTimestamp)
guard let hours = (currentLocationDate - previousDate).in(.hour) else { return }
guard hours >= 2 else { return }
if let previousLocationRealm = locationStorageManager.getCurrentUserPreviousLocation() {
let previousLocation = CLLocation(latitude: previousLocationRealm.latitude, longitude: previousLocationRealm.longitude)
let distance = LocationManager.shared.calculateDistanceBetweenLocations(location, secondLocation: previousLocation, valueType: .miles)
guard distance >= 5 else { return }
updateLocation(location)
} else {
updateLocation(location)
}
} else {
updateLocation(location)
}
}
private func updateLocation(_ location: CLLocation) {
let locationDatabaseManager = LocationDatabaseManager()
locationDatabaseManager.updateUserLocation(location, state: .background)
backgroundLocationUpdateTimestamp = location.timestamp.currentTimestamp
locationStorageManager.saveLocation(location)
}
}
LocationStorageManager
import Foundation
import CoreLocation
import RealmSwift
class LocationStorageManager {
func saveLocation(_ location: CLLocation) {
guard let currentUserID = RealmManager().getCurrentUser()?.id else { return }
let altitude = location.altitude
let latitude = location.coordinate.latitude
let longitude = location.coordinate.longitude
let locationRealm = LocationRealm(altitude: altitude, latitude: latitude, longitude: longitude, userID: currentUserID)
do {
let realm = try Realm()
try realm.write {
realm.add(locationRealm, update: true)
}
} catch {
debugPrint(error)
let funcName = #function
let file = #file
BugfenderManager.reportError(funcName, fileName: file, error: error)
}
}
func getCurrentUserPreviousLocation() -> LocationRealm? {
guard let currentUserID = RealmManager().getCurrentUser()?.id else { return nil }
do {
let realm = try Realm()
let previousLocation = realm.objects(LocationRealm.self).filter("userID == %#", currentUserID).first
return previousLocation
} catch {
debugPrint(error)
let funcName = #function
let file = #file
BugfenderManager.reportError(funcName, fileName: file, error: error)
return nil
}
}
}
According to Apple Docs:
Apps can expect a notification as soon as the device moves 500 meters or more from its previous notification. It should not expect notifications more frequently than once every five minutes. If the device is able to retrieve data from the network, the location manager is much more likely to deliver notifications in a timely manner.
startMonitoringSignificantLocationChanges() is the least accurate way to monitor location and there is no way to configure how often it's called as it's triggered in the event of a cell tower transition. Therefore it can trigger more often in areas with more densely located tower (cities). See this thread for more information.
I created a singleton class to handle location authorization because I needed it for several views in my app. So I created the below Location.swift class.
NOTE: I have added correctly into Info.plist, and have looked at several other posts but none seem to address this (at least none I found)
protocol LocationServiceDelegate {
func tracingLocation(currentLocation: CLLocation)
func tracingLocationDidFailWithError(error: NSError)
}
class Location: NSObject,CLLocationManagerDelegate {
var latitude: Double!
var longitude: Double!
var currentLocation : CLLocation!
var locationManager: CLLocationManager?
var lastLocation: CLLocation?
var delegate: LocationServiceDelegate?
static let sharedInstance:Location = {
let instance = Location()
return instance
}()
override init() {
super.init()
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
guard let locationManagers = self.locationManager else {
return
}
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManagers.requestWhenInUseAuthorization()
}
locationManagers.desiredAccuracy = kCLLocationAccuracyBest
locationManagers.pausesLocationUpdatesAutomatically = false
locationManagers.distanceFilter = 0.1
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
self.lastLocation = location
updateLocation(currentLocation: location)
}
#nonobjc func locationManager(manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
switch status {
case .notDetermined:
locationManager?.requestWhenInUseAuthorization()
break
case .authorizedWhenInUse:
locationManager?.startUpdatingLocation()
break
case .authorizedAlways:
locationManager?.startUpdatingLocation()
break
case .restricted:
// restricted by e.g. parental controls. User can't enable Location Services
break
case .denied:
// user denied your app access to Location Services, but can grant access from Settings.app
break
}
}
// Private function
private func updateLocation(currentLocation: CLLocation){
guard let delegate = self.delegate else {
return
}
delegate.tracingLocation(currentLocation: currentLocation)
}
private func updateLocationDidFailWithError(error: NSError) {
guard let delegate = self.delegate else {
return
}
delegate.tracingLocationDidFailWithError(error: error)
}
func startUpdatingLocation() {
print("Starting Location Updates")
self.locationManager?.startUpdatingLocation()
currentLocation = locationManager?.location
Location.sharedInstance.latitude = currentLocation.coordinate.latitude
Location.sharedInstance.longitude = currentLocation.coordinate.longitude
print(Location.sharedInstance.latitude, Location.sharedInstance.longitude)
// self.locationManager?.startMonitoringSignificantLocationChanges()
}
func stopUpdatingLocation() {
print("Stop Location Updates")
self.locationManager?.stopUpdatingLocation()
}
}
My app is crashing, and I think its because the location authorization is not set in the beginning. The funny thing is that the request alert which prompts the user to allow location services doesn't show up until you leave the app.
Once you close the app and accept the location services, the app works fine. So my question is, why isn't the alert showing up?
it is also interesting to note that this is only occurring through an actual device. In the simulator the alert pops up as expected when the initial view is loading.
my first view that is supposed to load and show data is as follows:
import UIKit
import Alamofire
class CurrentWeatherVC: UIViewController {
#IBOutlet weak var locationLabel: UILabel!
#IBOutlet weak var weatherIcon: UIImageView!
#IBOutlet weak var currentTempLabel: UILabel!
#IBOutlet weak var weatherTypeLabel: UILabel!
var currentWeather : CurrentWeather!
override func viewDidLoad() {
super.viewDidLoad()
Location.sharedInstance.locationManager(manager: Location.sharedInstance.locationManager, didChangeAuthorizationStatus: .authorizedWhenInUse)
currentWeather = CurrentWeather()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Location.sharedInstance.startUpdatingLocation()
currentWeather.downloadWeatherDetails {
self.updateMainUI()
}
}
func updateMainUI() {
//Double value convterted to string for current temp.
//Added the degree symbol here
//For forecast it gets added in before saved into list so be aware of that.
currentTempLabel.text = "\(currentWeather.currentTemp)°"
weatherTypeLabel.text = currentWeather.weatherType
locationLabel.text = currentWeather.cityName
weatherIcon.image = UIImage(named: currentWeather.weatherType)
}
}
I suspect downloadWeatherDetailss implementation uses a dataTask or one of the other NSURLSession methods that run in background.
Make sure to call UI stuff only on the mainQueue:
// ...
DispatchQueue.main.async {
self.updateMainUI()
}
// ...
I'm making an app for one of my courses. It is supposed to track the distance traveled and update a label to show how far they've gone. When I open the app it asks for permission to track location. The mapView works, it follows the location but the label is never updated to show the distance traveled. I've added my code below, any help is greatly appreciated!
//
// ViewController.swift
// location_tracker
//
// Created by Dale McCaughan on 2016-10-19.
// Copyright © 2016 Dale McCaughan. All rights reserved.
//
import UIKit
import CoreLocation
import MapKit
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var theMap: MKMapView!
#IBOutlet weak var l: UILabel!
let locationManager = CLLocationManager()
var startLocation: CLLocation!
var monitoredRegions: Dictionary<String, NSDate> = [:]
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
self.navigationController?.isToolbarHidden = false;
//Status bar style and visibility
UIApplication.shared.statusBarStyle = .lightContent
//Change status bar color
let statusBar: UIView = UIApplication.shared.value(forKey: "statusBar") as! UIView
//if statusBar.respondsToSelector("setBackgroundColor:") {
statusBar.backgroundColor = UIColor.white
//}
UIToolbar.appearance().backgroundColor = UIColor.white
}
override func viewDidLoad() {
super.viewDidLoad()
//Setup the Location Manager
locationManager.delegate = self;
locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
//Setup the Map View
theMap.delegate = self
theMap.showsUserLocation = true
theMap.userTrackingMode = .follow
// setup test data
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// status is not determined
if CLLocationManager.authorizationStatus() == .notDetermined {
locationManager.requestAlwaysAuthorization()
}
// authorization were denied
else if CLLocationManager.authorizationStatus() == .denied {
showAlert("Location services were previously denied. Please enable location services for this app in Settings.")
}
// we do have authorization
else if CLLocationManager.authorizationStatus() == .authorizedAlways {
locationManager.startUpdatingLocation()
}
}
// MARK: - MKMapViewDelegate
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let circleRenderer = MKCircleRenderer(overlay: overlay)
circleRenderer.strokeColor = UIColor.red
circleRenderer.lineWidth = 1.0
return circleRenderer
}
#IBAction func resetDistance(_ sender: AnyObject) {
startLocation = nil
}
// MARK: - CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
showAlert("enter \(region.identifier)")
monitoredRegions[region.identifier] = Date() as NSDate?
l.text = "in location manager1"
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
showAlert("exit \(region.identifier)")
monitoredRegions.removeValue(forKey: region.identifier)
l.text = "in location manager2"
}
var lastLocation: CLLocation!
var traveledDistance:Double = 0
func locationManager(manager:CLLocationManager, didUpdateLocations locations:[AnyObject]) {
if let firstLocation = locations.first as? CLLocation
{
theMap.setCenter(firstLocation.coordinate, animated: true)
let region = MKCoordinateRegionMakeWithDistance(firstLocation.coordinate, 1000, 1000)
theMap.setRegion(region, animated: true)
if let oldLocation = lastLocation {
let delta: CLLocationDistance = firstLocation.distance(from: lastLocation)
traveledDistance += delta
}
lastLocation = firstLocation
}
l.text = String(format: "%.3f", traveledDistance/1000) + " kilometers"
}
// MARK: - Helpers
func showAlert(_ title: String) {
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Try this:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let firstLocation = locations.first
The delegate signature that you were using wasn't quite what the delegate was looking for. With the signature corrected, you don't need the as CLLocation cast anymore.
I verified that it works with that change. For future reference, you could set a breakpoint at didUpdateLocations and you would see right away that it wasn't getting called and then work backwards from there.
Also make sure that Info.plist contains all the necessary location-related Privacy strings (I usually just put in all three to cover all the bases).
How can you stop getting the user location when using CLLocationManager and mapbox?
I have a application that does the following:
1) Gets the users current location with the CLLocationManager and then calls the command ".stopUpdatingLocation()" which stops getting the user location.
2) Creates a map with mapbox
As soon as the application has both, it does NOT stop getting the user location.
I tested the application in the each separate scenarios (option 1 above alone and option 2 alone) and it successfully stop getting the user location but when the application has both implemented it does NOT stop getting the user location.
viewController.swift:
import UIKit
import MapboxGL
import CoreLocation
class ViewController: UIViewController, MGLMapViewDelegate , CLLocationManagerDelegate {
//MARK: - Properties
var manager: CLLocationManager?
private var currentLocation: CLPlacemark?
private var currLocLatitude:CLLocationDegrees?
private var currLocLongitude:CLLocationDegrees?
private var currLocTitle:String?
private var currLocSubtitle:String?
private var MapBoxAccessToken = "AccessToken.GoesHere"
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
manager = CLLocationManager()
manager?.delegate = self
manager?.desiredAccuracy = kCLLocationAccuracyBest
manager?.requestWhenInUseAuthorization()
manager?.startUpdatingLocation()
}
//MARK: - Helper
/* gather location information */
func getLocationInfo(placemark: CLPlacemark) {
currentLocation = placemark //will delete later - redudant
currLocLatitude = placemark.location.coordinate.latitude
currLocLongitude = placemark.location.coordinate.longitude
currLocTitle = placemark.areasOfInterest[0] as? String
currLocSubtitle = placemark.locality
//DEBUGGING
print(placemark.location.coordinate.latitude)
print(placemark.location.coordinate.longitude)
print(placemark.areasOfInterest[0])
print(placemark.locality)
}
//MARK: - CLLocationManagerDelegate
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
manager.stopUpdatingLocation()
let location = locations[0] as? CLLocation
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(manager.location, completionHandler: {(placemarks, error) -> Void in
if (error != nil) {
println("ERROR:" + error.localizedDescription)
return
}
if placemarks.count > 0 {
var currLocation = placemarks[0] as! CLPlacemark
self.getLocationInfo(currLocation)
self.createMapBoxMap()
} else {
print("Error with data")
}
})
}
func locationManager( manager: CLLocationManager!, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
print(" Authorization status changed to \(status.rawValue)")
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError) {
print("Error:" + error.localizedDescription)
}
//MARK: - MapBox Methods
private func createMapBoxMap(){
//type of map style
let mapView = MGLMapView(frame: view.bounds, accessToken: MapBoxAccessToken)
//dark map style
// let mapView = MGLMapView(frame: view.bounds, accessToken: "pk.eyJ1IjoibHVvYW5kcmUyOSIsImEiOiI4YzAyOGMwOTAwMmQ4M2U5MTA0YjliMjgxM2RiYzk0NSJ9.isuNZriXdmrh-n9flwTY9g",styleURL: NSURL(string: "asset://styles/dark-v7.json"))
mapView.autoresizingMask = .FlexibleWidth | .FlexibleHeight
//setting the map's center coordinate
mapView.setCenterCoordinate(CLLocationCoordinate2D(latitude: currLocLatitude!, longitude: currLocLongitude!),
zoomLevel: 25, animated: false)
view.addSubview(mapView)
/*define the marker and its coordinates, title, and subtitle:*/
mapView.delegate = self // Set the delegate property of our map view to self after instantiating it.
// Declare the marker `ellipse` and set its coordinates, title, and subtitle
let ellipse = MyAnnotation(location: CLLocationCoordinate2D(latitude: currLocLatitude!, longitude: currLocLongitude!),
title: currLocTitle!, subtitle: currLocSubtitle!)
mapView.addAnnotation(ellipse) // Add marker `ellipse` to the map
}
//MARK: - MGLMapViewDelegate
/* defining the marker from MyAnnotation.swift */
func mapView(mapView: MGLMapView!, symbolNameForAnnotation annotation: MGLAnnotation!) -> String! {
return "secondary_marker"
}
/* Tapping the marker */
func mapView(mapView: MGLMapView!, annotationCanShowCallout annotation: MGLAnnotation!) -> Bool {
return true
}
}
AppDelegate.swift:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
return true
}
}
MyAnnotation.swift:
import Foundation
import MapboxGL
class MyAnnotation: NSObject, MGLAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String!
var subtitle: String!
init(location coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
You are calling the manager returned in the function, try call self.manager.stopUpdatingLocation()
Resolved this issue by getting the user location inside the "ViewDidLoad" method and creating the map inside the "ViewDidAppear" method
By having them seperated, it seems to have resolve the problem.
import UIKit
import CoreLocation
import MapboxGL
class AViewController: UIViewController, CLLocationManagerDelegate {
var manager:CLLocationManager!
var userLocation:CLLocation!
override func viewDidLoad() {
super.viewDidLoad()
getUserLocation()
}//eom
override func viewDidAppear(animated: Bool) {
createMapBoxMap()
}
/*getting user current location*/
func getUserLocation(){
self.manager = CLLocationManager()
self.manager.delegate = self
self.manager.desiredAccuracy = kCLLocationAccuracyBest
self.manager.requestWhenInUseAuthorization()
self.manager.startUpdatingLocation()
}//eom
/*location manager 'didUpdateLocations' function */
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
self.manager.stopUpdatingLocation() //stop getting user location
println(locations)
self.userLocation = locations[0] as! CLLocation
}//eom
/*Create preliminary map */
func createMapBoxMap(){
// set your access token
let mapView = MGLMapView(frame: view.bounds, accessToken: "pk.eyJ1IjoiZGFya2ZhZGVyIiwiYSI6IlplVDhfR3MifQ.pPEz732qS8g0WEScdItakg")
mapView.autoresizingMask = .FlexibleWidth | .FlexibleHeight
// set the map's center coordinate
mapView.setCenterCoordinate(CLLocationCoordinate2D(latitude: self.userLocation.coordinate.latitude, longitude: self.userLocation.coordinate.longitude),
zoomLevel: 13, animated: false)
view.addSubview(mapView)
//showing the user location on map - blue dot
mapView.showsUserLocation = true
}//eom