系统推送的集成(二十一) —— 关于本地通知的详细解析(一)

版本记录

版本号 时间
V1.0 2021.05.23 星期日

前言

我们做APP很多时候都需要推送功能,以直播为例,如果你关注的主播开播了,那么就需要向关注这个主播的人发送开播通知,提醒用户去看播,这个只是一个小的方面,具体应用根据公司的业务逻辑而定。前面已经花了很多篇幅介绍了极光推送,其实极光推送无非就是将我们客户端和服务端做的很多东西封装了一下,节省了我们很多处理逻辑和流程,这一篇开始,我们就利用系统的原生推送类结合工程实践说一下系统推送的集成,希望我的讲解能让大家很清楚的理解它。感兴趣的可以看上面几篇。
1. 系统推送的集成(一) —— 基本集成流程(一)
2. 系统推送的集成(二) —— 推送遇到的几个坑之BadDeviceToken问题(一)
3. 系统推送的集成(三) —— 本地和远程通知编程指南之你的App的通知 - 本地和远程通知概览(一)
4. 系统推送的集成(四) —— 本地和远程通知编程指南之你的App的通知 - 管理您的应用程序的通知支持(二)
5. 系统推送的集成(五) —— 本地和远程通知编程指南之你的App的通知 - 调度和处理本地通知(三)
6. 系统推送的集成(六) —— 本地和远程通知编程指南之你的App的通知 - 配置远程通知支持(四)
7. 系统推送的集成(七) —— 本地和远程通知编程指南之你的App的通知 - 修改和显示通知(五)
8. 系统推送的集成(八) —— 本地和远程通知编程指南之苹果推送通知服务APNs - APNs概览(一)
9. 系统推送的集成(九) —— 本地和远程通知编程指南之苹果推送通知服务APNs - 创建远程通知Payload(二)
10. 系统推送的集成(十) —— 本地和远程通知编程指南之苹果推送通知服务APNs - 与APNs通信(三)
11. 系统推送的集成(十一) —— 本地和远程通知编程指南之苹果推送通知服务APNs - Payload Key参考(四)
12. 系统推送的集成(十二) —— 本地和远程通知编程指南之Legacy信息 - 二进制Provider API(一)
13. 系统推送的集成(十三) —— 本地和远程通知编程指南之Legacy信息 - Legacy通知格式(二)
14. 系统推送的集成(十四) —— 发送和处理推送通知流程详解(一)
15. 系统推送的集成(十五) —— 发送和处理推送通知流程详解(二)
16. 系统推送的集成(十六) —— 自定义远程通知(一)
17. 系统推送的集成(十七) —— APNs从工程配置到自定义通知UI全流程解析(一)
18. 系统推送的集成(十八) —— APNs从工程配置到自定义通知UI全流程解析(二)
19. 系统推送的集成(十九) —— APNs配置接收和处理的简单入门(一)
20. 系统推送的集成(二十) —— APNs配置接收和处理的简单入门(二)

开始

首先看下主要内容:

了解如何按时间间隔,一天中的时间和位置创建通知,以及如何支持类别分组和提示操作。内容来自翻译

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

接着就是正文啦。

通知是在应用程序外部将信息传达给用户的一种必不可少的方式。 它们有助于吸引用户的注意力,并促进他们对应用程序的参与。 但是,大量的通知可能会使用户烦恼,从而导致他们将其关闭甚至删除该应用程序。 因此,您必须明智地使用通知。

有两种类型的通知:本地通知和远程通知。 本教程全部关于本地通知。 在本教程中,您将学习如何:

  • 请求通知权限。
  • 创建,安排和管理不同类型的本地通知。
  • 处理通知动作。

通常,用户会忘记任务并需要提醒。 您可以使用本地通知功能轻松提醒他们。

打开OrganizerPlus项目,构建并运行。

OrganizerPlus有助于跟踪您的任务。 点击+按钮开始创建任务。 接下来,为任务添加一个名称,然后点击Save。 您可以通过点击任务旁边的圆圈将任务标记为已完成。


Introducing Notifications

通知可以是本地或远程的。 设备上的应用可安排和配置本地通知。 相反,服务器使用Apple Push Notification Service(APNS)发送远程通知。 新闻alert是远程通知的示例,其中服务器将最新的突发新闻作为通知发送到设备。

远程通知可以是可见的也可以是静默的。 静默通知会在后台启动应用程序以执行操作,例如刷新应用程序。 您可以使用UserNotifications框架配置本地和远程通知。 要了解有关远程通知的更多信息,请查看 Push Notifications Tutorial: Getting Started

要创建和管理通知,您需要执行以下步骤:

  • Authorization
  • Content creation
  • Scheduling the notifications
  • Managing the notifications
  • Handling actions

您将在本教程中学习如何执行所有这些步骤。


Requesting Notification Permission

通知会打断用户,因此应用程序需要用户允许它们。 在OrganizerPlus中,当用户点击右上角的响铃按钮时,您将请求通知权限。

首先,打开NotificationManager.swift。 然后,将以下内容添加到NotificationManager

func requestAuthorization(completion: @escaping  (Bool) -> Void) {
  UNUserNotificationCenter.current()
    .requestAuthorization(options: [.alert, .sound, .badge]) { granted, _  in
      // TODO: Fetch notification settings
      completion(granted)
    }
}

代码是这样的:

  • 1) UNUserNotificationCenter处理应用程序中所有与通知有关的行为。 这包括请求授权,安排交付和处理操作。 您可以通过调用current()访问UNUserNotificationCenter的共享实例。
  • 2) 您调用requestAuthorization(options:completionHandler :)来请求授权以显示通知。 options表示通知的行为,例如显示alert,播放声音或更新应用程序的标志。 您可以在开发人员文档 in the developer documentation中了解有关各种UNAuthorizationOptions的更多信息。
  • 3) 完成处理程序(completion handler)接收一个布尔值,该布尔值指示用户是否授予了授权。 在这里,您使用布尔值调用完成处理程序。 TODO注释是用于获取通知设置的占位符。 接下来,您将执行此操作。

现在,在NotificationManager中添加以下内容:

func fetchNotificationSettings() {
  // 1
  UNUserNotificationCenter.current().getNotificationSettings { settings in
    // 2
    DispatchQueue.main.async {
      self.settings = settings
    }
  }
}

这是您添加的内容:

  • UNUserNotificationCentergetNotificationSettings(completionHandler :)请求应用授权的通知设置。 设置异步返回。 UNNotificationSettings管理所有与通知相关的设置和应用程序的授权状态。 settings是它的一个实例。
  • 可以在后台线程上调用完成块(completion block)。 在这里,您可以在更改主线程的值时更新主线程,从而更新主线程上的settings属性。

现在,在requestAuthorization(completion :)中,将// TODO: Fetch notification settings替换成如下:

self.fetchNotificationSettings()

在这里,您在用户授予授权后获取通知设置。

1. Prompting Notification Authorization

打开TaskListView.swift。 然后,在按钮的操作结束中找到// TODO: Add Notification authorization,并将其替换为以下内容:

// 1
NotificationManager.shared.requestAuthorization { granted in
  // 2
  if granted {
    showNotificationSettingsUI = true
  }
}

这是一个细分:

  • 1) 您可以通过调用在NotificationManager中定义的requestAuthorization(completion :)来请求通知授权。
  • 2) 如果用户授予了权限,则将showNotificationSettingsUI设置为true。 这将NotificationSettingsView呈现为sheet

构建并运行。

点击右上角的响铃图标。这将提示通知授权。接下来,选择Allow。您会看到该应用授予的通知设置列表。

顺利完成通知管理的第一步!

2. Understanding Critical Notifications

在通知设置中,请注意,该应用没有获得严重alert的授权。但是什么是紧急alert

紧急alert是健康,医疗,安全或公共安全通知。他们绕过请勿打扰和振铃开关并播放声音。这些都是极具破坏性的,因此并非所有应用程序都可以发送严重警报。

如果您的应用程序需要发送它们,则可以在Apple developer portal上申请权利。

OrganizerPlus的提醒很重要,但并不紧急,因此您无需为此教程启用紧急alert

现在,该创建和安排通知了。


Creating Notifications

您可以使用以下任何触发器来创建和调度本地通知:

  • Time interval
  • Calendar
  • Location

这些选项确定您的应用何时传递通知。下一步,您将学习如何使用这些触发器中的每个触发器来创建通知。


Triggering TimeInterval Notifications

打开NotificationManager.swift并将以下内容添加到NotificationManager

// 1
func scheduleNotification(task: Task) {
  // 2
  let content = UNMutableNotificationContent()
  content.title = task.name
  content.body = "Gentle reminder for your task!"

  // 3
  var trigger: UNNotificationTrigger?
  switch task.reminder.reminderType {
  case .time:
    if let timeInterval = task.reminder.timeInterval {
      trigger = UNTimeIntervalNotificationTrigger(
        timeInterval: timeInterval,
        repeats: task.reminder.repeats)
    }
  default:
    return
  }

  // 4
  if let trigger = trigger {
    let request = UNNotificationRequest(
      identifier: task.id,
      content: content,
      trigger: trigger)
    // 5
    UNUserNotificationCenter.current().add(request) { error in
      if let error = error {
        print(error)
      }
    }
  }
}

这是您添加的内容:

  • 1) scheduleNotification(task :)接受Task类型的参数。这就是您的模型,其中包含与任何任务相关的所有数据。它在Task.swift中定义。
  • 2) 您可以通过填充通知内容来开始创建通知。 UNMutableNotificationContent保留本地通知的payload。在这里,您将填充通知的titlebodyUNMutableNotificationContent具有其他属性,例如:
    • subtitle - 副标题:通知的辅助描述。
    • badge - 徽章:要显示的应用图标徽章的数字。
    • sound - 声音:在通知传递过程中播放的声音。
    • attachments - 附件:要在通知中显示的附件数组。这些可能是图像,视频或音频文件。
  • 3) UNNotificationTrigger是触发通知传递的抽象类。在这里,您检查任务reminderType是否基于时间且具有有效的时间间隔。然后,使用UNTimeIntervalNotificationTrigger创建基于时间间隔的通知触发器。您可以使用这种类型的触发器来安排计时器。除了获取timeInterval之外,构造函数还接受一个布尔参数,repeats。这确定了通知在传递后是否需要重新计划。您将在本教程的后面部分处理switch的其他情况。
  • 4) 定义触发器之后,下一步是创建通知请求。您可以使用UNNotificationRequest创建新请求,并指定identifier, content and a trigger。您创建的每个任务已经具有unique identifier。您将其作为notification identifier传递。
  • 5) 然后,通过将请求添加到UNUserNotificationCenter来调度通知。completion handler具有一个error对象,该对象指示在调度通知时是否发生了问题。

1. Scheduling the Notification

打开TaskManager.swift。在DispatchQueue.global().async块之后和方法结束之前,将以下内容添加到save(task :)

if task.reminderEnabled {
  NotificationManager.shared.scheduleNotification(task: task)
}

如果任务的reminderEnabled设置为true,则在此处调度通知。

构建并运行。

请按照以下步骤添加任务并测试您的通知:

  • 1) 点击+按钮开始添加任务。
  • 2) 给任务起个名字。
  • 3) 切换Add Reminder开关。
  • 4) 使用默认选择Time,将Time Interval设置为一分钟。
  • 5) 保留Repeat Notification off
  • 6) 点击右上角的Save以创建任务。

最后,后台运行该应用程序并等待一分钟。 您会看到通知出现。

恭喜您收到第一个通知!

要查看此通知,您必须使应用程序后台运行并等待。 在应用仍处于前台状态时查看通知会不会很酷? 接下来,您将要执行此操作。


Viewing Notification When the App Is in the Foreground

打开AppDelegate.swift并将以下内容添加到文件末尾:

// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
  func userNotificationCenter(
    _ center: UNUserNotificationCenter,
    willPresent notification: UNNotification,
    withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void
  ) {
    completionHandler(.banner)
  }
}

userNotificationCenter(_:willPresent:withCompletionHandler :)询问代理在应用程序处于前台时如何处理通知。 在这里,您可以将演示文稿选项设置为banner来调用completion handler。 您还可以添加其他选项,例如soundbadge

接下来,将以下内容添加到AppDelegate

private func configureUserNotifications() {
  UNUserNotificationCenter.current().delegate = self
}

在这里,您声明configureUserNotifications(),这使AppDelegate成为UNUserNotificationCenter的委托。

现在,在返回true之前,将以下内容添加到application(_:didFinishLaunchingWithOptions :)

configureUserNotifications()

在这里,您可以在应用启动后立即调用configureUserNotifications()来设置通知委托。

构建并运行。

要进行尝试,请添加时间间隔设置为一分钟的任务,然后将应用程序置于前台。 当应用程序处于前台时,您会看到一条通知。 做得好!

将任务标记为完成会删除它,但是通知呢? 您不想看到不存在的任务的提醒。 接下来,您将进行处理。


Removing Scheduled Notifications

打开NotificationManager.swift并将以下内容添加到NotificationManager

func removeScheduledNotification(task: Task) {
  UNUserNotificationCenter.current()
    .removePendingNotificationRequests(withIdentifiers: [task.id])
}

您需要确保在任务完成时删除所有待处理的通知。 通过使用removePendingNotificationRequests(withIdentifers :)来执行此操作。 在这里,您将任务的标识符传递到数组中。 这对于将repeats设置为true的任务特别有用。

接下来,打开TaskManager.swift并在返回之前添加以下内容到remove(task:)

if task.reminderEnabled {
  NotificationManager.shared.removeScheduledNotification(task: task)
}

任务完成后,这将删除所有计划的通知。

构建并运行。

通过创建时间间隔为一分钟的任务来尝试一下。 这次,切换Repeat Notification开关以将其启用。 您会注意到该通知每分钟出现一次。 接下来,通过点击任务旁边的圆圈按钮,将创建的任务标记为完成。 这将删除任务,并且您将不再看到该任务的计划通知。

您可以在UNUserNotificationCenter上使用removeAllPendingNotificationRequests()取消计划所有待处理的通知。 removeAllDeliveredNotifications()将删除所有已传递的通知。

时间间隔只是触发通知的一种方式。 还有其他本地触发通知的方法:日历和位置。 接下来,您将实现这些。


Triggering Calendar Notifications

打开NotificationManager.swift。 然后,将以下内容添加到scheduleNotification(task :)中,作为switch task.reminder.reminderType中的附加情况:

case .calendar:
  if let date = task.reminder.date {
    trigger = UNCalendarNotificationTrigger(
      dateMatching: Calendar.current.dateComponents(
        [.day, .month, .year, .hour, .minute],
        from: date),
      repeats: task.reminder.repeats)
  }

使用添加的代码,您:

  • 1) 检查task提醒reminder是否设置了日期date
  • 2) 创建类型为UNCalendarNotificationTrigger的通知触发器。 日历触发器基于特定的日期和时间传递通知。 它从用户选择的日期中提取dateComponents。 仅指定时间组件将在该指定时间触发通知。

构建并运行。

请按照以下步骤创建任务:

  • 1) 点击+按钮。
  • 2) 给任务起个名字。
  • 3) 切换Add Reminder开关。
  • 4) 点按细分栏中的Date trigger
  • 5) 选择一个特定的日期和时间 —— 也许从现在开始一分钟。
  • 6) 保留Repeat Notification off
  • 7) 点击Save按钮以创建任务。

您会看到通知出现。恭喜,您的第一个日历通知!

日历通知可让您安排定期提醒日常活动,例如每天早晨喝水。

现在,是时候提醒您通过GameStop商店时购买GME股票的提醒。输入基于位置的提醒!


Triggering Location Notifications

当用户进入或退出区域时,基于位置的触发器会导致应用传递通知。在后台,iOS使用地理围栏进行区域监视。这样,系统限制了用户可以同时调度的基于位置的触发器的数量。

添加基于位置的通知的第一步是请求位置权限。由于系统监视区域,因此该应用需要When-In-Use授权。LocationManager.swift中已经为您提供了位置授权和委托代码。

打开CreateTaskView.swift。然后,找到并替换//TODO: Request location authorization

locationManager.requestAuthorization()

在这里,当用户点击Request Location Authorization按钮时,您请求位置授权 —— 用户尚未获得授权。

1. Creating a Location Trigger

打开NotificationManager.swift。 接下来,通过替换switch task.reminder.reminderType中的default case,将以下内容添加到scheduleNotification(task :)中:

case .location:
  // 1
  guard CLLocationManager().authorizationStatus == .authorizedWhenInUse else {
    return
  }
  // 2
  if let location = task.reminder.location {
    // 3
    let center = CLLocationCoordinate2D(
      latitude: location.latitude,
      longitude: location.longitude)
    let region = CLCircularRegion(
      center: center,
      radius: location.radius,
      identifier: task.id)
    trigger = UNLocationNotificationTrigger(
      region: region,
      repeats: task.reminder.repeats)
  }

以下是分步细分:

  • 1) 您检查用户是否至少已授予When In Use位置授权。
  • 2) 然后,检查以确保任务提醒的位置数据存在。
  • 3) 您可以使用UNLocationNotificationTrigger创建基于位置的触发器。 首先,定义一个由CLLocationCoordiante2D表示的中心。 使用此方法,您可以通过指定半径和唯一标识符来创建CLCircularRegion的实例。 最后,使用此圆形区域创建触发器。 您可以使用CLCircularRegion上的notifyOnEntrynotifyOnExit来指定是仅在进入区域时还是在离开区域时触发通知。

构建并运行。

点击+按钮开始添加任务。接下来,点击细分栏中的Location。然后,如果您尚未授予位置授权,请点击Request Location Authorization。现在,在位置权限提示中选择Allow While Using App。您会看到Latitude, Longitude and Radius文本字段。

输入您打算不久后访问的地方的纬度或经度,或输入房屋的坐标。输入500作为半径。该值以米为单位。然后,点击Save以创建任务。

现在,当您输入或退出指定的位置时,您会收到一条通知。要对此进行模拟,请转到模拟器的Features菜单,选择Location ▸ Custom Location…,然后在提示符下输入相同的纬度和经度值。您会看到该通知从您的应用程序在后台显示。要走的路!

到目前为止,您已经根据不同的触发条件创建了通知。但是基于触发器将这些通知分组很酷,对吧?接下来,您将要执行此操作。


Grouping Notifications

默认情况下,您根据应用程序的bundle identifier将所有应用程序的通知分组。但是,您可以为您的应用程序创建自定义组。一个很好的例子是一个消息传递应用程序,该应用程序将不同消息组的通知进行分组。

对于OrganizerPlus,您将根据触发器类型对通知进行分组。

打开NotificationManager.swift并在NotificationManager之前添加以下内容:

enum NotificationManagerConstants {
  static let timeBasedNotificationThreadId =
    "TimeBasedNotificationThreadId"
  static let calendarBasedNotificationThreadId =
    "CalendarBasedNotificationThreadId"
  static let locationBasedNotificationThreadId =
    "LocationBasedNotificationThreadId"
}

在这里,您可以定义用于根据通知触发器类型对通知进行分组的常量。

scheduleNotification(task :)中找到switch task.reminder.reminderType。 通过为通知内容设置threadIdentifier为每种case添加标识符,如下所示:

switch task.reminder.reminderType {
case .time:
  content.threadIdentifier =
    NotificationManagerConstants.timeBasedNotificationThreadId
  // ...
case .calendar:
  content.threadIdentifier =
    NotificationManagerConstants.calendarBasedNotificationThreadId
  // ...
case .location:
  content.threadIdentifier =
    NotificationManagerConstants.locationBasedNotificationThreadId
  // ...
}

threadIdentifier帮助对相关通知进行分组。 在这里,您可以根据reminderType设置通知内容的标识符。

构建并运行。

用不同的触发器创建一些任务。出现通知时,您会看到它们根据触发器的类型进行了分组。

您可以选择关闭应用程序的通知分组。为此:

  • 1) 转到Settings ▸ Select the app
  • 2) 点击Notifications
  • 3) 点击Notification Grouping
  • 4) 将分组设置为Off

这将关闭分组。通知将按照收到的顺序显示。

轻按通知即可打开应用程序。有没有办法在不打开应用程序的情况下对通知采取行动?接下来,您将进行探讨。


Handling Notification Actions

可操作的通知允许用户在不打开应用程序的情况下对通知执行操作。可操作的通知除了内容之外,还可以显示一个或多个按钮。

为了支持可行的通知,您需要:

  • 1) 在应用启动时声明通知类别。
  • 2) 创建操作并将其分配给通知类别。
  • 3) 将类别标识符分配给通知有效负载payload
  • 4) 处理注册的动作。

接下来,您将实现所有这些功能。

1. Declaring Category and Creating Actions

打开AppDelegate.swift。然后,将以下内容添加到UNUserNotificationCenter.current().delegate = self下的configureUserNotifications()

// 1
let dismissAction = UNNotificationAction(
  identifier: "dismiss",
  title: "Dismiss",
  options: [])
let markAsDone = UNNotificationAction(
  identifier: "markAsDone",
  title: "Mark As Done",
  options: [])
// 2
let category = UNNotificationCategory(
  identifier: "OrganizerPlusCategory",
  actions: [dismissAction, markAsDone],
  intentIdentifiers: [],
  options: [])
// 3
UNUserNotificationCenter.current().setNotificationCategories([category])

以下是分步细分:

  • 1) 您声明两个动作:dismissActionmarkAsDone。这些将显示为带有通知的操作按钮。每个UNNotificationAction实例都有一个标识符,标题和选项数组。identifier唯一地标识动作。title代表按钮上的文本。options表示与动作相关联的行为。不同的选项是:
    • authenticationRequired:仅当设备解锁后,用户才能执行此操作。如果设备已锁定,则系统会提示用户将其解锁。
    • destructive:此操作执行破坏性任务。该选项的按钮显示为特殊突出显示。
    • foreground:此选项使应用程序在前台启动。
  • 2) 在这里,您可以定义一个通知类别。 UNNotificationCategory定义应用程序可以接收的通知类型。标识符identifier唯一地标识类别。 intentIdentifiers使系统知道该通知与Siri发出的请求有关。这些选项表示如何处理与它们关联的通知。您可以了解有关Apple developer documentation的更多信息。
  • 3) 在这里,您注册了新的可执行通知。

现在,系统知道了您的通知类别及其执行的操作,但是一个应用程序可能具有多个通知类别,并且并非每个类别都会为其分配操作。要让系统知道它应该显示您的操作,您必须将通知分配给该类别。

2. Assigning Identifiers to Notification Payload

打开NotificationManager.swift。然后,将以下内容添加到scheduleNotification(task :)content.body = "Gentle reminder for your task!"下面:

content.categoryIdentifier = "OrganizerPlusCategory"
let taskData = try? JSONEncoder().encode(task)
if let taskData = taskData {
  content.userInfo = ["Task": taskData]
}

在这里,您将通知内容的categoryIdentifier设置为创建UNNotificationCategory实例时使用的标识符。 然后,您对任务数据进行编码,并将其分配给通知内容的userInfo。 当用户对通知执行操作时,该应用将能够访问此内容。

3. Handling Actions

打开AppDelegate.swift。 将以下内容添加到AppDelegate扩展中:

// 1
func userNotificationCenter(
  _ center: UNUserNotificationCenter,
  didReceive response: UNNotificationResponse,
  withCompletionHandler completionHandler: @escaping () -> Void
) {
  // 2
  if response.actionIdentifier == "markAsDone" {
    let userInfo = response.notification.request.content.userInfo
    if let taskData = userInfo["Task"] as? Data {
      if let task = try? JSONDecoder().decode(Task.self, from: taskData) {
        // 3
        TaskManager.shared.remove(task: task)
      }
    }
  }
  completionHandler()
}

以下是代码细分:

  • 1) 当用户对通知执行操作时,iOS会调用userNotificationCenter(_:didReceive:withCompletionHandler :)
  • 2) 检查响应的actionIdentifier是否设置为markAsDone。 然后,您从userInfo解码任务。
  • 3) 解码成功后,您可以使用TaskManager的共享实例删除任务。

构建并运行。

通过选择时间触发器来创建任务。 当通知出现时,长按该通知。 您会看到两个带有通知的按钮。 点击Mark as Done按钮。 无需打开应用程序即可删除任务。

做得好! 您现在已成为本地通知的专家。

在本教程中,您学习了如何创建,安排,分组和处理本地通知。 您可以使用Notification Content Extension自定义通知的外观。 Push Notifications Tutorial for iOS: Rich Push Notifications对此进行了详细介绍。

后记

本篇主要讲述了关于本地通知的详细解析,感兴趣的给个赞或者关注~~~

推荐阅读更多精彩内容