[iOS 10 day by day] Day 6:自定义的通知
续上篇,在简单闹钟的例子上,在通知界面上显示图片动画,并用通知关联的按钮更新通知界面。介绍 iOS 10 通知 API 的扩展:自定义通知显示界面。
《iOS 10 day by day》是 shinobicontrols 公司编写的系列博客,介绍开发者需要了解的 iOS 10 新特性,每周更新。本系列翻译(文集地址)已取得官方授权。目录点此。仓薯翻译,欢迎指正:)
Shinobicontrols 为 iOS 和 Android 开发者提供高性能、响应式的 UI 控件 SDK,尤其是图表方面的控件。 官网 : shinobicontrols.com twitter : @shinobicontrols
我们在 Day 5 中介绍了新的 UserNotifications
框架。新框架可以统一处理本地通知和远程推送,同时增加了一些新 API 来控制等待中和已发出的通知。
以上这些都很棒,不过苹果还在通知方面更进一步,让开发者能添加一个自定义的通知界面,用户收到通知之后可以选择查看这个自定义界面。要实现这个功能,需要添加一个单独的 UserNotificationsUI
框架。这个框架的 API 特别简单,只含有一个公共的 protocol:UNNotificationContentExtension
。
工程
我们的样例工程是在上一篇文章的闹钟 app 基础上,增加了一个炫酷的自定义通知界面。通过这个界面,用户可以不用切换到闹钟 app 就能直接取消通知。先来看下效果:
![](https://img.haomeiwen.com/i227290/0c09d911ff05670f.png)
跟所有 Day by Day 系列文章一样,工程源码放在了 Github 上。
创建 Extension
iOS 10 的许多旗舰功能都是建立在苹果的 Extension 架构上的。前面的系列文章 Xcode 插件 和 iMessage 插件 都是如此。而自定义通知界面也是用同样的方法实现的。
首先,我们要给闹钟 app 的工程加一个新的 target。在下面这个选择 target 模板的界面,选择 Notification Content
。然后随便起个名字,我用的是 NagMeContentExtension
。
![](https://img.haomeiwen.com/i227290/457495502483a4e0.png)
你可能会注意到,除了默认的Info.plist
之外,这个 extension 还包含另外两个文件:
-
MainInterface.storyboard
: 我们把自定义通知界面的 UI 画在这里 -
NotificationViewController.swift
: 一个 UIViewController 的子类,这就是自定义界面的 ViewController,我们通过这个类来管理自定义的界面。
把 Extension 与通知 category 关联起来
现在工程设置好了,我们需要让系统知道,是哪个通知要展示这个界面。不知道你记不记得,上一篇文章讲过,一个 category 就是一个很简单的对象(参考 UNNotificationCategory),里面定义了你的 app 支持哪些类型的通知,以及每种通知关联了什么操作——就是用户把通知展开的时候,通知下面出现的那些操作按钮。
具体实现这一步,需要打开 extension 的 Info.plist
,展开 NSExtensionAttributes
Dictionary,把下面 UNNotificationExtensionCategory
这个键对应的值改为通知 category 的名字("reminder")。注意,这个值既可以填一个 string ,也可以填一个 string 数组,如果想让多个通知 category 共用一个 extension 界面就可以填 string 数组。
![](https://img.haomeiwen.com/i227290/9b24c7f12530f35a.png)
现在把工程 Build、Run 一下,我们可以看到一个比默认的通知弹框更有意思一点的界面。
![](https://img.haomeiwen.com/i227290/a353d8b38a265c5d.png)
管用了!现在用的是 extension 默认的 MainInterface.storyboard
界面,然后是 NotificationViewController
里的模板代码在更新界面上的 label。不过这个界面还是有几点需要改进的地方。首先,通知的内容("Walk Dog!!")在 extension 的界面上和 DefaultContent 区域重复出现了两次。我们先把这个重复的去掉吧!
去掉 DefaultContent
很简单,只需在 Info.plist
文件里的 NSExtensionAttributes 下面增加一个 key ,UNNotificationExtensionDefaultContentHidden
,然后值设为 YES
,就不会显示 DefaultContent 了。
![](https://img.haomeiwen.com/i227290/92ea97efc85da478.png)
好,下面我们来写自定义的界面吧。
自定义的通知界面
切换到 MainInterface.storyboard
,加上 UI 控件。加一个 label 描述提醒的事项,加一个小喇叭的图片。加完之后,只需拖几个 IBOutlets 出来,就大功告成啦!
收到通知的时候,我们要更新 label 上的文本,同时摇晃小喇叭的图片——用这种粗暴的方式吸引用户的注意力。要实现这些功能,需要在 NotificationViewController
里进行一些修改。我们的 viewController 实现了 UNNotificationContentExtension
这个 protocol,下面用到的就是这个 protocol 中定义的方法:
func didReceive(_ notification: UNNotification) {
label.text = "Reminder: \(notification.request.content.body)"
speakerLabel.shake() // 具体实现下载源码可以看到
}
这个方法就是收到通知之后,根据通知内容来配置通知界面的指定方法。
![](https://img.haomeiwen.com/i227290/6c1c7886eec1875c.png)
看起来还不错,但是中间有一大段空白,看上去不大美观。
幸运的是,要解决这个问题只需加 Info.plist
里再加一个 key UNNotificationExtensionInitialContentSizeRatio
,它定义了自定义通知界面的高宽比。这个值可能需要多试几次来调整,对于我们目前的情况取 0.5 就比较合适了(当宽度是 300 的时候,高度是 150)。
![](https://img.haomeiwen.com/i227290/c6a3ef31d3366d9c.png)
NotificationViewController
就是一个单纯的 UIViewController 的子类,用起来跟你平常在主 app 里用普通的 viewController 是一样的。唯一的不同点在于它的 userInteraction 是 disabled 的,意思是完全无法接收到用户的点击、触摸事件。所以有部分控件是用不了的,比如 UIScrollView、UIButton 等。
接受用户操作
自定义的界面我们画出来了,但是还有一点要改进:点击 “Cancel” 按钮,只会让用户切回到闹钟 app,这一步有点多余。
在上一篇文章我们讲了怎么给通知加上操作按钮:通知出现时可以进行的每一项操作都是一个 UNNotificationAction,关联在通知 category 上。更详细的介绍可以参考官方文档。
而 UNNotificationContentExtension
这个 protocol 提供了另一个处理点击事件的方法:didReceive(_:completionHandler:)
。我们就用这个方法,把小喇叭的 icon 改成红线划掉的小喇叭,然后把通知从 UNNotificationCenter
中移除。
func didReceive(_ response: UNNotificationResponse,
completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "cancel" {
let request = response.notification.request
let identifiers = [request.identifier]
// 移除后续的通知
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: identifiers)
// 移除之前的通知,不在用户的通知列表里占地方了
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
// 通知取消的视觉反馈
speakerLabel.text = "🔇"
speakerLabel.cancelShake()
completion(.doNotDismiss)
}
else {
completion(.dismiss)
}
}
相关的通知都移除了,UI 也更新了,接下来我们需要告诉系统该怎么处置这个通知界面。因为我们想让用户看到被划掉的小喇叭,得到通知被取消的视觉反馈,所以要把通知留在屏幕上,因此回调里传入 UNNotificationContentExtensionResponseOption
的一个取值 .doNotDismiss
。
![](https://img.haomeiwen.com/i227290/8ceac0cb657dfe5d.png)
既然要用这个方法处理点击,就得处理好每一个按钮事件。在这个例子里,我们只有一个“Cancel”按钮。然而,如果还有别的按钮,它们的点击事件也需要处理好:要么也在 extension 工程的这个方法里处理,要么回调传
UNNotificationContentExtensionResponseOption.dismissAndForwardAction
,传给主 app 去处理。
扩展阅读
UserNotificationsUI
这个框架并没有什么惊天动地的突破,但它能让用户与 app 的交互更便捷。用户可以直接对通知进行操作,不用再切换到发出通知的 app 了;甚至通知界面的 UI 也能动态改变,来更好地反馈用户操作的结果。
关于通知的其他“高级”特性,我推荐看看 WWDC 2016 的演讲视频。这场视频中,演讲者给出了几个苹果官方 app 自定义通知界面的例子,比如接收日程邀请。
原文地址:iOS 10 Day by Day :: Day 6 :: Notification Content Extensions
原作者:Sam Burnstone @sam_burnstone
ShinobiControls 官网:ShinobiControls.com twitter : @shinobicontrols
译者:戴仓薯