I'm working on simple To-Do app which uses IOS 8 feature of CLRegion support in UILocalNotification.
Here is the code I'm using to schedule local notification:
var schedule = false
let notification = UILocalNotification()
/// Schedlue with date
if let reminder = self.dateReminderInfo {
schedule = true
notification.fireDate = reminder.fireDate
notification.repeatInterval = reminder.repeatInterval
notification.alertBody = self.title
}
/// Schedule with location
if let reminder = self.locationReminderInfo {
schedule = true
let region = CLCircularRegion(circularRegionWithCenter: reminder.place.coordinate, radius: CLLocationDistance(reminder.distance), identifier: taskObjectID)
region.notifyOnEntry = reminder.onArrive
region.notifyOnExit = reminder.onArrive
notification.region = region
notification.regionTriggersOnce = false
}
/// Schedule
if schedule {
notification.userInfo = ["objectID": taskObjectID]
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
Scheduling with date works. I schedule notification, exit the app and at the proper time notification is displayed on the screen, great. The problem is when I'm scheduling notification with the location. I pass coordinates and radius in meters (e.g. 100 meters). The app isn't displaying nothing. I was testing it out of the home placing the point in distance of 1km and getting there. No notification displayed. I was playing also with simulator and changing location from available there to custom location near my place and back. No notification displayed. Where is the problem?
In the AppDelegate I'm registering for notifications:
application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: UIUserNotificationType.Sound | UIUserNotificationType.Alert | UIUserNotificationType.Badge, categories: nil))
Here is the code of location services.
func startLocationService() {
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
let status = CLLocationManager.authorizationStatus()
if status == CLAuthorizationStatus.AuthorizedWhenInUse || status == CLAuthorizationStatus.Denied {
let title = (status == CLAuthorizationStatus.Denied) ? "Location services are off" : "Background location is not enabled"
let message = "To use background location you must turn on 'Always' in the Location Services Settings"
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
/// Settings
alertController.addAction(UIAlertAction.normalAction("Settings", handler: { _ in
UIApplication.sharedApplication().openURL(NSURL(string: UIApplicationOpenSettingsURLString)!)
return
}))
/// Cancel
alertController.addAction(UIAlertAction.cancelAction(String.cancelString(), handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
} else if status == CLAuthorizationStatus.NotDetermined {
/// nothing
}
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
CLLocationManager is working because I've got delegate method which is called very often.
/// Mark: CLLocationManagerDelegate
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let location = locations.first as? CLLocation {
self.navigationItem.title = "\(location.coordinate.latitude)" + " " + "\(location.coordinate.longitude)"
println(self.navigationItem.title)
}
}
I'm trying second day to solve the problem and can't find good solution how to display local notification when user appear in the place or leave the place. I don't know if using UILocalNotification region property is good for this solution or maybe I should create mechanism which will search through the places I've got saved in the app and check every time location change. Any comments to this topic are appreciated too ;)
Thank you in advance.
I see that you are not registering for the notifications
let settings = UIUserNotificationSettings(forTypes: notificationType, categories: categories)
application.registerUserNotificationSettings(settings)
You can find more information here iOS 8 Notifications in Swift and here CoreLocation and region Monitoring
I am having the same issue, however I've found a way to trigger the notification.
It looks like it is not responding to the radius parameter. The way I trigger the notification is by simulating the location being very far away from my region.
So if I set the region for a small city in Denmark, and move my location 1km, and back again, it does not trigger.
However, if I set the region for the same small city in Denmark, and move my location to London and back again, then it will trigger.
So for me it looks like the radius parameter is somehow disgarded? I tried with radius on 250.0, 100.0, 10.0, 0.0001, and it had no impact at all.
Can this maybe inspire someone else to what the problem can be and how to solve it?
Related
I've had an issue for like 3-4 months. I've tried everything you can ever imagine to get this to work, but I really can't. Now I'm looking for your help to fix this issue.
I've an application, when you press a start button it should get locations. (Works perfectly fine when ur on the application.)
But once you leave the application, (not killing the process) and goes to the background. The polyline is not drawing like it should. It pauses or something.
I need someone either who can help me here, or create a chatroom with me so we can discuss and I will send the rest of the code.
Here is parts of it, which I think is the most important.
Inside the viewDidLoad
let app = UIApplication.shared
NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive(notification:)), name: UIApplication.willResignActiveNotification, object: app)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive(notification:)), name: UIApplication.didBecomeActiveNotification, object: app)
-
#objc func applicationWillResignActive(notification: NSNotification)
{
start = CFAbsoluteTimeGetCurrent()
print("Background entered")
startReceivingSignificantLocationChanges()
}
#objc func didBecomeActive(notification: NSNotification)
{
let elapsed = CFAbsoluteTimeGetCurrent() - start
counter = counter + Int(elapsed)
print("Returned to application")
locationManager.stopMonitoringSignificantLocationChanges()
}
< Inside the start button.
//Checking userpermission to allow map and current location
if (CLLocationManager.locationServicesEnabled())
{
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.showsBackgroundLocationIndicator = true
//Retrieve current position
if let userLocation = locationManager.location?.coordinate
{
//Zooming in to current position
let viewRegion = MKCoordinateRegion(center: userLocation, latitudinalMeters: 200, longitudinalMeters: 200)
mapView.setRegion(viewRegion, animated: false)
//Creating a start annotation
if locations.isEmpty
{
let annotation = MKPointAnnotation()
annotation.title = "Start"
annotation.coordinate = userLocation
mapView.addAnnotation(annotation)
}
self.locations.append(userLocation)
print(self.locations, "First")
//Starts the walk-timer, with interval: 1 second
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCounter), userInfo: nil, repeats: true)
//Sending to update
update()
}
}
< Background worker
func startReceivingSignificantLocationChanges()
{
let authorizationStatus = CLLocationManager.authorizationStatus()
if authorizationStatus != .authorizedAlways
{
return
}
if !CLLocationManager.significantLocationChangeMonitoringAvailable()
{
// The service is not available.
return
}
else
{
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.distanceFilter = 100.0 //100.0 meters
locationManager.activityType = .fitness
locationManager.allowsBackgroundLocationUpdates = true
locationManager.delegate = self
locationManager.startMonitoringSignificantLocationChanges()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations
locations: [CLLocation])
{
let lastLocation = locations.last!
self.locations.append(lastLocation.coordinate)
print("Locations retrieved from background: ", self.locations)
}
There is a lot more I've to show you. But unfortunately it would be way too much...
Please enable the Background Modes from the capabilities of the project and enable the 'Location updates'. After enabling this, the only configuration to get the updates in the background(not in killed state) is to set'allowsBackgroundLocationUpdates' to true(which you have done already).
Here the significant location changes are only needed when you want to get the location when the application is killed by the user. This significant location change will launch the application in background and read the location of the device. For more information on getting location in the background follow :
https://developer.apple.com/documentation/corelocation/cllocationmanager/1620568-allowsbackgroundlocationupdates
For significant location changes while the application is in killed state, follow below link. This is in objective C but it can be easily done in swift also.
http://mobileoop.com/getting-location-updates-for-ios-7-and-8-when-the-app-is-killedterminatedsuspended
Hope this helps.
I am trying to get geofencing working in CoreLocation but even though I am standing right on top of the lat/long point, didEnterRegion never gets called.
Here's my code:
override func viewDidAppear(_ animated: Bool) {
myLocationManager = CLLocationManager()
myLocationManager.delegate = self
myLocationManager.desiredAccuracy = kCLLocationAccuracyBest
if CLLocationManager.authorizationStatus() == .notDetermined {
myLocationManager.requestAlwaysAuthorization()
} else if CLLocationManager.authorizationStatus() == .authorizedAlways {
let geoSet = arrLocSets[2]
let regionLocation: CLLocation = CLLocation(latitude: geoSet[0], longitude: geoSet[1])
let region = CLCircularRegion(center: regionLocation.coordinate, radius: 30, identifier: "region1")
region.notifyOnEntry = true
region.notifyOnExit = false
myLocationManager.startMonitoring(for: region)
print(myLocationManager.monitoredRegions)
}
if CLLocationManager.locationServicesEnabled() {
myLocationManager.startUpdatingLocation()
}
}
the print call logs this:
CLCircularRegion (identifier:'region1', center:<+40.20675900,-75.48613300>, radius:30.00m)]
which is correct.
the print call in
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("started monitoring!")
}
prints to the log so I know at least the monitoring started.
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print(region)
/*
if let region = region as? CLCircularRegion {
let myAlert = UIAlertController(title: "Entered Region", message: "You found the region \(region.identifier)", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
myAlert.addAction(okAction)
present(myAlert, animated: true, completion: nil)
}
*/
}
and here's a screen capture from my phone, where I was tracking lat/long. (Ignore the bottom labels, those are previous hard coded coordinates).
I put a break point in didEnterRegion and it never get triggered. 206851 and 206759 should be within a 30M diameter of each other? No? Did I miss a set up step?
didEnterRegion/didExitRegion happens on status change only. When starting to monitor for a region while being inside of the region you will not receive didEnterRegion.
Start walking out until didExitRegion happens, and this will "rearm" the region for didEnterRegion events, now you can walk back in.
Just do not be surprised that you will have to walk much more than 30m in each direction to trigger the events. Be prepared for up to 300 meters journey - really depends on your speed and (wireless) environment. Region monitoring does not involve GPS at all, it is all about wifi/cell tower triangulation - depends on coverage, quality of prior measurements and it is not "real time", as it is driven by wifi/cell scans.
I am developing a code that should get my location every 10 minutes and salvation in CoreData. When I walk into background with conectavo app to xcode can not see the log that the service is running, but when I go out walking the streets he simply not saved or saved too few times.
This is part of my code that should do this function.See save in codeData:
var saveLocationInterval = 60.0
func applicationDidEnterBackground(application: UIApplication) {
UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
self.timer = NSTimer.scheduledTimerWithTimeInterval(saveLocationInterval, target: self, selector: #selector(AppDelegate.saveLocation), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.timer, forMode: NSRunLoopCommonModes)
locationController.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
saveLocation()
}
func saveLocation(){
print("======")
let logUser = NSEntityDescription.insertNewObjectForEntityForName("LOG_GPS", inManagedObjectContext: self.managedObjectContext) as! LOG_GPS
if locationController.location == nil{
logUser.latitude = ""
logUser.longitude = ""
} else {
logUser.latitude = "\(locationController.location!.coordinate.latitude)"
logUser.longitude = "\(locationController.location!.coordinate.longitude)"
}
logUser.velocidade = userSpeed > 0 ? Int(userSpeed) : 0
logUser.address = "\(userSpeed)"
if _usuario.chave != nil {
logUser.chave_usuario = "\(_usuario.chave!)"
}
if _empresa.chave != nil {
logUser.chave_licenca = "\(_empresa.chave!)"
}
print("localizaƧao salva no bd \(logUser.latitude)")
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd/MM/yy HH:mm:ss"
let dateString = dateFormatter.stringFromDate(date)
logUser.data = dateString
do {
try self.managedObjectContext.save()
} catch {
}
}
Another major error in my code I can not solve is the User's speed. In the method the low I'm trying to save your speed in a variable and then save the CoreData however this is me always returning a negative value:
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation) {
var speed: CLLocationSpeed = CLLocationSpeed()
speed = newLocation.speed
print(speed * 3.6)
userSpeed = speed * 3.6
}
this is my background mode
Possible solution 1
You need a key in your Info.plist that describes why your app needs background location.
Go to your Info.plist, find the Bundle Version key and click the + that appears when you hover over that. Then add the key NSLocationAlwaysUsageDescription, set it to be a string, and set the value as whatever you want the description to be, like "We need your location in the background so we share your location with friends."
Now your app should work. If it doesn't....
Possible solution 2 (more likely solution if you know what you're doing)
With iOS 9, Apple made it so that apps on physical devices need a special line of code to run location services in the background. The change was not widely reported on (if at all?) but I managed to figure this one out a while ago. Here's what you need to do to get location services working in the background on physical devices again:
In your main location tracking view controller's ViewDidLoad put...
if #available(iOS 9.0, *) {
locationManager.allowsBackgroundLocationUpdates = true
} else {
// You don't need anything else on earlier versions.
}
This will (mysteriously enough) likely be all you need to solve your problem.
I am currently receiving location updates and want to periodically send an update to a server via an api call. Everything works when launching the app but updates in the background are really inconsistent.
Here's what I'm currently trying to do:
var lastUpdateTime: Double?
func locationManager(manager: CLLocationManager, didUpdateToLocation newLocation: CLLocation, fromLocation oldLocation: CLLocation)
{
userLocation = newLocation
if let lastUpdate = lastUpdateTime {
let thisUpdate = NSDate().timeIntervalSince1970
let timeInterval = thisUpdate - lastUpdate
if timeInterval > 60 {
if
let latitude = userLocation?.coordinate.latitude,
let longitude = userLocation?.coordinate.longitude
{
// Make API call to update location
lastUpdateTime = NSDate().timeIntervalSince1970
}
}
} else {
lastUpdateTime = NSDate().timeIntervalSince1970
}
}
I've read what I can find regarding background tasks and location updates but honestly I'm a bit lost. If it helps, the api call is made through Alamofire if that has an effect on how often it gets executed.
EDIT:
Added CLLocationManager options...
locationManager.delegate = self
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
For getting location updates in background , app should enable 'location update' background mode in capabilities tab in project settings.
Apart from this, required background location key(NSLocationAlwaysUsageDescription) needs to be added into the info.plist file. If these two things are done correctly, app will receive location updates continuously in background when call 'startUpdatingLocation'
I am developing a location based app which is supposed to fetch user location always.Im using standard location service. But the problem is that the app after keeping idle for some time in background will not fetch the coordinates even after we move to some other locations. As per apple documentation, when a new location arrives, app should wake up automatically, but that is not happening here. I'm sharing the code and using to fetch location and screenshot of my plist.
class SALocation: NSObject,CLLocationManagerDelegate
{
static let sharedInstance : SALocation = SALocation()
var locationManager : CLLocationManager!
var location : CLLocation!
var address : String!
var latitude : NSString?
var longitude : NSString?
var isAdderssLoaded : Bool = false
var locdictionary : NSMutableDictionary = NSMutableDictionary()
func startLocationManager()
{
if self.locationManager == nil
{
self.locationManager = CLLocationManager()
if CLLocationManager.locationServicesEnabled(){
print("location service enabled")
}
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
if ( Float (UIDevice.currentDevice().systemVersion) >= 9) {
if #available(iOS 9.0, *) {
self.locationManager.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
};
}
self.locationManager.startUpdatingLocation()
//self.locationManager.stopMonitoringSignificantLocationChanges()
}
else
{
self.locationManager.startUpdatingLocation()
}
}
// MARK: CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
UIAlertView(title:"Alert", message:error.description, delegate: nil, cancelButtonTitle:nil, otherButtonTitles:"Ok").show()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if locations.count > 0
{
self.location = locations[0]
/* storing date and location to plist
*/
let datenow = NSDate()
let dateformatternow = NSDateFormatter ()
dateformatternow.dateFormat = "yyyyMMdd HH:mm:ss"
let timenow:NSString = dateformatternow.stringFromDate(datenow)
let documetsdirectorypath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last
latitude = NSString(format: "%f",self.location.coordinate.latitude)
longitude = NSString (format: "%f",self.location.coordinate.longitude)
let latlong : NSString = NSString(format:"%#~%#",latitude!,longitude!)
NSUserDefaults.standardUserDefaults().setObject(latlong, forKey: "latlong")
let aFilePath = NSString(format: "%#/location.plist",documetsdirectorypath!)
locdictionary.setObject(latlong, forKey: timenow as String)
locdictionary.writeToFile(aFilePath as String, atomically: true)
///////////// ||storing date and location to plist code ends here||\\\\\\
// self.getAddressFromLocation(locations[0] )
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) != nil)
// {
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) as! NSString).isEqualToString(FalseString)
// {
// // self.locationManager.stopUpdatingLocation()
// }
// }
}
}
}
What i'm doing here is just get location and write it to a plist file. This works in foreground, background etc fine. But when i keep the app idle for 20 minutes, location is not fetched even if i move to some other locations as the app is suspended
Capabilities tab looks like this
To start location in background you must start background service from the following path
Click on your name -> Click on your app name (target) -> goto capabilities -> find the background mode -> enable the location update mode
I am not sure you started that or not because you not put any screenshot about this.
And also check that your user started background refresh in settings.refer below link for this.
Background App Refresh checking, enabling and disabling programatically for whole device and for each particular application in iOS 7
Update::
For location update in background used below link(objective c)
http://www.creativeworkline.com/2014/12/core-location-manager-ios-8-fetching-location-background/
Well, I don't know how you're getting location updates - significant-location change as example and how you exit from background.
I suggest checking if your app is truly in background mode - UIApplication.sharedApplication().applicationState as it can be terminated.
And I also suggest checking out Apple's Execution States for Apps. - especially for your possible use case Implementing Long-Running Tasks part. There is also a good tutorial at rayywenderlich.com called Background modes.
Please use
self.locationManager.requestAlwaysAuthorization()
and don't forget to update your Info.plist to define the NSLocationAlwaysUsageDescription key.