Group Group Group Group Group Group Group Group Group

Push Notifications Tutorial: Getting Started | raywenderlich.com

Push notifications allow developers to reach users, even when users aren’t actively using an app! In this tutorial, you’ll learn how to configure your app to receive push notifications and to display them to your users or perform other tasks.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/11395893-push-notifications-tutorial-getting-started

How do I handle View Action without opening the app? Is there way to do that?

Could you elaborate a bit on what you are trying to do? I’d be happy to try and offer some guidance.

I want the user to complete a task from the push notification without going into the app.
I followed your tutorial and added a category and an action identifier. When I run the app and trigger push notification( when the app is in the background ), the force touch brings up the “complete task” button. If I click on it, it opens the app. Is there a way I can complete the task without taking user into the app?

I think what you are looking for is described in this tutorial on our site: Rich Push Notifications. Check out the section on adding service extensions. It should give you some ideas.

I was working thru this tutorial and I believe there may be a slight mistake in the format of push notifications. Specifically, this sample notification with the custom field link_url -

{
  "aps": {
    "alert": "Breaking News!",
    "sound": "default",
    "link_url": "https://raywenderlich.com"
  }
}

In this example, link_url is part of “aps”, but according to the Apple docs the message should be formatted this way, with link_url as part of the main json object -

{
  "aps": {
    "alert": "Breaking News!",
    "sound": "default"
  },
  "link_url": "https://raywenderlich.com"
}

This is the link I was reading, and in the sample notification “gameID” is the custom field.
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification#2943361

Here is the sample notification from the Apple docs -

{
   "aps" : {
      "alert" : {
         "title" : "Game Request",
         "subtitle" : "Five Card Draw",
         "body" : "Bob wants to play poker"
      },
      "category" : "GAME_INVITATION"
   },
   "gameID" : "12345678"
}

Thank you for pointing that out. You are correct. The Apple documentation specifically says, " Add app-specific keys as peers of the aps dictionary." I have embedded a correction in the tutorial and we will update the tutorial appropriately when we next update. As you probably noticed, it still works even when placed as a member of the aps dictionary, but we always suggest doing things in accordance with Apple’s documentation. Nice catch!

1 Like

I have a confusion, when we request authorisation why self?.getNotificationSettings() is needed? cant we just simply call UIApplication.shared.registerForRemoteNotifications() there itself.
I mean even if user turns off notifications, value of granted will be false and registerForRemoteNotifications will not be called.
What is the use of getting notification settings? Someone please help me there
thanks

As with many things in programming, there are various ways to accomplish the same thing. This tutorial is showing just one approach.

What if the user initially denies the authorization and then, without closing the app, he turns on notifications from device settings? registerForPushNotifications() will not be called and he doesn’t receive notifications until he restarts app.

Do we have to call registerForPushNotifications() also inside applicationWillEnterForeground(_ application:) func?

But even so user will not receive notification until he brings app in foreground.

I think we should call registerForRemoteNotifications() regardless of the user choice.

What do you think about this?

You make a good point. Edit: I removed the earlier suggestion after researching your issue more deeply.

Thank you for your response.
I didn’t understand when you would call that snippet of code.
However, if user turns on notifications from device Settings app, our app will not receive notifications until user reopens it and this could be happen much later.

@bmn Spent some more time on the issue you raised of the user changing the permissions after the first ask. Actually, I didn’t find any good solutions. Apple deliberately doesn’t let you pester the user with repeated asks. I didn’t find any way to get notified of a change as it happens. You can check each time the app becomes active, as you suggested, but that won’t handle the case of when running in the background. You could add a background check, but those run at the whim of iOS, so no guarantee of when it would run. Bottom line, checking at did finish launching is the best available since users don’t tend to modify this permission all that often and the app will eventually be killed an relaunched by iOS.

@ckrutsinger what I suggested in my first comment was to call UIApplication.shared.registerForRemoteNotifications() every time the app is launched, regardless of the user choice. To understand us, I would remove the guard checks from registerForPushNotifications() and getNotificationSettings() func. In this way, I will always receive deviceToken from Apple (even if the user denied the push notifications consent) and I can send it to my server. Later, if the user turns on notifications from device Settings app, my app will start to receive notifications (when i send it) because the deviceToken is already registered on my server.
I would untie consent request (UNUserNotificationCenter.current() .requestAuthorization()) form deviceToken request (UIApplication.shared.registerForRemoteNotifications()).

1 Like

I believe that the article should mention apns-priority property.
Source: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns
(Bold font is mine.)

Use the background push type for notifications that deliver content in the background, and don’t trigger any user interactions. If you set this push type, the apns-topic header field must use your app’s bundle ID as the topic. Always use priority 5 . Using priority 10 is an error.

Good suggestion. We should put that into the next revision.

great post. if you only have one device running in the simulator you can use “booted” and not have to find the ID.

xcrun simctl push booted pushNotificationPayload

another note is that the completion handler must be called and your example only calls it in the failure case. from the apple docs:

As soon as you finish processing the notification, you must call the block in the handler parameter or your app will be terminated. Your app has up to 30 seconds of wall-clock time to process the notification and call the specified completion handler block.

func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler:
@escaping (UIBackgroundFetchResult) -> Void
) {
guard let aps = userInfo[“aps”] as? [String: AnyObject] else {
completionHandler(.failed)
return
}
NewsItem.makeNewsItem(aps)
}

I have updated the tutorial with that tip about using booted instead of the device UUID. Thanks for sharing that. Learned something new today.

You are correct about always needing to call the handler. The finished app actually does call the handler in all cases. Here is the finished code after you make the final edits:

  func application(
    _ application: UIApplication,
    didReceiveRemoteNotification userInfo: [AnyHashable: Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
  ) {
    guard let aps = userInfo["aps"] as? [String: AnyObject] else {
      completionHandler(.failed)
      return
    }
    if aps["content-available"] as? Int == 1 {
      let podcastStore = PodcastStore.sharedStore
      podcastStore.refreshItems { didLoadNewItems in
        completionHandler(didLoadNewItems ? .newData : .noData)
      }
    } else {
      NewsItem.makeNewsItem(aps)
      completionHandler(.newData)
    }
  }