通知相关系列文章
iOS10 之前通知使用介绍
[iOS] 通知详解: UIUserNotification
iOS10 相关API
[iOS] 通知详解:iOS 10 UserNotifications API
iOS10 本地/远程通知
[iOS] 通知详解: iOS 10 UserNotifications
iOS10 通知附加包
[iOS] 通知详解: iOS 10 UserNotifications -- 附加包Media Attachments
iOS10 自定义UI
[iOS] 通知详解: iOS 10 UserNotifications -- 自定义通知UI
新建 Notification content extension
通知UI的自定义使用到了Notification content extension,同创建Notification Service Extension一样,我们需要创建一个新的 Target ,只不过这次选择Notification content extension:
下一步,为这个Target起一个名字,完成即可!
可以看到多了下面几个文件:
这里的NotificationViewController就是我们编写自定义UI逻辑的控制器,他和一般的控制器一样,MainInterface.storyboard是与其绑定的,可以在此往storyboard添加控件。Info.plist为其相关的配置文件,有些操作需要在这里配置一些设置后,才能看到预期的效果,下面关于此部分的所有配置,都是在这里进行的。
在NotificationViewController中,实现了UNNotificationContentExtension协议,他有两个协议方法
// 必须实现,用来处理自定义UI的内容
public func didReceive(_ notification: UNNotification)
// 选择实现,用来处理action的事件
optional public func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void)
第一个是必须要实现的,在NotificationViewController默认已经实现了,主要是处理当通知来的时候,布局自定义的UI内容以及相关的处理逻辑的地方;
第二个方法,当前发送的通知带有快捷操作action的时候(UNNotificationAction),来处理相关的点击事件。
因为我们自定义的任何View都是无法交互的,只能借助添加的action来处理相关的事件。
绑定 Category
Notification content extension添加完成后,在通知界面是看不到我们自定义的UI的,还需要绑定相关的 Category,即在创建通知的时候,我们添加的UNNotificationCategory,如果没有需要交互的action,可以传个空数组:
let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))
然后在该扩展下的Info.plist中添加该Category的identifier,对应着UNNotificationExtensionCategory字段:
注意:这里的UNNotificationExtensionCategory可以修改为数组类型,如果我们有多个Category公用一套UI,可以将此值修改为Array类型,然后在数组里添加多个 Category 的identifier。
再去发送通知,注意此时的Payload中要添加category字段:
{
"aps":
{
"alert":
{
"title":"iOS10远程推送标题",
"subtitle" : "iOS10 远程推送副标题",
"body":"这是在iOS10以上版本的推送内容,并且携带来一个图片附件"
},
"category":"categoryidentifier",
"badge":1,
"mutable-content":1,
"sound":"default",
"image":"https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3078873712,1340878922&fm=26&gp=0.jpg"
}
}
弹框和锁屏页显示的内容和之前一样,打开通知或者下拉弹框,就会看到我们自定义的页面了:
比较丑的那部分就是我们自定义的UI了,可以看到真的很丑,大小还不合适,而且和系统默认的也重复的。
如果我们想要隐藏系统默认的内容页面,也就是下面的那部分,头是隐藏不了的;只需要在Info.plist里添加字段UNNotificationExtensionDefaultContentHidden,bool类型并设置其值为YES;
关于页面太大的问题,有的说通过修改其宽高比UNNotificationExtensionInitialContentSizeRatio的值,如果你的UI是固定的,可以通过适配大部分屏幕后,通过修改此值来得到合适的宽高比视图,但其值也是需要各种尝试的。
另外也可以使用autolayout,如果是在storyboard里添加的实图,顺便添加相应的约束即可;然后重新发送消息,大概就是这个样子:
这样,通知页面会先显示一个大的页面,然后再resize到约束后的页面大小,这样就会一个缩放的动画,这是因为在通知即将展示的时候,系统还没有调用我们的约束代码,也就是约束还没有起效,所以会有个resize的动画过渡。
为解决这个问题,只能在自定义UI的时候配合UNNotificationExtensionInitialContentSizeRatio设置合适页面大小,即采用固定的样式来展示通知内容。
显示附加包(attachment)的内容
如果我们的通知是携带附加包的,例如一张图片,添加自定义的UI后,打开通知或者下拉弹框会发现,大图不显示了,我们可以把相关的内容显示到自定义的UI上,还是以图片为例,在didReceive方法里添加以下获取附加包数据的代码:
if let att = notification.request.content.attachments.first {
if att.url.startAccessingSecurityScopedResource() {
self.coverImage.image = UIImage(contentsOfFile: att.url.path)
att.url.stopAccessingSecurityScopedResource()
}
}
这里需要说一下startAccessingSecurityScopedResource和stopAccessingSecurityScopedResource方法:
因为attachment是由系统单独管理的,所以这里我们在使用attachment之前,需要告诉iOS系统,我们需要使用它,并且在使用完毕之后告诉系统我们使用完毕了。对应上述代码就是startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource()的操作。当我们获取到了attachment的使用权之后,我们就可以使用那个文件获取我们想要的信息了。
再去发送上面的Payload,打开后就是这样了:
意思是那么个意思,但是加载的图片好像不太完整,上面我们是从attachment里面获取的,目前不清楚出现这个情况的原因,可能原数据被压缩了导致数据不全。所以,我们可以从发送的Payload中来获取数据:
if let aps = notification.request.content.userInfo["aps"] as? [String: Any] {
if let imagePath = aps["image"] as? String {
if let url = URL(string: imagePath) {
if let data = try? Data.init(contentsOf: url) {
self.coverImage.image = UIImage(data: data)
}
}
}
}
这样就能正常显示了:
处理action事件
如果我们添加的category是带有action的,并且action的点击事件要响应到我们自定义的UI里面,例如点击的时候更换一个图片, 就需要UNNotificationContentExtension协议的另一个协议方法了:
// response:可以拿到点击的action,和通知的内容
// completion:处理完成后需要告诉系统,接下来该如何处理该通知
optional public func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void)
UNNotificationContentExtensionResponseOption 是一个枚举,他有三个值:
@available(iOS 10.0, *)
public enum UNNotificationContentExtensionResponseOption : UInt {
// 通知页面不会消失,例如更新UI,显示出来
case doNotDismiss
// 关闭当前通知页面
case dismiss
// 将此action事件传递给app,在通知中心的代理方法里继续处理该事件
case dismissAndForwardAction
}
需要注意的是,如果实现了此方法,就需要对所有添加的action进行处理,而不能只处理某个action
例如我们这样处理点击事件:
func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
// 改变标题
self.label?.text = self.label?.text ?? "" + "点击了 "
if response.actionIdentifier == "okidentifier" {
// 点击了查看按钮,这里改变了标题的颜色
self.label?.textColor = UIColor.red
completion(.doNotDismiss)
} else if response.actionIdentifier == "cancelidentifier" {
// 点击了关闭,直接关闭通知
completion(.dismiss)
} else {
// 如果还有其他的按钮,交给app处理
completion(.dismissAndForwardAction)
}
}
然后,在创建通知的时候,添加相应的action:
let okAction = UNNotificationAction(identifier: "okidentifier", title: "查看", options: UNNotificationActionOptions.foreground)
let cancel = UNNotificationAction(identifier: "cancelidentifier", title: "关闭", options: UNNotificationActionOptions.destructive)
let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [okAction, cancel], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))
再次发生Payload,点击通知的查看action,会发现标题和标题的颜色都修改了。
处理快捷回复(输入文字)
前面知道,我们可以在通知中心进行快捷回复,只需要创建UNTextInputNotificationAction的action,添加到对应的category即可:
let okAction = UNTextInputNotificationAction(identifier: "okidentifier", title: "回复", options: .foreground, textInputButtonTitle: "快捷回复", textInputPlaceholder: "请输入。。。")
let cancel = UNNotificationAction(identifier: "cancelidentifier", title: "关闭", options: UNNotificationActionOptions.destructive)
let category = UNNotificationCategory(identifier: "categoryidentifier", actions: [okAction, cancel], intentIdentifiers: [], options: UNNotificationCategoryOptions.customDismissAction)
UNUserNotificationCenter.current().setNotificationCategories(Set.init([category]))
这里,我们需要这样来处理接收到的反馈:
func didReceive(_ response: UNNotificationResponse, completionHandler completion: @escaping (UNNotificationContentExtensionResponseOption) -> Void) {
self.label?.text = "点击了 "
if response.actionIdentifier == "okidentifier" {
// 这里处理输入框的事件
if let txRes = response as? UNTextInputNotificationResponse {
// 如果是输入框的反馈,获取输入内容,显示出来
let text = txRes.userText
self.label?.text = text
}
// 点击了查看按钮,这里改变了标题的颜色
self.label?.textColor = UIColor.red
completion(.doNotDismiss)
} else if response.actionIdentifier == "cancelidentifier" {
// 点击了关闭,直接关闭通知
completion(.dismiss)
} else {
// 如果还有其他的按钮,交给app处理
completion(.dismissAndForwardAction)
}
}
然后在通知中心点击回复按钮的时候会弹出输入框,输入结束后,通知中心即显示了输入的内容:
到此,断断续续,总算是把通知相关的内容整体过了一遍,虽然感觉上还是有些逻辑混乱,基本上能够体现通知的一些使用方法。
参考文章
iOS10-UserNotifications
WWDC2016 Session笔记 - iOS 10 推送Notification新特性
网友评论