trigger localNotification only when user enters a specified region - ios

This is my code for the didEnterRegion function. Problem is that the notification triggers while entering as well as exiting. What can I do to trigger the notification only when the user enters the location ?
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
var localNotification:UILocalNotification = UILocalNotification()
localNotification.alertBody = "you've entered rohini sector -8"
localNotification.region = region
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
NSLog("Entering region")
}

You don't need to specify the region in the local notification. You already know that the device is inside the region, because you had a call to didEnterRegion - specifying the region on the notification is redundant - You can simply post the notification without the region specified.

You are not specifying wether you want to monitor entering or exiting the region. When you create your region you set notifyOnExit or notifyOnEntry . If you only want exit then make that value true and the other false... or if only entry.. do the opposite
///// snipped taken from ray wenderlichs code...
// 1
let region = CLCircularRegion(center: geotification.coordinate,
radius: geotification.radius, identifier: geotification.identifier)
// 2
region.notifyOnEntry = false
region.notifyOnExit = true

Related

Properly launching an iOS app into the background from a location event

(iOS 11, Swift 4.1)
I wrote a cordova plugin in swift to handle region monitoring, but it isn't working properly when my app is suspended or killed. Here is my class with the relevant functions:
class GeofenceManager : NSObject, CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
switch state {
case .inside:
log ("Did Enter Region: " + region.identifier)
self.postGeofenceTransition(region: region, transitionType: 1)
break
case .unknown:
log ("Unknown Transition for region: " + region.identifier)
// self.postGeofenceTransition(region: region, transitionType: 1)
break
default:
break
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
log ("Did Exit Region: " + region.identifier)
self.postGeofenceTransition(region: region, transitionType: 2)
}
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
log("Did start monitoring region: " + region.identifier)
self.locationManager.requestState(for: region)
}
}
(I do it this way to handle someone that's already in the region when we start monitoring)
I've also got "didChangeAuthorizationStatus" that fetches and sets all the geofences, that is working fine.
In my AppDelegate (objc) I'm attempting to start Location Services if the app is opened from a region event, with
if([launchOptions objectForKey:UIApplicationLaunchOptionsLocationKey]) {
_locationManager = [[CLLocationManager alloc] init];
[_locationManager setDelegate:self];
[_locationManager setDistanceFilter:kCLHeadingFilterNone];
//change the desired accuracy to kCLLocationAccuracyBest
[_locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
//SOLUTION: set setPausesLocationUpdatesAutomatically to NO
[_locationManager setPausesLocationUpdatesAutomatically:NO];
[_locationManager startUpdatingLocation];
}
My question is, if my app is opened from an event, i.e. "didEnterRegion", I immediately start location services, but will I hit my "didEnterRegion" delegate again? And am I doing something wildly wrong here?
I think I'm missing some understanding of iOS application states, have dug into the docs but it hasn't clicked yet. Any insight is very appreciated.
My question is, if my app is opened from an event, i.e. "didEnterRegion", I immediately start location services, but will I hit my "didEnterRegion" delegate again?
If your app was suspended or terminated, and if it is awakened or launched just to receive didEnterRegion, it stays in the background. It is given time just to handle this one event and then it is suspended.
You cannot start location updates with startUpdatingLocation at that point. You are in the background! Even if you are authorized for background updates, you cannot start getting updates while you are in the background. And even if you could, your code would fail, because you never set allowsBackgroundLocationUpdates. And even if you could do that, you wouldn't get any events, because you have no implementation of didUpdateLocation.
The correct procedure is: do nothing. Just handle didEnterRegion and get out. If the user enters the region again, your didEnterRegion will be called again. Region monitoring just goes on forever until you stop it (and make sure you do stop it, or the user may be be forced to delete your app).
Addendum: How to respond to being launched from scratch in the background like this? You can detect the key in didFinishLaunchWithOptions and handle it there and return false, or you can ignore it and return true and receive didEnterRegion. But either way you must immediately create a location manager and appoint its delegate or you will get nothing, obviously. This is why you should always create the location manager and set its delegate in your app delegate or root view controller, something that always exists, and exists as early as possible in the life of the app, as soon as it launches.

Is this possible to activate app in terminate state if user enter into any region - iOS

I have implemented geofencing functionality in my App.
I get notified when user enter into particular region with below method
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if region is CLCircularRegion {
handleEvent(forRegion: region)
}
}
Above method is calling even in terminated state.
Is there any way to relaunch or activate application in terminated
state if enter into the region.

Receiving Location even when app is not running in Swift

Still very new to Swift. I have come from an Android background where there is BroadcastReceiver that can deliver location info to a service even though the app isn't running.
So I was looking for something similar in iOS/Swift and it appears that before this wasn't possible but it may be now. I am developing for iOS 10 but would be great if it was backwards compatible.
I found
startMonitoringSignificantLocationChanges
which I can execute to start delivering location updates, although this raises a few questions. Once I call this and my app is NOT running, are the updates still being sent ? And how would the app wake up to respond ?
Also restarting the phone and when it return, does this mean I still need call startMonitoringSignificantLocationChanges again meaning that I would have to wait for the user to execute my app. Or does it remember the setting after reboot ?
Still a little confused how to get around this, here's a brief explanation of what I am trying to do.
I would like to update the location of the phone even though the app is not running, this would be sent to a rest service every so often.
This way on the backend services I could determine if somebody is within X meters of somebody also and send them a push notification.
It may or may not be a good solution but if I were you I would have used both startMonitoringSignificantLocationChanges and regionMonitoring.
Here is the sample I made which worked well with iOS 13.
Lets take regionMonitoring first. We have certainly no problems when the app is in foreground state and we can use the CLLocationManager's didUpdate delegate to get the location and send it to the server.
Keep latest current location in AppDelegate's property, lets say:
var lastLocation:CLLocation?
//And a location manager
var locationManager = CLLocationManager()
We have two UIApplicationDelegates
func applicationDidEnterBackground(_ application: UIApplication) {
//Create a region
}
func applicationWillTerminate(_ application: UIApplication) {
//Create a region
}
So whenever the user kills the app or makes the app go to background, we can certainly create a region around the latest current location fetched. Here is an example to create a region.
func createRegion(location:CLLocation?) {
if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
let coordinate = CLLocationCoordinate2DMake((location?.coordinate.latitude)!, (location?.coordinate.longitude)!)
let regionRadius = 50.0
let region = CLCircularRegion(center: CLLocationCoordinate2D(
latitude: coordinate.latitude,
longitude: coordinate.longitude),
radius: regionRadius,
identifier: "aabb")
region.notifyOnExit = true
region.notifyOnEntry = true
//Send your fetched location to server
//Stop your location manager for updating location and start regionMonitoring
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoring(for: region)
}
else {
print("System can't track regions")
}
}
Make use of RegionDelegates
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("Entered Region")
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
print("Exited Region")
locationManager?.stopMonitoring(for: region)
//Start location manager and fetch current location
locationManager?.startUpdatingLocation()
}
Grab the location from didUpdate method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if UIApplication.shared.applicationState == .active {
} else {
//App is in BG/ Killed or suspended state
//send location to server
// create a New Region with current fetched location
let location = locations.last
lastLocation = location
//Make region and again the same cycle continues.
self.createRegion(location: lastLocation)
}
}
Here I have made a 50m region radius circle. I have tested this and it is called generally after crossing 100m from your center point.
Now the second approach can me using significantLocationChanges
On making the app go background or terminated, we can just stop location manager for further updating locations and can call the startMonitoringSignificantLocationChanges
self.locationManager?.stopUpdatingLocation()
self.locationManager?.startMonitoringSignificantLocationChanges()
When the app is killed, the location is grabbed from didFinishLaunching method's launchOptions?[UIApplicationLaunchOptionsKey.location]
if launchOptions?[UIApplicationLaunchOptionsKey.location] != nil {
//You have a location when app is in killed/ not running state
}
Make sure to keep BackgroundModes On for Location Updates
Also make sure to ask for locationManager?.requestAlwaysAuthorization() by using the key
<key>NSLocationAlwaysUsageDescription</key>
<string>Allow location</string>
in your Info.plist
There can be a third solution by taking 2 LocationManagers simultaneously.
For region
Significant Location Changes
As using significantLocationChanges
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.
as per the give Apple Doc
So it totally depends on your requirements as the location fetching depends on many factors like the number of apps opened, battery power, signal strength etc when the app is not running.
Also keep in mind to always setup a region with good accuracy.
I know that this will not solve your problem completely but you will get an idea to move forward as per your requirements.

Local Notifications After Device Restart

I start my app and schedule my local notifications. This is a simplified version of the code I'm using:
let content = UNMutableNotificationContent()
content.body = "Wild IBEACON appeared!"
let region = CLBeaconRegion(proximityUUID: uuid, identifier: "iBeacon region")
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
let request = UNNotificationRequest(identifier: "iBeacon notification", content: content, trigger: trigger)
notificationCenter.add(request)
They trigger while my app is in the background. So far, so good.
Then I restart the device. I don't force-quit the app.
And now the notifications don't trigger anymore. I need to open the app again.
Is there a way to let my schedules survive the restart?
The UNLocationNotificationTrigger is a new helper classes added in iOS10 to make it easier to trigger notifications based on beacon or geofence detections. According to the documentation, it is designed to be used only when the app is in use:
Apps must request access to location services and must have when-in-use permissions to use this class. To request permission to use location services, call the requestWhenInUseAuthorization() method of CLLocationManager before scheduling any location-based triggers.
https://developer.apple.com/reference/usernotifications/unlocationnotificationtrigger
Based on the above permissions, the app will only trigger when in use. The documentation does not explicitly say that it won't work in the background, so you might try requesting always location permission with the requestAlwaysAuthorization() instead of requestWhenInUseAuthorization() (be sure you put the correct key in your plist if you do this), to see if this helps.
An alternative would be to not use this helper class and instead manually start up CoreLocation and beacon monitoring, then create your own UILocalNotification manually when you get the region entry callback:
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let region = region as? CLBeaconRegion {
let notificationMessage = "Wild IBEACON appeared!"
let notification = UILocalNotification()
notification.alertBody = notificationMessage
notification.alertAction = "OK"
UIApplication.shared.presentLocalNotificationNow(notification)
}
}
The above approach is known to work across app restarts.

iOS: How to expire a local notification with a region trigger?

I have a local geofence notification, set up with the following code:
func startMonitoring(annotation:MKAnnotation) {
let region = CLCircularRegion(center: annotation.coordinate, radius: 10.0, identifier: "randomID")
region.notifyOnExit = false
let notification = UILocalNotification()
notification.region = region
notification.alertBody = "You got it!"
notification.category = self.notificationCategory
notification.soundName = "my_sound.wav"
notification.userInfo = ["ID": "randomID"]
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
This works great, but the user has to get to the area within a certain time limit for the action of the notification to be available. How can I make the notification NOT fire if the time limit has passed?
I currently only have "WhenInUse" authorization for the user's location and would prefer to keep it that way.
You'd be better off monitoring the CLCircularRegion manually using CLLocationManager, and when your app is notified that the user has entered the region, check whether it's in the time limit and post your UILocalNotification manually.
See the section Using Regions to Monitor Boundary Crossings here: https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html
You can setup background fetch with setminimumbackgroundfetchinterval , and cancel the scheduled notification if it's going to expire.
It's not 100% reliable because you cannot control exact time, but it's better than nothing.

Resources