iOS how to set Alarm and schedule work/notifications - ios

Hi I am new to iOS basically I am android developer. But Right now I am working on iOS app,It is simply an iOS replica of android. Let me tell you about what I want in app:
Our app features alarm, that will remind our client that on a specific date you have this meeting. For example, if user sets alarm for 1-jan-2019 at time 9:00AM then on that day and time user must be notified of this meeting.
I have read alot and found that in iOS we can not do this since when app is in background it can not run code of his own? So I have 2 basic questions:
What I want:
First of all how to schedule an Alarm
If alarm is set and app is in background/terminated then how to generate notification and when user click on notification take him to specific view?
If app is in forground then how to take him to wanted view? also if app is on specific view how to update view itself when alarm goes on?
I know these are 3 main and major part that required too much coding. But I just want directions. Give me link of chunks of code. I am using xcode 9.2 and swift 4.0. Thanks in advance ...

You many have to schedule local notification which is now available in UNUserNotificationCenter.
So,
For Scheduling a Notification Locally from Your App, follow this doc.
For Handling Notifications and Notification-Related Actions, follow this doc.
To Handle Notifications in your AppDelegate or where you want to handle UNUserNotificationCenter delegate method, add below code:
class AppDelegate:NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate{
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
let center = UNUserNotificationCenter.current()
center.delegate = self // Don't forgot to set delegate
//To get permissions from user:
let options: UNAuthorizationOptions = [.alert, .sound, .badge];
center.requestAuthorization(options: options) {
(granted, error) in
if !granted {
print("Something went wrong")
}
}
return true
}
}

You can use local notifications for getting an alarm notifications.
You can handle the click in the delegate method of UNUserNotificationCenterDelegate and navigate to the desired page.
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void)
For displaying notification when app is in foreground use this method from same delegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void)

Related

How to know when app received notification and when user clicked on notification in iOS

I know there has been a lot written about this topic but I just can't find the right answer.
Is there a way how to know when the user received remote notification and when the user clicked on one on iOS 8.
I would like to know this because when I receive it I want to save it and when user clicks on it I want to open some view.
I have found this answer https://stackoverflow.com/a/16393957/1241217 but the problem is when user is in the app and opens notification center and clicks on one, the app is not inactive and not in the background.
I also found this answer https://stackoverflow.com/a/12937568/1241217 but I know that this is ran only when the app is killed and started from new.
I also don't want to do this https://stackoverflow.com/a/32079458/1241217 since I need to detect when I received notification.
So is there a way how to know if the user only clicked on notification. As far as I understood it has to be done in didReceiveRemoteNotification but I don't know how to separate between them. And I need an answer for before iOS 10 because the app target is iOS 8.
MY SOLUTION:
So as I wrote in the comment of Shabbir Ahmad answer my solution was to remember date when the application did become active and the date when the notification was received. If the difference between this dates was a second or less I accepted that as the user clicked on the notification.
You have to implement UNUserNotificationCenterDelegate and its method
userNotificationCenter(_:willPresent:withCompletionHandler:) and userNotificationCenter(_:didReceive:withCompletionHandler:) which gets called when a user taps a notification. In willPresent: you have to call the completionHandler with an option that would indicate what should happen when a notification arrives while the app is in foreground.
Registering such a delegate is easy:
UNUserNotificationCenter.current().delegate = self
So e.g.:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(UNNotificationPresentationOptions.alert)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let userInfo = userInfo as? [String: Any] {
// TODO: implement your logic
// just don't forget to dispatch UI stuff on main thread
}
}
You can implement that delegate by AppDelegate, but also by any NSObject, I would go with the latter to keep AppDelegate as clean as possible.
P.S.: Of course, this assumes that you have been granted permissions by the user (UNUserNotificationCenter.current().requestAuthorization(options:completionHandler:)) and you are registered to accept notifications (UIApplication.shared.registerForRemoteNotifications()).
Read more in Scheduling and Handling Local Notifications, section Responding to the Delivery of Notifications - while the section is about local notifications, it is exactly the same for the remote ones (they are handled both by the same delegate).
when you click on notification in background mode before ios 10 and when you are in foreground,in both cases your below method will call,
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
So you can differentiate the behaviour,
First of all you assign a boolean variable in AppDelegate class like this:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var isUserTapOnNotification = false
after that make true isUserTapOnNotification in
func applicationWillEnterForeground(_ application: UIApplication) {
isUserTapOnNotification = tue
}
because when you tap on notification bar, your app will came in foreground and applicationWillEnterForeground will call first,
after that your didReceiveRemoteNotification will call:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
if #available(iOS 10.0, *) {
//do nothing
}else { //<ios 10
if isUserTapOnNotification == true {//when app is in background and user tap on notification bar
//do action whatever you want
} else { //when user is in foreground and notification came,
//before ios10,notification bar not display in foreground mode,So you can show popup by using userInfo
}
}
after that applicationDidBecomeActive will call and you reset isUserTapOnNotification to false like this:
func applicationDidBecomeActive(_ application: UIApplication) {
isUserTapOnNotification = false
}
I hope this answer will help you.

Swift: Getting Data from Remote notification

I have a messaging app, which gets data from server through push notification.
In the following image, you can see the process and also check where it gets faulted:
I have researched a lot about this problem but not getting a universal solution.
One solution I get is to use silent notification , discussed Here , to get data before it is shown and save it. But I didn't find how to show notification from app delegate once I received silent notification.
Kindly correct me if I am conceptually wrong somewhere.
Update:
One of problem is that the application:didReceiveRemoteNotification:fetchCompletionHandler: is only called when
app is in foreground
user clicks the notification
But when user doesn't click the notification, application:didReceiveRemoteNotification:fetchCompletionHandler: is not called, and I can't find a way to save the data, that is present in notification.
Your question is "To get data from Remote notification while the App is in foreground" if im correct, You can use these two methods to get data from Remote notifications for background and foreground app modes.
// Notification will present call back
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound, .badge])
// Handle your data here
print("Notification data: \(notification.request.content.userInfo)")
}
#available(iOS 10.0, *)
// Notification interaction response call back
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
// Handle your data here
print("Notification data: \(response.notification.request.content.userInfo)")
}
There are two cases:
Background
Closed State
If the app is in Background State, you have to enable Background Fetch, then this method will be called when the app is in Background application:didReceiveRemoteNotification:fetchCompletionHandler:
When the app is closed or killed by the user the above method not gonna work, for that you can use the VoIP technique.
didReceiveIncomingPushWithPayload method will be called whenever Remote Notification arrives
You can find more details about VoIP here

In which method should I handle iOS remote notification?

I know similar questions have been asked many times. But it is still very confusing to me after reading those threads, especially after UNUserNotificationCenter is introduced in iOS 10.
The official documentation mentioned 3 methods where I can handle remote notifications:
Implement userNotificationCenter:willPresentNotification:withCompletionHandler: to handle a notification when the app is in foreground.
Implement userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: when the app is in background or not running.
But the documentation also mentioned: In iOS and tvOS, the system delivers the notification payload to the application:didReceiveRemoteNotification:fetchCompletionHandler: method of the app delegate.
So,
To handle a remote notification when app is in background/inactive, should I put my code in application delegate method in 3, or the notificationCenter delegate in 2? Since UNUserNotificationCenter is only available for iOS>10, should I write different code to handle each case?
About 1, it is only available after iOS 10. How can I handle remote notifications when app is running in foreground before iOS 10?
And, more confusing: In case the app is in background, when are the delegate methods called: when the notification message is received? or when the user taps the notification?
Related: iOS push notification: how to detect if the user tapped on notification when the app is in background?
iOS 10 and later:
1) userNotificationCenter willPresent notification: Generally used to decide what to do when user is already inside the app and a notification arrives. You could possibly trigger a remote notification inside the app. After the user taps on the remote notification, method 2 (didReceive response) gets called.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (_ options: UNNotificationPresentationOptions) -> Void) {
//Handle push from foreground
//When a notification arrives and your user is using the app, you can maybe notify user by showing a remote notification by doing this
completionHandler([.alert, .badge, .sound])
//To print notification payload:
print(notification.request.content.userInfo)
}
2) userNotificationCenter didReceive response: Generally used to redirect the user to a particular screen of the app after user taps on the notification.
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
//Handle push from background or closed (or even in foreground)
//This method is called when user taps on a notification
//To print notification payload:
print(response.notification.request.content.userInfo)
}
Below iOS 10:
3) application didReceiveRemoteNotification:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
//To print notification payload
print(userInfo)
if #available(iOS 10.0, *) {
}
else {
//Handle remote notifications for devices below iOS 10
if application.applicationState == .active {
//app is currently in foreground
}
else if application.applicationState == .background {
//app is in background
}
else if application.applicationState == .inactive {
//app is transitioning from background to foreground (user taps notification)
}
}
}
4) application didFinishLaunchingWithOptions launchOptions: The only scenario which is left for devices below iOS 10 is when app is closed and user taps on the notification launching the app. You'll have to check the following method for this scenario.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//To print notification payload:
if let notification = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
print(notification)
}
}
LaunchOptions is a dictionary indicating the reason the app was
launched (if any). The contents of this dictionary may be empty in
situations where the user launched the app directly.
Now to answer your questions,
To handle a remote notification when app is in background/inactive, you'll have to add your code in method 2 (userNotificationCenter didReceive response) for devices with iOS 10 and above. Also, you'll have to use method 3 (application didReceiveRemoteNotification) for devices below iOS 10.
To handle remote notifications when app is running in foreground before iOS 10, use the method 3 active state.
In addition to the great answer by Ameya, I wanted to point out that userNotificationCenter:willPresent:notification does not get called if app is in background state.
My complete solution to handle all cases on iOS 10+ would be to also use application:didFinishLaunchingWithOptions:launchOptions, and check if in background state, and handle the notification there too. Your payload, however, now also needs to include the "content-available": 1 field).

IOS: Inapp notification + Notification Service + Content extensions

The thing is that notification service extension is useless by itself, except one thing that it can be shown when the app is killed. So with inapp notifications and content extension I can show custom notification, but that notification will appear only if the app is not killed/force-closed.
Question: How to manage the inapp notifications in case there are content and service notifications extensions, and how to force notifications service extension to call/wake up the notification content extension.
Seemed that I needed to clean the project and remove the app and install again. Here is the full steps for FCM to achieve this case, maybe some of the steps are redundant, but I don't want to touch it while it is working :
App is killed/closed: Notification appears with custom content view
App is in background: Notification appears with custom content view
App is in foreground: Notification arrives silently
From server side the notification should look like this
{
"notification"://mandatory
{
"data":{},
"body":""//seemed mandatory as well, anyway you can change it in service extension
}
"content_available":true,//mandatory
"mutable_content":true,//mandatory
"to":""//mandatory
}
Create Notification Service Extension target.
In info.plist under NSExtension add UNNotificationExtensionCategory as Array and add one category with whatever name you want, but use the same one everywhere.
In Notification Service Extension target be sure everything is same as in main target(swift version, build version, deployment target, devices).
In capabilities add app groups(should be same as in main target).
In NotificationService in didReceive get the mutable content
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
Add your category to the content
bestAttemptContent.categoryIdentifier = "yourCategory"
After calling contentHandler(bestAttemptContent) the standart notification appears.
Create Notification Content Extension target.
In info.plist under NSExtension add UNNotificationExtensionCategory as Array and add one category with same name you already created for the service.
Change the design in MainInterface.storyboard according to your design.
In NotificationViewController the custom data will be in notification.request.content.userInfo["gcm.notification.data"] . So in NotificationViewController fill the views with the data. Don't forget about the the preferredContentSize.
In capabilities add app groups(should be same as in main target).
In main target AppDelegate implement UNUserNotificationCenterDelegate.
In didFinishLaunchingWithOptions add
UNUserNotificationCenter.current().delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
}
application.registerForRemoteNotifications()
In AppDelegate add
func application( _ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken
deviceToken: Data ) {
Messaging.messaging().apnsToken = deviceToken
}
In userNotificationCenter willPresent check if your app is not running. I made it this way:
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
if self.window?.rootViewController == nil {
completionHandler([.badge, .alert, .sound])
}
UIApplication.shared.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
}
Handle the silent notification here:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void)
Handle the tap on the notification in here:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void)
here you can get the custom data with the same way response.notification.request.content.userInfo["gcm.notification.data"]
If you have async network calls to load data/images don't forget to implement func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void)
Seems that's all :) . Ah, the extensions are working starting IOS 10. Please correct me if there is something missing or something is redundant.

Local notifications won't pop out when the app is running but will when the app is in the background

I have checked that all the notifications are scheduled correctly at the NotificationCenter. And, I have the completionHandler([.alert, .badge, .sound]) code in my app delegate to make sure the notifications will show up even when the user is using the app.
The result is notifications will show only the app is in the background, not the other way.
Did you handle the local notifications in app delegate like this?
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
// Do something you want
println("Received Local Notification:")
println(notification.alertBody)
}
Above is for local notifications and there is another method for hadle remote notifications,
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// Do something you want
}
Make sure you are handling the local notifications using local notification method.
Please try this:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .sound])
}
}
You can find Apple doc here.
What I have understood is, whatever options I send to completionHandler will happen.
If you send just .alert, it will just show the alert
If you send just .sound, it will just play the sound specified by notification.
And will do both if we send both options.
And will do nothing if we don't send any parameter.
So what you need to do is send parameter as .alert.
In didFinishLaunchingWithOptions you will also need to add
UNUserNotificationCenter.current().delegate = self

Resources