How to implement geofence triggered local notifications? - ios

I've created a simple geofence based application to monitor the movement of people entering and exiting a geofence and I'm trying to send a notification on these events but can't seem to implement it correctly.
I would really appreciate some explained sample code which would go in the view controller and app delegate.
P.s My knowledge of swift is somewhat limited but I understand most aspects necessary for this application.
Thanks for any help!
Edit:
This is my function for creating the notification which I think is correct.
func scheduleNotification() {
let centre = CLLocationCoordinate2DMake(51.364730, -0.189986)
let region = CLCircularRegion(center: centre, radius: 150, identifier: "SGS")
region.notifyOnEntry = true
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
let enterContent = UNMutableNotificationContent()
enterContent.title = "Enter"
enterContent.body = "Entered premesis"
enterContent.sound = UNNotificationSound.default()
let enterRequest = UNNotificationRequest(identifier: "enterNotification", content: enterContent, trigger: trigger)
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(enterRequest) {(error) in
if let error = error {
print("Error: \(error)")
}
}
}
I added this to the didFinishLaunchingWithOptions:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {(accepted, error) in
if !accepted {
print("Notification access denied.")
}
}
I know I need to call the function but I don't know where to do it so that it is called when the geofence is left. Also I'm not sure how to call it from the view controller as it is in the AppDelegate.
Sorry if I'm being stupid but thanks for the help!

You need to implement the location manager's didEnterRegion and didExitRegion delegate methods. Within your implementation of those methods is where you'll post the local notifications.
If you've already done that, make sure that the app has the proper capibility for posting notifications, and that you've registered the proper notification settings for the app in the app delegate.
Ray Wenderlich has a good tutorial on Geofencing and here's Apple's guide on local and remote push notifications.

Related

Are remote notifications possible for free provisioning account?

I'm slightly confused about the difference between "normal" push notifications vs. remote notifications, as well as which of them is possible with my free provisioning profile.
I'm able to send push notifications that appear on lock-screen with the following code:
AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions: [UIApplication.LaunchOptionsKey : Any]?) -> Bool {
...
registerForPushNotifications()
createNotification()
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
print("Permission granted: \(granted)")
guard granted else { return }
}
}
static func createNotification() {
let content = UNMutableNotificationContent()
content.title = "test-title"
// 2. create trigger
var components = DateComponents.init()
components.hour = 14
components.minute = 39
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: false)
content.badge = 1
content.sound = UNNotificationSound.default
// 4. create send request
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
// add request to send center
UNUserNotificationCenter.current().add(request) { error in
if error == nil {
print("Time Interval Notification scheduled!")
}
}
}
However, what I really want is to create a daily notification that is based on some HTTP request.
In other words, I would like to send an HTTP request to some API (say it returns a boolean value) and create a notification based on that value.
I've done some research and I think that remote notifications are capable of doing so.
Unfortunately, when I try to register for remote notifications:
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
I get an error: no valid “aps-environment” entitlement string found for application.
As I've stated - I do not have a paid Apple developer membership.
My questions are:
will remote notifications actually fulfill my needs?
Are remote notifications possible with free provisioning account?
I've found that "normal" push notifications are indeed possible.
Thanks!
It seems you have a misunderstanding about how remote push notifications work. Your server needs to schedule the remote notifications, not your app. You can schedule a daily remote notification on your server, which should suffice your needs, but as I've said, you'll need server-side logic to achieve this.
No - you need a paid developer membership to be able to use remote push notifications. Local notifications require no paid membership, but remote ones do.

How to prevent multiple location notifications from being triggered

I have multiple location notifications registered, often they overlap with each other. I want to limit to one fired notification per day, but the problem is that I do not have any way to cancel all the pending notifications when app is killed and can only do it when BGAppRefreshTaskRequest is triggered (which is not a common thing most of the times). And the worst part is that if user is in overlapping area he'll receive multiple push notifications at once.
Code to register notification:
let content = UNMutableNotificationContent()
content.title = "Title"
content.body = "Body"
content.sound = .default
content.userInfo = ["locationNotificationId" : id]
content.categoryIdentifier = "geofenceNotifications"
let region = CLCircularRegion(center: location,
radius: CLLocationDistance(range),
identifier: id)
region.notifyOnEntry = true
region.notifyOnExit = false
let trigger = UNLocationNotificationTrigger(region: region, repeats: false)
let request = UNNotificationRequest(identifier: id,
content: content,
trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: { error in
if let error = error {
print(error)
}
})
What do I do to prevent other notifications being triggered after one is fired if app is in the background or killed?
Ok, so I managed to resolve the issue by monitoring region itself instead of using local push notification with UNLocationNotificationTrigger. The downsides are - I need to ask location access for background instead of "when in use" and I'm limited to 20 regions but it still better than uncontrolled flow of push notifications.
To read more about region monitoring: https://www.raywenderlich.com/5470-geofencing-with-core-location-getting-started#toc-anchor-004

iBeacon Notification When App Closed

I'm trying to send the user a local notification when a region is entered (at an immediate distance) while the app is closed. I have it currently working if the app is in the background, but can't get it to work if the app is closed. I've read other posts that say this is possible, but none of the solutions work and they are outdated. I'd appreciate some help in Swift 3.
Here's my code (all in AppDelegate):
In didFinishLaunchingWithOptions:
locationManager.delegate = self
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.allowsBackgroundLocationUpdates = true
locationManager.requestAlwaysAuthorization()
let uuid = UUID(uuidString: "someuuid")!
let beaconRegion = CLBeaconRegion(proximityUUID: uuid, identifier: "SomeBeacon")
beaconRegion.notifyEntryStateOnDisplay = true
beaconRegion.notifyOnEntry = true
beaconRegion.notifyOnExit = true
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(in: beaconRegion)
I also have didRangeBeacons implemented.
The code looks correct to allow detection when the app is closed. You do not need:
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.allowsBackgroundLocationUpdates = true
But they should not hurt anything.
You only mention trouble with the app closed, so I assume foreground detection works fine. Does it? If not, troubleshoot this first.
It is often difficult to properly test the app closed use case leading to failures due to test setup issues. A few tips may help:
iOS will only send a region entry event if it thinks it is out of region. Often in testing it thinks it is in region so you do not get an event. To ensure you are out of region, turn off your beacon or go out of range with the app in the foreground and wait until you get an exit callback. Only then should you kill the app to test closed detection.
If rebooting your phone, always wait 5 minutes after startup to be sure CoreLocation is fully initialized. And make sure you have followed rule 1.
Make sure you do not have a bunch of other beacon apps installed on the phone taking up all the Bluetooth detection hardware acceleration slots. If you do, detections in the background can be delayed up to 15 min. Uninstall all beacon apps then uninstall and reinstall yours.
If you follow the testing tips above you should see detection callbacks within a couple of seconds of the beacon being in the vicinity.
UPDATE As #davidgyoung pointed out to me on a different question, this method of using the UNLocationNotificationTrigger won't report the major and minor identifiers to you because it is using the monitoring API and not ranging which is what you need to obtain the major and minor numbers. This limits what all you might do with this wrapper API. It sure is convenient, but not as helpful as one might hope.
The new (iOS 10, Swift 3) way from what I can tell is to use the UserNotifications library and implement it with a UNNotificationRequest. You can pass it a beacon region via UNLocationNotificationTrigger. Now I know it seems counter intuitive for what you're wanting (notification when app is closed), however, you want to add the NSLocationWhenInUseUsageDescription key to your Info.plist instead of NSLocationAlwaysUsageDescription. I'm not sure why it is that way, but it's a necessary step. Here's the code I'm using for creating a (iBeacon) location based notification:
// Ensure you use a class variable in order to ensure the location
// manager is retained, otherwise the alert message asking you to
// authorize it will disappear before you can tap "allow" which
// will keep it from working
let locationManager = CLLocationManager()
// ...
// Somewhere in your class (e.g. AppDelegate or a ViewController)
self.locationManager.requestWhenInUseAuthorization()
let region = CLBeaconRegion(proximityUUID: UUID(uuidString: "YOUR-UNIQUE-UUID-STRING")!, identifier: "com.yourcompany.youridentifier")
region.notifyOnEntry = true
region.notifyOnExit = false
let content = UNMutableNotificationContent()
content.title = "Notification Title"
content.body = "Body text goes here"
// Not sure if repeats needs to be set to true here, but that's
// how I've implemented it
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
// Use the same identifier each time to ensure it gets overwritten
// in the notification system
let identifier = "BeaconLocationIdentifier"
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
// This is probably unnecessary since we're using the same identifier
// each time this code is called
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
// Make sure you set the notification center's delegate. My
// containing class is the AppDelegate. I implement the delegate
// methods there.
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
})
// Not sure if this is required either, but after I added this, everything
// started working
self.locationManager.startRangingBeacons(in: region)
Make sure you implement the notification center delegate methods:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
// Called when the notification has been swiped or tapped by the user
// Do something with the response here
// Make sure you call the completion handler
completionHandler()
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
// Called when the app is in the foreground
// Do something with the notification here
// Make sure you call the completion handler
completionHandler([.alert, .sound])
}
Finally, you're going to need to authorize notifications first to get this all to work. You can use this code to do that:
let options: UNAuthorizationOptions = [.alert,.sound]
UNUserNotificationCenter.current().requestAuthorization(options: options) {
(granted, error) in
if !granted {
debugPrint("Something went wrong")
} else {
debugPrint("Notifications granted")
}
}
One last tip: I have an old metal tin that (sort of) acts as a Faraday cage for the beacon I'm using. I just close my beacon up in there for a minute or so and then the app detects that I've gone out of range. This is more convenient than trying to walk far enough away from the beacon or removing the battery (which doesn't seem to work at all for me). Some beacons have stronger signals than others and may still get through the tin, so YMMV.

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 10.2 UserNotifications problems on simple Alert and Badge

Before writing this post I made a lot of researches on UserNotification Framework, that substituted UILocalNotification in IOS 10. I also followed this tutorial to learn everything about this new feature : http://useyourloaf.com/blog/local-notifications-with-ios-10/.
Today I'm encountering so much troubles to implement such trivial Notifications and since it's a recent new feature I couldn't find any solutions (especially in objective C)! I currently have 2 different notifications, one Alert and one Badge updtate.
The Alert Issue
Before Updating my Phone from IOS 10.1 to 10.2, I made an alert on the Appdelegate that is triggered immediatly whenever the user closes the app manually:
-(void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"applicationWillTerminate");
// Notification terminate
[self registerTerminateNotification];
}
// Notification Background terminate
-(void) registerTerminateNotification {
// the center
UNUserNotificationCenter * notifCenter = [UNUserNotificationCenter currentNotificationCenter];
// Content
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.title = #"Stop";
content.body = #"Application closed";
content.sound = [UNNotificationSound defaultSound];
// Trigger
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
// Identifier
NSString *identifier = #"LocalNotificationTerminate";
// création de la requête
UNNotificationRequest *terminateRequest = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
// Ajout de la requête au center
[notifCenter addNotificationRequest:terminateRequest withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Error %#: %#",identifier,error);
}
}];
}
Before IOS 10.2 it worked just fine, when I closed the app manually, an alert showed up. But since I updated to IOS 10.2, nothing shows up without any reason, I havent change anything, and I can't see what's missing..
The Badge Issue
I also tried (only in IOS 10.2 this time) to implement badging on my app icon which worked just fine, until I tried to remove it. Here is the function that does it :
+(void) incrementBadgeIcon {
// only increment if application is in background
if ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground){
NSLog(#"increment badge");
// notif center
UNUserNotificationCenter *notifCenter = [UNUserNotificationCenter currentNotificationCenter];
// Content
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
content.badge = [NSNumber numberWithInt:1];
// Trigger
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
// Identifier
NSString *identifier = #"LocalNotificationIncrementBadge";
// request
UNNotificationRequest *incrementBadgeRequest = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
// Ajout de la requête au center
[notifCenter addNotificationRequest:incrementBadgeRequest withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"Error %#: %#",identifier,error);
}
}];
}
}
For now it does not increment the badge number, as it's name should suggest, but it just set the badge number to 1. Documentation says that if you set content.badge to 0, it removes it, but this does not work. I tried with other numbers, when I manually change it to '2', '3', etc... it changes, but if I set it to 0, it does not work.
Also, in the tutorial I linked earlier, it is mentionned several functions as getPendingNotificationRequests:completionHandler: and getDeliveredNotificationRequests:completionHandler:. I noticed that, when I call these functions right after calling incrementBadgeIcon, if the content.badge is set to '1', '2' etc... it appears in the pending notifications list. However, when I set it to 0, it does not appear anywhere. I get no error, no warning in Xcode, and my application badge still remain.
Does anyone know how I can fix these two Alerts?
Thank in advance
PS: I also tried to use removeAllPendingNotificationRequests and removeAllDeliveredNotifications for both without success.
Regarding the alert:
It's possible that your app is still in the foreground when your local notification fires, so you'll need to implement a delegate method in order for the notification to do anything. For instance, defining this method in your delegate will allow the notification to display an alert, make a sound, and update the badge:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert,.badge,.sound])
}
Regarding the badge:
I have observed that creating a UNMutableNotificationContent object and specifying only the badge value (as an NSNumber object) works for all badge values except 0 (i.e., you cannot clear the badge in this way). I haven't found any documentation for why 0 would behave differently than any other value, especially since the .badge property is defined as an NSNumber?, so the framework should be able to differentiate between nil (no change) and 0 (clear the badge).
I have filed a radar against this.
As a work around, I've found that setting the title property on the UNMutableNotificationContent object, with a badge value of NSNumber(value: 0) does fire. If the title property is missing, it will not fire.
Adding the title property still does not present an alert to the user (Update: this is no longer the case in iOS 11!), so this is a way to silently update the badge value to 0 without needing to invoke the UIApplication object (via UIApplication.shared.applicationIconBadgeNumber = 0).
Here's the entirety of the code in my example project; there's a MARK in the ViewController code showing where inserting the title property resolves the problem:
//
// AppDelegate.swift
// userNotificationZeroBadgeTest
//
// Created by Jeff Vautin on 1/3/17.
// Copyright © 2017 Jeff Vautin. All rights reserved.
//
import UIKit
import UserNotifications
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (success, error) -> Void in
print("Badge auth: \(success)")
}
// For handling Foreground notifications, this needs to be assigned before finishing this method
let vc = window?.rootViewController as! ViewController
let center = UNUserNotificationCenter.current()
center.delegate = vc
return true
}
}
//
// ViewController.swift
// userNotificationZeroBadgeTest
//
// Created by Jeff Vautin on 1/3/17.
// Copyright © 2017 Jeff Vautin. All rights reserved.
//
import UIKit
import UserNotifications
class ViewController: UIViewController, UNUserNotificationCenterDelegate {
#IBAction func start(_ sender: Any) {
// Reset badge directly (this always works)
UIApplication.shared.applicationIconBadgeNumber = 0
let center = UNUserNotificationCenter.current()
// Schedule badge value of 1 in 5 seconds
let notificationBadgeOneContent = UNMutableNotificationContent()
notificationBadgeOneContent.badge = NSNumber(value: 1)
let notificationBadgeOneTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 1*5, repeats: false)
let notificationBadgeOneRequest = UNNotificationRequest.init(identifier: "1", content: notificationBadgeOneContent, trigger: notificationBadgeOneTrigger)
center.add(notificationBadgeOneRequest)
// Schedule badge value of 2 in 10 seconds
let notificationBadgeTwoContent = UNMutableNotificationContent()
notificationBadgeTwoContent.badge = NSNumber(value: 2)
let notificationBadgeTwoTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 2*5, repeats: false)
let notificationBadgeTwoRequest = UNNotificationRequest.init(identifier: "2", content: notificationBadgeTwoContent, trigger: notificationBadgeTwoTrigger)
center.add(notificationBadgeTwoRequest)
// Schedule badge value of 3 in 15 seconds
let notificationBadgeThreeContent = UNMutableNotificationContent()
notificationBadgeThreeContent.badge = NSNumber(value: 3)
let notificationBadgeThreeTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 3*5, repeats: false)
let notificationBadgeThreeRequest = UNNotificationRequest.init(identifier: "3", content: notificationBadgeThreeContent, trigger: notificationBadgeThreeTrigger)
center.add(notificationBadgeThreeRequest)
// Schedule badge value of 4 in 20 seconds
let notificationBadgeFourContent = UNMutableNotificationContent()
notificationBadgeFourContent.badge = NSNumber(value: 4)
let notificationBadgeFourTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 4*5, repeats: false)
let notificationBadgeFourRequest = UNNotificationRequest.init(identifier: "4", content: notificationBadgeFourContent, trigger: notificationBadgeFourTrigger)
center.add(notificationBadgeFourRequest)
// Schedule badge value of 0 in 25 seconds
let notificationBadgeZeroContent = UNMutableNotificationContent()
// MARK: Uncommenting this line setting title property will cause notification to fire properly.
//notificationBadgeZeroContent.title = "Zero!"
notificationBadgeZeroContent.badge = NSNumber(value: 0)
let notificationBadgeZeroTrigger = UNTimeIntervalNotificationTrigger.init(timeInterval: 5*5, repeats: false)
let notificationBadgeZeroRequest = UNNotificationRequest.init(identifier: "0", content: notificationBadgeZeroContent, trigger: notificationBadgeZeroTrigger)
center.add(notificationBadgeZeroRequest)
}
#IBAction func listNotifications(_ sender: Any) {
let center = UNUserNotificationCenter.current()
center.getDeliveredNotifications() { (notificationArray) -> Void in
print("Delivered notifications: \(notificationArray)")
}
center.getPendingNotificationRequests() { (notificationArray) -> Void in
print("Pending notifications: \(notificationArray)")
}
}
// MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
print("Received notification: \(notification)")
completionHandler([.alert,.badge,.sound])
}
}
Well then I finally managed to make these two alerts functionning. As if posting this question on stackoverflow helped me to open my mind on that subject that hold me for the last few days (also these are really simple answers which is pretty shameful).
Here are my solutions if somebody come accross this post.
The Alert issue
For the alert that should show up when the app is closes, for instance when the app is killed by the user when in background, the code snippet is 'correct' overall. The point is that, when the appDelegate trigger the applicationWillTerminate: function, the system has already started to dealloc/dismantle the whole memory of your application. Therefore, if your app has many views loaded, and many datas to free, the thread that add the notification to the center has enough time to do it's task. But if the application has only few memory to dispose of, the notification is never added to the notifications center's queue.
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"applicationWillTerminate");
// Notification terminate
[Utils closePollenNotification];
// Pause the termination thread
[NSThread sleepForTimeInterval:0.1f];
}
So in my case I added a simple sleep in applicationWillTerminate right after creating the notification, which give enough time for it to be registered. (Note: I don't know if this is a good practice but it worked for me).
The Badge Issue
Obviously, after a better understanding of the Apple documentation, setting content.badge to 0 does not remove the previous badge set. It just tells the notification not to update the badge. To remove it, I simply had to call sharedApplication function :
//Reset badge icon
+(void) resetBadgeIcon {
NSLog(#"reset badge");
// remove basge
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
}
So simple.
Hope this can help somebody.
This fix only works on older versions of iOS. Use only for backward compatibility.
This is a fix for the badge issue OP describes (but not the alert issue).
Step 1: Send the notification with badge = negative 1
On your UNMutableNotificationContent set content.badge = #-1 instead of 0. This has 2 benefits:
unlike 0, -1 actually clears the badge (if you do step 2 as well)
unlike [UIApplication sharedApplication].applicationIconBadgeNumber = 0 it won't clear alerts from your app in notification center.
Step 2: Implement the UNUserNotificationCenterDelegate
You'll need to implement the UNUserNotificationCenterDelegate and do the following:
Implement the willPresentNotification method in your delegate, and call completionHandler(UNNotificationPresentationOptionBadge) in the body. Note: I check the request id, and call the default UNNotificationPresentationOptionNone unless this is specifically a notifications designed to clear the badge.
Don't forget to retain your delegate instance somewhere with a strong pointer. The notification center does not keep a strong pointer.
After these 2 changes, you can again clear the badge with a local notification.

Resources