Access user current location in custom Sirikit intends, iOS, swift - ios

I created a custom Sirikit intent, in IntentHandler class I can not find the location of user where location privacy is set by default 'Always'.
please look at the code.
import Foundation
import CoreData
import CoreLocation
class PhotoOfTheDayIntentHandler: NSObject, PhotoOfTheDayIntentHandling {
let context = CoreDataStorage.mainQueueContext()
var counter : DistanceEntity?
var locationManger = CLLocationManager()
func confirm(intent: PhotoOfTheDayIntent, completion: #escaping (PhotoOfTheDayIntentResponse) -> Void) {
completion(PhotoOfTheDayIntentResponse(code: .ready, userActivity: nil))
}
func handle(intent: PhotoOfTheDayIntent, completion: #escaping (PhotoOfTheDayIntentResponse) -> Void) {
self.context.performAndWait{ () -> Void in
let counter = NSManagedObject.findAllForEntity("DistanceEntity", context: self.context)
if (counter?.last != nil) {
self.counter = (counter!.last as! DistanceEntity)
let currentLocation: CLLocation = locationManger.location!
let greenLocation = CLLocation(latitude:self.counter!.latitude, longitude: self.counter!.longitude)
let distanceInMeters = currentLocation.distance(from: greenLocation) // result is in meters
debugPrint("distanceInMeters",distanceInMeters)
completion(PhotoOfTheDayIntentResponse.success(photoTitle: "\(distanceInMeters) Meter"))
completion(PhotoOfTheDayIntentResponse.success(photoTitle: "\(self.counter!.distance) Meter"))
}
}
}
}
if I comment the location manager it crash.

TLDR: Create CLLocationManager in the main thread and it should work
If you open the Console.app on your mac and monitoring the device you are running the Siri Intent on you will probably se a message similar to this:
A location manager (0xe86bdf0) was created on a dispatch queue executing on a thread other than the main thread.
(Just like in this question: location manager was created on a dispatch queue.)
The problem is that core location must be created in a run loop that is attached to the main loop. Easiest solution is to create the CLLocationManager in the main loop.
Here is an example intent handler that uses location.
import Foundation
import CoreLocation
class ExampleIntentHandler: NSObject, ExampleIntentIntentHandling, CLLocationManagerDelegate {
private var locationManager: CLLocationManager?
var onDidChangeAuthorization: ((ExampleIntentResponse) -> Void)?
var onDidUpdateLocations: ((ExampleIntentResponse) -> Void)?
func confirm(intent: CurrentSpeedIntent, completion: #escaping (CurrentSpeedIntentResponse) -> Void) {
DispatchQueue.main.async {
self.onDidChangeAuthorization = completion
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
self.locationManager?.requestWhenInUseAuthorization()
}
}
func handle(intent: CurrentSpeedIntent, completion: #escaping (CurrentSpeedIntentResponse) -> Void) {
DispatchQueue.main.async {
self.onDidUpdateLocations = completion
self.locationManager = CLLocationManager()
self.locationManager?.delegate = self
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager?.startUpdatingLocation()
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
switch status {
case .authorizedAlways, .authorizedWhenInUse:
let response = ExampleIntentResponse(code: .ready, userActivity: nil)
onDidChangeAuthorization?(response)
default:
let response = ExampleIntentResponse(code: .failure, userActivity: nil)
onDidChangeAuthorization?(response)
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else {
return
}
// Do something with the `location` but note that this
// method could be called multiple times by iOS. So if
// you do more that just responding, like fetching a
// photo, or manipulate something in your database you
// will probably set some kind of variable here and
// stop if that is already set.
//
// Example:
// guard intentHandled == false else { return }
// intentHandled = true
//
// The `intentHandled` must of course be a instance variable
// Don't forget to respond!
let response = ExampleIntentResponse(code: .success, userActivity: nil)
self.onDidUpdateLocations?(response)
}
}
This will also execute only when there actually is a location. I can see that you are force unwrapping your location which is a bad practice because it may be nil and then your intent will just crash. Here we will instead do what we need when we have the location.
Request to use location must also be made in the app first if that is not done yet.

Related

Timer is not allowing to add annotation on mapView in iOS swift

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.

Can't update label after getting location

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

Retrieve current location when application is in background

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()

CLLocationManager region monitoring delegate class issue

I have implemented a custom class around CoreLocation to do iBeacon region monitoring.
This class has some custom properties that i'm using to store some information related to beacons for later use (during entry and exit events).
The problem that I'm facing is when the app is terminated or kept in background these stored properties are no more available. By that I mean, let's say the app found a beacon region while in background/terminated, as usual the app will be launched in the background for us to process. I wanted to use the stored properties for custom operations during that time.
Did anyone faced this issue before? Am I doing this in a wrong way? Also, I am using this class from a cocoapod library that I'm currently working.
Below is the class that I wrote.
#available(iOS 9.0, *)
class BeaconManager: NSObject, CLLocationManagerDelegate {
//these properties are becoming nil
private var manager: CLLocationManager
private var lastDetection: NSDate?
private var isMonitoring = false
private var repository: [String: DBeacon]
private var monitoredRegions: [String: DBeacon] becoming nil
private var notifyBackground = true
static let sharedManager = BeaconManager()
weak var delegate:BeaconProtocol?
private override init() {
manager = CLLocationManager()
repository = [:]
monitoredRegions = [:]
if CLLocationManager.authorizationStatus() != .AuthorizedAlways {
manager.requestAlwaysAuthorization()
}
super.init()
manager.delegate = self
}
func startMonitoringForBeacon(beacon: Beacon) throws {
guard CLLocationManager.locationServicesEnabled() else {
CFLogger.ERROR("Location services not enabled")
throw BeaconErrorDomain.AuthorizationError(msg: "Location services not enabled")
}
guard CLLocationManager.authorizationStatus() == .AuthorizedAlways else {
switch CLLocationManager.authorizationStatus() {
case .Denied:
throw BeaconErrorDomain.AuthorizationError(msg: "User denied location services")
case .Restricted:
throw BeaconErrorDomain.AuthorizationError(msg: "App is prevented from accessing Location Services")
default:
throw BeaconErrorDomain.AuthorizationError(msg: "App doesn't have authorization to monitor regions")
}
}
guard CLLocationManager.isMonitoringAvailableForClass(CLBeaconRegion) else {
CFLogger.ERROR("Region monitoring not available on this device")
throw DBeaconKitErrorDomain.RegionMonitoringError(msg: "Region monitoring not available on this device")
}
guard let auuid = NSUUID(UUIDString: beacon.uuid) else {
throw BeaconErrorDomain.InvalidUUIDString
}
let region:CLBeaconRegion!
switch (beacon.major, beacon.minor) {
case (.None, .None):
region = CLBeaconRegion(proximityUUID: auuid, identifier: dbeacon.identifier)
case (.Some(let major), .None):
region = CLBeaconRegion(proximityUUID: auuid, major: UInt16(major), identifier: beacon.identifier)
case (.Some(let major), .Some(let minor)):
region = CLBeaconRegion(proximityUUID: auuid, major: UInt16(major), minor: UInt16(minor), identifier: beacon.identifier)
default:
throw BeaconErrorDomain.InvalidDBeaconInfo
}
region.notifyEntryStateOnDisplay = false
region.notifyOnEntry = true
region.notifyOnExit = true
repository[beacon.identifier] = beacon
manager.startMonitoringForRegion(region)
}
func stopMonitoringForBeacons(beacons: [Beacon]) {
guard isMonitoring else {
return
}
beacons.forEach { (dbeacon) -> () in
stopMonitoringForBeacon(beacon)
}
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
guard let handler = delegate else {
return
}
handler.initializationFailed(error)
}
func locationManager(manager: CLLocationManager, didStartMonitoringForRegion region: CLRegion) {
guard let aregion = region as? CLBeaconRegion, beacon = repository[aregion.identifier] else {
return
}
isMonitoring = true
monitoredRegions[aregion.identifier] = beacon
manager.requestStateForRegion(region)
}
func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) {
guard let aregion = region as? CLBeaconRegion, beacon = repository[aregion.identifier], handler = delegate else {
return
}
handler.monitoringFailedForRegion(beacon, error: error)
}
func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
guard let aregion = region as? CLBeaconRegion else {
return
}
guard let beacon = monitoredRegions[aregion.identifier] else {
return
}
guard let handler = delegate else {
print("Handler not available to report beacon entry event \(region.identifier)")
return
}
print("Entered beacon region \(beacon)")
handler.entered(beacon)
}
func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
guard let aregion = region as? CLBeaconRegion, beacon = monitoredRegions[aregion.identifier], handler = delegate else {
print("Handler not available to report beacon exit event \(region.identifier)")
return
}
print("Exited beacon region \(beacon)")
handler.exited(beacon)
}
}
I ended up finding that stored properties doesn't have the values that I set while initiating region monitoring.
Any help is truly appreciated.
Regards.
The properties are losing their initialization values on app restarts as #Paulw11 said. The typical way to handle this is to store these properties into NSUserDefaults. The snippet below shows how you would restore the lastDetection field in the bottom of the init method. A second method called save() would have to be called to persist that field once changed.
private override init() {
manager = CLLocationManager()
repository = [:]
monitoredRegions = [:]
if CLLocationManager.authorizationStatus() != .AuthorizedAlways {
manager.requestAlwaysAuthorization()
}
super.init()
manager.delegate = self
let userDefaults = NSUserDefaults.standardUserDefaults()
lastDetection = userDefaults.valueForKey("last_detection") as! NSDate?
}
func save() {
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setValue(lastDetection, forKey: "last_detection")
}
The example above only shows saving and restoring a single of your properties. You would need to do this with all of them, and some would be more complicated to deal with (like monitoredRegions and repository) because they are complex data types that can't directly be serialized to NSUserDefaults. To do this serialization, you might try using JSON to convert them to a string you can store and then parse them out from that same string.

CLLocationManager didUpdateLocations not being called

I am in the process of learning iOS 8 app development with Swift. I have followed a tutorial on Treehouse that walks you through building a weather app in Swift and iOS 8.
As an improvement to the app, the author/tutor suggests using CLLocationManager to get the location of the device to feed into the weather API instead of the hard coded latitude and longitude values.
So having read various tutorial online, I have gone ahead and attempted to implement this suggested improvement.
I have placed the code responsible for getting the location coordinates inside the AppDelegate.swift file.
AppDelegate.swift Code
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
var window: UIWindow?
var locationManager: CLLocationManager!
var errorOccured: Bool = false
var foundLocation: Bool = false
var locationStatus: NSString = "Not Started"
var location: CLLocationCoordinate2D?
var locationName: String?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
application.setStatusBarHidden(true, withAnimation: .None)
initializeLocationManager()
return true
}
func initializeLocationManager() {
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
println("didUpdateLocations running")
if (foundLocation == false) {
self.locationManager.stopUpdatingLocation()
foundLocation = true
var locationArray = locations as NSArray
var locationObj = locationArray.lastObject as CLLocation
var geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(locationObj, completionHandler: { (placemarks, error) -> Void in
var p = placemarks as NSArray
var placemark: CLPlacemark? = p.lastObject as? CLPlacemark
self.locationName = placemark?.name
})
self.location = locationObj.coordinate
}
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
locationManager.stopUpdatingLocation()
if ((error) != nil) {
if (errorOccured == false) {
errorOccured = true
print(error)
}
}
}
// authorization status
func locationManager(manager: CLLocationManager!,
didChangeAuthorizationStatus status: CLAuthorizationStatus) {
var shouldIAllow = false
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = "Restricted Access to location"
case CLAuthorizationStatus.Denied:
locationStatus = "User denied access to location"
case CLAuthorizationStatus.NotDetermined:
locationStatus = "Status not determined"
default:
locationStatus = "Allowed to location Access"
shouldIAllow = true
}
NSNotificationCenter.defaultCenter().postNotificationName("LabelHasbeenUpdated", object: nil)
if (shouldIAllow == true) {
NSLog("Location to Allowed")
// Start location services
locationManager.startUpdatingLocation()
} else {
NSLog("Denied access: \(locationStatus)")
}
}
}
And then in my ViewController.swift file I want to obtain the location coordinates. Here is the code:
ViewController.swift Code
func getCurrentWeatherData() -> Void {
let baseURL = NSURL(string: "https://api.forecast.io/forecast/\(apiKey)/")
var forecastURL: NSURL
var locName = "London"
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
appDelegate.foundLocation = false
if let loc = appDelegate.location {
println("Got Location!") // for debug purposes
var currentLat = loc.latitude
var currentLng = loc.longitude
forecastURL = NSURL(string: "\(currentLat),\(currentLng)", relativeToURL: baseURL)
locName = appDelegate.locationName!
} else {
println("No Location :(") // for debug purposes
var currentLat = "51.513445"
var currentLng = "-0.157828"
forecastURL = NSURL(string: "\(currentLat),\(currentLng)", relativeToURL: baseURL)
}
let sharedSession = NSURLSession.sharedSession()
let downloadTask: NSURLSessionDownloadTask = sharedSession.downloadTaskWithURL(forecastURL, completionHandler: { (location: NSURL!, response: NSURLResponse!, error: NSError!) -> Void in
var urlContents = NSString.stringWithContentsOfURL(location, encoding: NSUTF8StringEncoding, error: nil)
if (error == nil) {
let dataObject = NSData(contentsOfURL: location)
let weatherDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(dataObject, options: nil, error: nil) as NSDictionary
let currentWeather = Current(weatherDictionary: weatherDictionary)
dispatch_async(dispatch_get_main_queue(), {
() -> Void in
self.locationNameLabel.text = "\(locName)"
self.temperatureLabel.text = "\(currentWeather.temperature)"
self.iconView.image = currentWeather.icon!
self.currentTimeLabel.text = "At \(currentWeather.currentTime!) it is"
self.humidityLabel.text = "\(currentWeather.humidity)"
self.percipitationLabel.text = "\(currentWeather.percipProbability)"
self.summaryLabel.text = "\(currentWeather.summary)"
// Stop refresh animation
self.refreshActivityIndicator.stopAnimating()
self.refreshActivityIndicator.hidden = true
self.refreshButton.hidden = false
})
} else {
let networkIssueController = UIAlertController(title: "Error", message: "Unable to load data. Connectivity error!", preferredStyle: .Alert)
let okButton = UIAlertAction(title: "OK", style: .Default, handler: nil)
networkIssueController.addAction(okButton)
let cancelButton = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
networkIssueController.addAction(cancelButton)
self.presentViewController(networkIssueController, animated: true, completion: nil)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.refreshActivityIndicator.stopAnimating()
self.refreshActivityIndicator.hidden = true
self.refreshButton.hidden = false
})
}
})
downloadTask.resume()
}
The above is not working. My didUpdateLocations delegate never gets called. And in the debug console/output I always get No Location :( printed out, suggesting a failure in getting the location, more specifically suggesting that the location property on my AppDelegate is nil.
Things I have done to remedy this:
In the info.plist I have added the two keys NSLocationWhenInUseUsageDescription and NSLocationAlwaysUsageDescription
Ensured that I am connected via WiFi and not Ethernet
And countless other code tweaks, and still nothing.
A couple of observations:
As you point out, if you're going to call requestAlwaysAuthorization, then you must set NSLocationAlwaysUsageDescription. If you called requestWhenInUseAuthorization, you'd need NSLocationWhenInUseUsageDescription. (The fact that you see the confirmation dialog means that you've done this correctly. I assume you are seeing whatever description you supplied in the confirmation alert.)
On your simulator, you may not see location updates like on a device. Test this on an actual device.
When I used your code, I see didUpdateLocations when I called this from a device, but not from the simulator.
Once you solve the issue of not seeing didUpdateLocations being called, there is another issue:
You are posting a notification when the authorization status changes, but not when a location is received asynchronously (i.e. later). Frankly, the latter is the more critical event from the view controller's perspective, so I would have thought that (a) you should post a notification when the location is received; and (b) the view controller should observe this notification. Right now, even if you succeed in getting didUpdateLocations to be called, the view controller won't be notified of such.
Also, your didUpdateLocations is initiating yet another asynchronous process, the geocode of the coordinate. If your view controller needs that, too, you should post a notification inside the completion block of the geocoder.
Frankly, you haven't even shown us the view controller code that adds an observer for whatever notifications that this CLLocationManagerDelegate code will invoke, but I assume you have done that.
Just for the record: I first put the two keys (NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription) into the test-plist instead of the application-plist..Took me some time to realize.....

Resources