iOS 10 推送Notification新特性

前言
在今年6月14号苹果WWDC开发者大会上,苹果带来了新的iOS系统——iOS 10。苹果为iOS 10带来了十大项更新。苹果高级副总裁Craig Federighi称此次对iOS的更新是“苹果史上最大的iOS更新”。

1467010311344431.jpg

新的屏幕通知查看方式:苹果为iOS 10带来了全新的通知查看功能,即抬起iPhone的屏幕,用户就能看到目前的通知和更新情况。

苹果将Siri开放给第三方开发者: 现在用户可以让Siri实现更多的功能,例如让Siri向自己的联系人发送微信信息等。目前Siri可以直接支持的应用有微信、WhatsApp以及Uber、滴滴、Skype等。

Siri将会更加智能:Siri将拥有更多对语境的意识。基于用户的地点、日历、联系人、联系地址等,Siri会做出智能建议。Siri将越来越成为一个人工智能机器人,具备深度学习功能。

照片应用更新:基于深度学习技术,iOS 10对照片应用有比较大的更新。iOS 10对照片的搜索能力进一步增强,可以检测到新的人物和景色。未来的iPhone能够将相关的照片组织在一起,比如某次旅行的照片、某个周末的照片,并且能够进行自动编辑。iOS 10照片还新增了一个“记忆”标签。

苹果地图:有点类似Siri和照片的更新,苹果地图也增加了很多预测功能,例如苹果地图能够将提供附近的餐厅建议。苹果地图的界面也得到了重新设计,更加的简洁,并增加了交通实时信息。新的苹果地图还将整合在苹果CarPlay中,将为用户提供turn-by-turn导航功能。和Siri一样,地图也将开放给开发者。

苹果音乐:苹果音乐的界面得到了更新,界面会更加简洁、支持多任务,增加最近播放列表。苹果音乐现在已经有1500万付费用户。

HomeKit:iOS 10新增智能家庭应用,支持一键场景模式,HomeKit可以与Siri相连接。

1467010742980067.jpg

苹果电话:苹果更新了电话功能,来电时可以区别出骚扰电话。

1467010894357337.jpg

iMesseage:在iMessage方面,用户可以直接在文本框内发送视频、链接,分享实时照片。另外,苹果还增添了表情预测功能,打出的文字若和表情相符,将会直接推荐相关表情。

以下是我关于关于iOS 10中变化比较大的推送通知的学习笔记。
目录
1.Notification User Interface
2.Media Attachments
3.Customize user interface
4.Customize Actions
一. Notification User Interface
让我们先来看看用户推送在iOS X中的样子,如下图:

1467011160771897.jpg

上图这是在锁屏界面下的推送。支持抬起手机唤醒功能。
1467011213392479.jpg

上图是Banner,可以看到这个推送更加的易读,并且包含更多的内容。
[图片上传中。。。(6)]
上图是通知中心。从上面三种图可以看到,它们都长一个样。
1467011251463529.jpg

在iOS 8 中,我们可以给推送增加用户操作,这样使推送更加具有交互性,并且允许用户去处理用户推送更加的迅速。到了iOS 9 中,苹果又再次增加了快速回复功能,进一步的提高了通知的响应性。开发者可以允许用户通过点击推送,并用文字进行回复。再就到了iOS 10 中,推送变得更加给力。因为在iOS X中,推送对iOS系统来说,是很重要的一部分。在日常使用中,我们会经常和推送打交道。推送是我们和设备进行互动非常重要的方式。
在iOS X 中,你可以按压推送,推送就会被展开,展示出更加详细的用户界面。展示出来的详细界面对用户来说,提供了更加有用的信息。用户可以通过点击下面的按钮,来处理一些事件,并且推送的详细界面也会跟着用户的操作进行更新UI界面。
1467012541300441.jpg

iOS 8 中iMessage支持了快速回复功能,但是你只能看见一条信息,并且你也只能回复一条信息。但是在iOS X中,你可以展开推送,这个时候你就可以看到整个对话的内容了。你可以等待你的朋友回复,你再回复他,并且可以回复很多条。
以上就是iOS X的强大功能。以上的所有功能都能通过iOS X的新API来实现。所有的新特性都能在我们开发者开发的app里面有所体现。
二. Media Attachments
1467012570942124.jpg

如果经常使用iMessage的朋友们,就会经常收到一些信息,附带了一些照片或者视频,所以推送中能附带这些多媒体是非常重要的。如果推送中包含了这些多媒体信息,可以使用户不用打开app,不用下载就可以快速浏览到内容。众所周知,推送通知中带了push payload,及时去年苹果已经把payload的size提升到了4k bites,但是这么小的容量也无法使用户能发送一张高清的图片,甚至把这张图的缩略图包含在推送通知里面,也不一定放的下去。在iOS X中,我们可以使用新特性来解决这个问题。我们可以通过新的service extensions来解决这个问题。
为了能去下载service extension 里面的attachment,我们必须去按照如下的要求去设置你的推送通知,使你的推送通知是动态可变的。
{
aps: {
alert : {……}
mutable-content : 1
}

my-attachment : https:
//example.com/phtos.jpg"

}

在上面代码中,可以看到加载了一个mutable-content 的flag,然后我们就可以引用一个链接,把你想加入到推送里面的attachments加入到里面来。在上面的例子里面,我们就加入了一个URL。更复杂的,你甚至可以去加入一个identifier来标示你想加入到推送里面的内容,这个identifier是你app知道的,app能通过拿到identifier,然后知道去你自己的服务器哪里去下载内容。


1467012616388318.jpg

通过设置完上述的部分,推送就被推送到了每个设备的Service Extension那里了。在每个设备里面的Service Extension里面,就可以下载任意想要的attachment了。然后推送就会带着下载好的attachment推送到手机并显示出来了。
如果来设置Service Extension呢?来看看如下的代码:
// Adding an attachment to a user notification
public class NotificationService: UNNotificationServiceExtension {
override public func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: (UNNotificationContent) -> Void)
{
let fileURL = // ...
let attachment = UNNotificationAttachment(identifier: "image",
url: fileURL,
options: nil)
let content = request.content.mutableCopy as! UNMutableNotificationContent
content.attachments = [ attachment ]
contentHandler(content)
}
}
首先定义了一个didReceive的方法,用来接收request,后面跟着withContentHandler的回调函数。
这个NotificationServiceExtension会在收到推送之后,被调用,然后在这个方法里面去下载自己的attachment。下载可以通过URL,或者任何你喜欢的方式。当下载完成之后,就可以创建attachment对象了。创建完UNMutableNotificationContent,我们就可以把这个加入到推送的content中了。最后,通过contentHandler回调,把它传递给iOS系统,iOS 系统就会展示给用户。


1467012645824290.jpg

通过以上的设置,我们就能在推送中看到丰富的媒体信息了。用户并不需要去打开app,也不用去点击下载。
简单的概述一下Media Attachments:

新特性使推送支持附带Media Attachments。本地推送和远程推送同时都可支持。

attachment支持图片,音频,视频,系统会自动提供一套可自定义化的UI,专门针对这3种内容。

在service extension里面去下载attachment,但是需要注意,service extension会限制下载的时间,并且下载的文件大小也会同样被限制。这里毕竟是一个推送,而不是把所有的内容都推送给用户。所以你应该去推送一些缩小比例之后的版本。比如图片,推送里面附带缩略图,当用户打开app之后,再去下载完整的高清图。视频就附带视频的关键帧或者开头的几秒,当用户打开app之后再去下载完整视频。

把下载完成的attachment加入到notification中。

推送里面包含的attachment这些文件,是由系统帮你管理的,系统会把这些文件放在单独的一个地方,然后统一管理。

额外说明一点,推送的attachment的图片还可以包含GIF图。

通过以上可以看出,Media Attachments非常的酷,它为我们提供了更加丰富的推送内容。
接下来我们再来看看如何自定义推送的用户界面
三. Customize user interface
要想创建一个自定义的用户界面,需要用到Notification content extension。
先来说说下面这个例子的应用场景:
比如有个朋友在日历中给我了一个聚会的邀请,这个时候就来了推送,推送里面的内容就是包含了聚会的时间地点信息,推送下面有三个按钮,接受,谢绝。下面的例子都以此为例。
Notification content extension允许开发者加入自定义的界面,在这个界面里面,你可以绘制任何你想要的东西。但是有一个最重要的限制就是,这个自定义的界面没有交互。它们不能接受点击事件,用户并不能点击它们。但是推送通知还是可以继续与用户进行交互,因为用户可以使用notificaiton的actions。extension可以处理这些actions。
接下来我们就来说说如何自定义界面
1. 推送的四部分
先来看一个日历的推送例子:

1467012706770305.jpg

上图,整个推送分4段。用户可以通过点击Header里面的icon来打开app,点击取消来取消显示推送。Header的UI是系统提供的一套标准的UI。这套UI会提供给所有的推送通知。
Header下面是自定义内容,这里就是显示的Notification content extension。在这里,就可以显示任何你想绘制的内容了。你可以展示任何额外的有用的信息给用户。
content extension下面就是default content。这里是系统的界面。这里的系统界面就是上面推送里面payload里面附带的内容。这也就是iOS 9 之前的推送的样子。
最下面一段就是notification action了。在这一段,用户可以触发一些操作。并且这些操作还会相应的反映到上面的自定义的推送界面content extension中。
2.创建Notification content extension
接下来我们就来看看如何创建一个Notification content extension
1467012741461416.jpg

第一件事就是去创建一个新的target。创建好了之后,Xcode会自动帮我们生成一个template。template会在新的target里面生成3个文件,一个新的ViewController,main Interface storyboard,info.plist。info.plist中就是可以定义化一些target的配置。
1467012788934009.jpg

打开Notification content extension的ViewController

// Minimal Content Extension
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet var label: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
// Do any required interface initialization here.
}
func didReceive(_ notification: UNNotification) {
label?.text = notification.request.content.body
}
}

我们会发现,这个ViewController是UIViewController的子类,其实就是一个很普通的ViewController,和我们平时使用的没有啥两样。后面是UNNotificationContentExtension的protocol,这里是系统要求你必须实现的协议。
UNNotificationContentExtension只有一个required的方法,就是didReceive方法。当推送到达你的设备之后,这个didReceive方法会随着ViewController的生命周期的方法 ,一起被调用。当开发者给推送加上expands的时候,一旦推送送达以后,这时会接到所有的ViewController生命周期的方法,和didReceive方法。这样,我们就可以接收notification object ,接着更新UI。
3. 配置target
接下来,我们需要做的是,告诉iOS系统,推送送达之后,iOS系统如何找到你自定义的Notification content extension。

1467012819718082.jpg

Notification content extension和我们注册notification actions一样,注册的相同的category。这个例子中,我们使用event-invite。值得提到的一点是,这里的extension是可以为一个数组的,里面可以为多个category,这样做的目的是多个category共用同一套UI。
1467012846191744.jpg

上图中,event-invite 和 event-update就共用了一套UI。这样我们就可以把他们打包到一个extension里面来。但是不同的category是独立的,他们可以相应不同的actions。
通过以上设置,iOS系统就知道了我们的target了。
4. 自定义用户UI界面
接下来我们来自定义UI界面。

// Notification Content Extension
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet var eventTitle: UILabel!
@IBOutlet var eventDate: UILabel!
@IBOutlet var eventLocation: UILabel!
@IBOutlet var eventMessage: UILabel!
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
eventTitle.text = content.title
eventDate.text = content.subtitle
eventMessage.text = content.body
if let location = content.userInfo["location"] as? String {
eventLocation.text = location
}
}
}

上述代码中,我们在stroyboard 里面加入了一些labels 。当接收到推送的时候,我们提取出内容,得到我们想要的内容,然后把这些内容设置到label上面去,并展示出来。在content的userinfo里面我们还能加入一些额外的信息,这些信息是标准的payload无法展示的,比如说位置信息等等。

1467012887153413.jpg

代码完成之后就是如上的样子,中间就是我们自定义的UIView了。但是这样子会有2个问题。第一个问题就是这个自定义的View实在太大了。大量的空白不需要显示出来。第二个问题就是我们自定义的内容和下面默认的推送内容重复了。我们需要去掉一份。
5.改进
我们先来改进上面说的第二个问题。
这个问题很简单,其实就是一个plist的设置。我们可以在plist里面把默认的content隐藏。设置如下图。
1467012919982324.jpg

再来说说第一个问题,界面大小的问题。
我们可以通过平时我们Resize其他ViewController一样,来Resize这个ViewController。来看看如下的代码。
// Notification Content Extension
class NotificationViewController: UIViewController, UNNotificationContentExtension {
override func viewDidLoad() {
super.viewDidLoad()
let size = view.bounds.size
preferredContentSize = CGSize(width: size.width, height: size.width / 2)
}
func didReceive(_ notification: UNNotification) {
// ...
}
}
这里我们也可以加入constraints来做autolayout。
1467012951689066.jpg

解决完上面2个问题,界面就会变成这个样子。看上去比之前好很多了。正常的尺寸,没有多余的空白。没有重复信息。但是这又出现了另外一个问题。当通知展示出来之后,它的大小并不是正常的我们想要的尺寸。iOS系统会去做一个动画来Resize它的大小。如下图,系统会先展现出第一张图,然后紧接着展示第二张图,这个用户体验很差。
1467012981105092.jpg

1467013016284006.jpg

会出现上面这张图的原因是,在推送送达的那一刻,iOS系统需要知道我们推送界面的最终大小。但是我们自定义的extension在系统打算展示推送通知的那一刻,并还没有启动。所以这个时候,在我们代码都还没有跑起来之前,我们需要告诉iOS系统,我们的View最终要展示的大小。
现在问题又来了。这些通知会跑在不同的设备上,不同的设备的屏幕尺寸不同。为了解决这个问题,我们需要设置一个content size ratio。
1467013048433045.jpg

这个属性定义了宽和高的比例。当然设置了这个比例以后,也并不是万能的。因为你并不知道你会接受到多长的content。当你仅仅只设置比例,还是不能完整的展示所有的内容。有些时候如果我们可以知道最终的尺寸,那么我们固定尺寸会更好。
6. 进一步美化
我们可以给这个extension加上Media Attachments。一旦我们加入Media Attachments,我们可以在content extension里面使用这些内容。

// Notification Content Extension Attachments
class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet var eventImage: UIImageView!
func didReceive(_ notification: UNNotification) {
let content = notification.request.content
if let attachment = content.attachments.first {
if attachment.url.startAccessingSecurityScopedResource() {
eventImage.image = UIImage(contentsOfFile: attachment.url.path!)
attachment.url.stopAccessingSecurityScopedResource()
}
}
}
}

我们可以提取content的attachments。前文提到过,attachment是由系统管理的,系统会把它们单独的管理,这意味着它们存储在我们sandbox之外。所以这里我们要使用attachment之前,我们需要告诉iOS系统,我们需要使用它,并且在使用完毕之后告诉系统我们使用完毕了。对应上述代码就是startAccessingSecurityScopedResource()和stopAccessingSecurityScopedResource()的操作。当我们获取到了attachment的使用权之后,我们就可以使用那个文件获取我们想要的信息了。
上述例子中,我们从attachment中获取到图片,并展示到UIImageView中。于是notification就变成下面这个样子了。

1467013198723993.jpg

四.Customize Actions
说道这里,我们不得不说一下iOS8开始引入的action的工作原理:
默认系统的Action的处理是,当用户点击的按钮,就把action传递给app,与此同时,推送通知会立即消失。这种做法很方便。
但是还有一种情况,当用户点击了按钮,希望接受一些日历上的邀请,我们需要把这个操作即时的展示在我们自定义的UI上,这是我们就只能用Notification content extension来处理这些用户点击事件了。这个时候,用户点击完按钮,我们把这个action直接传递给extension,而不是传递给app。当actions传递给extension时,它可以延迟推送通知的消失时间。在这段延迟的时间之内,我们就可以处理用户点击按钮的事件了,并且更新UI,一切都处理完成之后,我们再去让推送通知消失掉。
这里我们可以运用UNNotificationContentExtension协议的第二个方法,这方法是Optional

// Intercepting notification action response
class NotificationViewController: UIViewController, UNNotificationContentExtension {
func didReceive(_ response: UNNotificationResponse, completionHandler done: (UNNotificationContentExtensionResponseOption) -> Void) {
server.postEventResponse(response.actionIdentifier) {
if response.actionIdentifier == "accept" {
eventResponse.text = "Going!"
eventResponse.textColor = UIColor.green()
} else if response.actionIdentifier == "decline" {
eventResponse.text = "Not going :("
eventResponse.textColor = UIColor.red()
}
done(.dismiss)
}
}
}

不用这个方法的时候就可以不声明出来。但是一旦声明了,那么你就需要在这个方法里面处理推送通知里面所有的actions。这就意味着你不能只处理一个action,而不管其他的action。
在上述代码中,当用户点击了按钮,这个时候我们同步一下服务器信息,当接收到了服务器应答之后,然后我们更新UI。用户点击了“accept”之后,表示接受了这次聚会邀请,于是我们把text的颜色变成绿色。当用户点击了“decline”,表示谢绝,于是我们把text的颜色变成红色。当用户点击之后,更新完界面,我们就让推送通知消失掉。
这里值得一提的是,如果你还想把这个action传递给app,那么最后的参数应该是这样。

done(.dismissAndForwardAction)

参数设置成这样之后,用户的action就会再传递给app。
如果此时用户还想输入写文字来评论这条推送,我们该如何做?
这个输入文字的需求是来自于iOS 9。这个的使用方法和9是相同的。
// Text Input Action
private func makeEventExtensionCategory() -> UNNotificationCategory {
let commentAction = UNTextInputNotificationAction(
identifier: "comment",
title: "Comment",
options: [],
textInputButtonTitle: "Send",
textInputPlaceholder: "Type here...")
return UNNotificationCategory(identifier: "event-invite", actions: [ acceptAction, declineAction, commentAction ],
}

我们可以创建一个UNTextInputNotificationAction,并把它设置到plist里面的Category中。当推送通知到来之后,用户点击了按钮,textfield就会显示出来。同样的处理action代码如下:
// Text input action response
class NotificationViewController: UIViewController, UNNotificationContentExtension {
func didReceive(_ response: UNNotificationResponse,
completionHandler done: (UNNotificationContentExtensionResponseOption) -> Void) {
if let textResponse = response as? UNTextInputNotificationResponse {
server.send(textResponse.userText) {
}
}
}
}

这个时候当用户点击了评论按钮,就会弹出textfield。
这里还有一个问题,就是用户点完评论按钮之后,之前的接受和谢绝的按钮就消失了。这个时候用户可能有这个需求,想又评论,又接受或者谢绝。那么我们就需要在下面键盘上加入这两个按钮。如下图这样子。


1467013283104781.jpg

这里并没有新的API,还是用原来的API。我们可以使用已经存在的UIKit的API去定制输入的input accessory view。它可以让我们开发者加入自定义的按钮。
// Custom input accessory view
class NotificationViewController: UIViewController, UNNotificationContentExtension {
override func canBecomeFirstResponder() -> Bool {
return true
}
override var inputAccessoryView: UIView { get {
return inputView
}
}
func didReceive(_ response: UNNotificationResponse,
completionHandler done: (UNNotificationContentExtensionResponseOption) -> Void) {
if response.actionIdentifier == "comment" {
becomeFirstResponder()
textField.becomeFirstResponder()
}
}
}

解析一下上述的代码。首先我们需要让ViewController BecomeFirstResponder。这里做了2件事情,一是告诉responder chain,我成为了第一响应者,二是告诉iOS系统,我不想使用系统标准的text field。接着就可以创建自定义化的inputAccessoryView。如上图中显示的,带自定义的两个按钮。然后,当extension接受到了用户点击按钮后产生的action,这时自定义的textfield就会变成第一响应者,并且伴随着键盘的弹起。
注意,这里需要2个becomeFirstResponder,第一个becomeFirstResponder是使viewController变成第一响应者,这样textfield就会出现。第二个becomeFirstResponder是使我们自定义的textfield变成第一响应者,这样键盘才会弹起。
总结
以上就是iOS X中notification的所有新特性,通过上文,我们学到的以下的知识,总结一下:
什么是attachment

如何在service extension中使用attachment

如何定义content extension的用户UI界面

如何响应用户操作action

最后,请大家多多指教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容