I'm new to notifications in swift and have setup the following in my AppDelegate.swift file. It runs and I get print output that the notifications are setup and do not error. I also get the permission ask for notifications, which I click yes to. I believe the interval notification I have should trigger in 20 seconds after launch. It does not trigger, this doesn't change if I'm in the app or have minimized the app. Note I am running in Simulator mode from XCODE. Why is my notification not triggering?
func setupNotifications(){
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Yay!")
} else {
print("D'oh")
}
}
notificationCenter.removeAllPendingNotificationRequests()
print("setting up notifications")
let content = UNMutableNotificationContent()
content.title = "Answer your daily questions"
content.body = "Answer your daily questions please"
content.categoryIdentifier = "alarm"
content.userInfo = ["customData": "fizzbuzz"]
content.sound = UNNotificationSound.default
//let trigger = UNTimeIntervalNotificationTrigger(timeInterval: (1*60), repeats: true)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 20, repeats: false)
// Create the request
let request = UNNotificationRequest(identifier: "daily-notifier",
content: content, trigger: trigger)
// Schedule the request with the system.
notificationCenter.add(request) { (error) in
if error != nil {
print("there were errors to address")
// Handle any errors.
}
}
print("done setting up notifications")
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let path = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)
print("\(path)")
// Override point for customization after application launch.
setupNotifications()
return true
}
First note that you should probably wait to receive permission before adding a request for a local notification as per Apple's documentation of the requestAuthorization(options:completionHandler:)
Note: Always call this method before scheduling any local notifications and before registering with the Apple Push Notification service.
Also make sure that you've added Push Notifications as a capability for your app as this will be needed should you submit it.
After I ran the above code I did not receive a notification while the app was still active but I did receive one while the app was in the background. I believe that if a user receives a notification for an app that is in the active state it is given silently; i.e., without the banner and sound (at least this is what I have experienced with my notifications, although there may be more to it).
Related
I have an app with push notifications and RealmDB. User himself chooses the date of notifications after that the notification date goes to the RealmDB. Every time a user receives a notification, the date of the next notification changes, and the user can see the date in Table View. Problem is...after the user has received the notification, he goes to the application through the push notification so func doesn't work. If user go to app by open it - everything is OK.
In cellForRowAt:
center.getDeliveredNotifications { (notifications) in
for notification:UNNotification in notifications {
print(notification.request.identifier)
if (notification.request.identifier == timeId){
let today = notification.date
let realNextDate = Calendar.current.date(byAdding: .day, value: timeInterval!, to: today)
let realm = try! Realm()
try! realm.write {
for plants in realm.objects(MyPlant.self).filter("id == %#", timeId as Any) {
plants.nextDate = realNextDate
plants.prevDate = today
}
}
}
}
}
The reason behind this I believe is that you are configuring Realm in the AppDelegate with
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
This is what is executed when you open the app by tapping on the App icon. In the case where you open the app by tapping the push notification, the above AppDelegate method isn't executed. Instead a method shown below is executed,
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
// unused
}
Due to this, you will have to do some configurations in this method as well for it to work when tapped on the notification.
My app's deployment target is 10.0, and I used UNUserNotificationCenter to show region notification even when the app is closed/killed. But new mission is to show it at most once a month, though the user may enter the region more than once a month.
What I tried until now (which worked great) is...
let content = UNMutableNotificationContent()
content.title = "... Reminder"
content.body = "Welcome to \(element.name). Please let us know how we can serve you and your loved ones, and we hope ... will simplify your visit here."
content.sound = UNNotificationSound.default
content.categoryIdentifier = "LOCATION_CAT"
let centerCoordinate2D = element.location.coordinate
let identifierName = element.name.replacingOccurrences(of: " ", with: "_")
let region = CLCircularRegion(center: centerCoordinate2D, radius: 300, identifier: identifierName)
region.notifyOnExit = false
region.notifyOnEntry = true
let trigger = UNLocationNotificationTrigger(region: region, repeats: true)
// request = content + trigger
let request = UNNotificationRequest(identifier: "REGION \(element.name)", content: content, trigger: trigger)
// add (or "schedule") request to center
let center = UNUserNotificationCenter.current()
center.add(request, withCompletionHandler: { (error: Error?) in
if let theError = error {
print(theError.localizedDescription)
}
})
But then, to let it happen at most once a month, I did the following:
let centerCoordinate2D = element.location.coordinate
let identifierName = element.name.replacingOccurrences(of: " ", with: "_")
let region = CLCircularRegion(center: centerCoordinate2D, radius: 300, identifier: identifierName)
region.notifyOnExit = true
region.notifyOnEntry = true
R.shared.appleLocationManager.startMonitoring(for: region)
Also in AppDelegate.swift,
extension AppDelegate: CLLocationManagerDelegate {
// called when user Enters a monitored region
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print("AppDelegate.locationManager( didEnterRegion ): called with region identifier: \(region.identifier)")
if region is CLCircularRegion {
// Do what you want if this information
self.handleEvent(forRegion: region)
}
}
func handleEvent(forRegion region: CLRegion) {
// we save 'date' with "NOTIFICATION DATE request_identifier" as its key.
let key = "NOTIFICATION DATE \(region.identifier)"
let defaultValue = defaults.double(forKey: key)
if defaultValue == 0 {
print("AppDelegate.handleEvent(): need to show notification: no key")
// set value first.
defaults.set(Date().timeIntervalSince1970, forKey: key)
showNotification(forRegion: region)
} else {
let diff = Date().timeIntervalSince(Date(timeIntervalSince1970: defaultValue))
if diff > 60 * 60 * 24 * 30 {
print("AppDelegate.handleEvent(): need to show notification: diff > 30 days")
// set value first.
defaults.set(Date().timeIntervalSince1970, forKey: key)
showNotification(forRegion: region)
} else {
// just pass.
print("AppDelegate.handleEvent(): need NOT to show notification: diff: \(dot2(diff / 24 / 60)) mins")
}
}
}
func showNotification(forRegion region: CLRegion, message: String = "") {
// customize your notification content
let content = UNMutableNotificationContent()
content.title = "... Reminder"
let hospitalName = region.identifier.replacingOccurrences(of: "_", with: " ")
content.body = "Welcome to \(hospitalName). \(message) Please let us know how we can serve you and your loved ones, and we hope ... will simplify your visit here."
content.sound = UNNotificationSound.default
// the actual trigger object
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0,
repeats: false)
// notification unique identifier, for this example, same as the region to avoid duplicate notifications
let identifier = "REGION \(hospitalName)"
// the notification request object
let request = UNNotificationRequest(identifier: identifier,
content: content,
trigger: trigger)
// trying to add the notification request to notification center
UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in
if let theError = error {
print(theError.localizedDescription)
}
})
}
with the following, still for the AppDelegate class:
let appleLocationManager = CLLocationManager()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.appleLocationManager.delegate = self
...
}
I think there is error in the code, but it is not clear to me if the locationManager( didExitRegion: ) is to be called even when the app is closed/killed - in which case appleLocationManager is not alive?
If I can't use locationManager( didExitRegion: ) for this problem, what can I do to make the region notification happen at most once a month? I also know that there is a different type of trigger, UNTimeIntervalNotificationTrigger or UNLocationNotificationTrigger, and I wanted to use them somehow to solve this problem, but would there be any way to make it run some of my code even when the app is not running at all? If this is impossible to solve, isn't it enough to say that the region notification is too much restricted?
Described as Apple's document, region monitoring with CLLocationManager wakes up your app if needed.
c.f. https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions
Whenever the user crosses the boundary of one of your app's registered regions, the system notifies your app. If an iOS app is not running when the boundary crossing occurs, the system tries to launch it. An iOS app that supports region monitoring must enable the Location updates background mode so that it can be launched in the background.
You can also detect if the app is launched by regional notification with UIApplication.LaunchOptionsKey.
Boundary crossing notifications are delivered to your location manager's delegate object. Specifically, the location manager calls the locationManager(:didEnterRegion:) or locationManager(:didExitRegion:) methods of its delegate. If your app was launched, you must configure a CLLocationManager object and delegate object right away so that you can receive these notifications. To determine whether your app was launched for a location event, look for the UIApplication.LaunchOptionsKey in the launch options dictionary.
Hello I'm new at IBeacons and beginner on swift and I'm trying to make a small app that detects Ibeacon on the background of the app and send a notification when the Ibeacon is in range I manage to do so but only when I walk while the app is open I could not make it work and search for Ibeacons on the background even though I gave the app access to take the location on the background by using
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedAlways) {
locationManager.requestAlwaysAuthorization();
}
this is my main problem, I have a side problem too that the app does not save the person name if the app is closed and opened again the app will forget the name. Here is my code I'd really appreciate your help so much also please if you have any references to learn more about IBeacons applications I'd appreciate it
import UIKit
import CoreLocation
import UserNotifications
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var field: UITextField!
#IBOutlet weak var textLbl : UILabel!
var inRoom = false
var name = ""
var sendYet = false ;
func sendHiNoti()
{
name = field.text! ;
let content = UNMutableNotificationContent()
content.title = "Heloo "+name
content.subtitle = "Welcome to your Home"
content.body = "this messaage to welcome you in Home"
content.badge = 1
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "quiteimpressed.mp3"))
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
let request = UNNotificationRequest(identifier: "azanSoon", content: content, trigger: trigger)
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print("error: \(error)")
}
}
}
func sendByeNoti()
{
name = field.text! ;
let content = UNMutableNotificationContent()
content.title = "OH"+name
content.subtitle = "You are going out already ??"
content.body = "Take care of your self"
content.badge = 1
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "quiteimpressed.mp3"))
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
let request = UNNotificationRequest(identifier: "azanSoon", content: content, trigger: trigger)
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print("error: \(error)")
}
}
}
#IBAction func getText(){
name = field.text!
let alert = UIAlertController(title: "Your Name is", message: name, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge], completionHandler: {didAllow, error in})
let uuid = UUID(uuidString: "E2C56DB5-DFFB-48D2-B060-D0F5A71096E0")!
let beaconRegion = CLBeaconRegion(proximityUUID: uuid, major: 444, minor: 333, identifier: "abcdefac005b")
if (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.authorizedAlways) {
locationManager.requestAlwaysAuthorization();
}
locationManager.startRangingBeacons(in: beaconRegion)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
print(beacons)
if(beacons.count > 0){
if(!sendYet){
if beacons[0].proximity.rawValue < 2 {
textLbl.text = "In the room"
sendHiNoti()
sendYet = true ;
}
}
else if beacons[0].proximity.rawValue >= 3 {
textLbl.text = "Outside the room"
sendByeNoti()
sendYet = false ;
}
}
}
}
The code shown uses beacon ranging locationManager.startRangingBeacons(in: beaconRegion) which is generally not supported in the background for more than 10 seconds after transition between foreground and background.
The locationManager.requestAlwaysAuthorization() will only unlock the ability to use beacon monitoring in the background. Beacon monitoring gives you a single call when beacons either first appear (didEnter(region: region)) or all disappear(didExit(region: region)).
This is the only beacon API which works in the background under normal circumstances.
It is possible to do beacon ranging in the background for longer than 10 seconds using two techniques:
You can get 180 seconds of background ranging after the app transitions to the background by starting a background task as described in my blog post here.
You can also tell iOS that you are a location app to unlock unlimited background beacon ranging. You must first implement the solution in part 1. Then, in your Info.plist, declare:
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
Finally, in your code run locationManager.startUpdatingLocation(). This will cause your app to receive regular updates of its latitude/longitude, but as a side effect allows you background task from step 1 to run forever, allowing ranging to continue forever in the background.
If you choose to use option 2, beware that it will be more difficult to get your app approved for sale in the AppStore. You must convince Apple reviewers that your app is a legitimate location app (like Waze or Apple Maps) and the user is aware that your app is always running in the background. If you do not convince them of this, they will reject your app.
Separately, it is simple to save off values to persistent storage so they are retained across app restarts. Just use NSUserDefaults like this:
// save off name when user fills it in
UserDefaults.standard.set(name, forKey: "name")
// load back name on app restart
name = UserDefaults.standard.string(forKey: "name")
What code do I write to trigger a watch kit notification from the watch app itself? For example, if I connect a button from my watch storyboard to my WatchInterfaceController as an action then when pressed it triggers a notification on the watch.
For trigger a notification, first of all you need permission: (declared usually in the ExtensionDelegate)
func askPermission() {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert,.sound]) { (authBool, error) in
if authBool {
let okAction = UNNotificationAction(identifier: "ok", title: "Ok", options: [])
let category = UNNotificationCategory(identifier: "exampleCategoryIdentifier", actions: [okAction], intentIdentifiers: [], options: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
UNUserNotificationCenter.current().delegate = self
}
}
}
For having this working, you need to import (in the ExtensionDelegate) "UserNotifications" and to extend:
UNUserNotificationCenterDelegate
Once you have done that, you can call askPermission where you want, in a way like this:
if let delegate = WKExtension.shared().delegate as? ExtensionDelegate {
delegate.askPermission()
}
Now you have (hopefully) the permission for trigger a notification!
For trigger a notification, you can use a function like this:
func notification() {
let content = UNMutableNotificationContent()
content.body = "Body Of The Notification"
content.categoryIdentifier = "exampleCategoryIdentifier" // Re-Use the same identifier of the previous category.
content.sound = UNNotificationSound.default() // This is optional
let request = UNNotificationRequest(identifier: NSUUID().uuidString,
content: content,
trigger: nil)
let center = UNUserNotificationCenter.current()
center.add(request) { (error) in
if error != nil {
print(error!)
} else {
print("notification: ok")
}
}
}
In order to test watch notifications, you must first create a new build scheme.
Duplicate your watch app scheme, and in the "Run" section, choose your custom notification as the executable.
Now you can run the notification scheme.
Inside the extensions group in your project, under Supporting Files is a file called PushNotificationPayload.json.
You can edit the payload file to try different notifications and categories.
I am trying to send a location notification when I receive a remote FCM notification because as far as I know FCM notification doesn't support action buttons. But the local notification get sent only sometimes and it seems to me that it is random. I always get the remote notification though. I'd like to get the local notification in all three states, foreground, background, or when app is killed.
Here's where I add the buttons in didFinishLoadingWithOptions:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
let incrementAction = UIMutableUserNotificationAction()
incrementAction.identifier = "First"
incrementAction.title = "First"
incrementAction.activationMode = UIUserNotificationActivationMode.foreground
incrementAction.isAuthenticationRequired = true
incrementAction.isDestructive = true
let decrementAction = UIMutableUserNotificationAction()
decrementAction.identifier = "second"
decrementAction.title = "second"
decrementAction.activationMode = UIUserNotificationActivationMode.foreground
decrementAction.isAuthenticationRequired = true
decrementAction.isDestructive = false
// Category
let counterCategory = UIMutableUserNotificationCategory()
counterCategory.identifier = "BOOKING_CATEGORY"
// A. Set actions for the default context
counterCategory.setActions([incrementAction, decrementAction],
for: UIUserNotificationActionContext.default)
// B. Set actions for the minimal context //No more than 2
counterCategory.setActions([incrementAction, decrementAction],
for: UIUserNotificationActionContext.minimal)
// iOS 9 support
else if #available(iOS 9, *) {
UIApplication.shared.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: NSSet(object: counterCategory) as? Set<UIUserNotificationCategory>))
UIApplication.shared.registerForRemoteNotifications()
}
}
Here's where I create the local notification :
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if UIApplication.shared.applicationState == UIApplicationState.active {
// Do something you want when the app is active
} else {
// Do something else when your app is in the background
}
// fire a local notification upon receiving FCM notification
let localNotification = UILocalNotification()
localNotification.alertTitle = "Notification Title"
localNotification.alertBody = "more details"
localNotification.alertAction = "ShowDetails"
localNotification.fireDate = NSDate().addingTimeInterval(5) as Date
localNotification.soundName = UILocalNotificationDefaultSoundName
localNotification.applicationIconBadgeNumber = 1
localNotification.category = "BOOKING_CATEGORY"
UIApplication.shared.scheduleLocalNotification(localNotification)
print("Push notification received bkg: \(userInfo)")
print("completed" , completionHandler)
}
Any help would be appreciated.
In order to ensure that your app always handles how a notification is displayed (even when its not running or is in the background) you need to set a data payload only e.g.
{
"to":"push_token",
"content_available": true,
"priority": "high",
"data": {
"title": "New Message",
"message": "Bob sent you a message",
}
}
Setting a notification payload triggers FCM to automatically display the message to end-user devices on behalf of the client app. If you use a data payload instead you have full control over how to display the notification.
For more info check out https://firebase.google.com/docs/cloud-messaging/concept-options