在今年6月14号苹果WWDC开发者大会上,苹果带来了新的iOS系统——iOS 10。苹果为iOS 10带来了十大项更新。苹果高级副总裁Craig Federighi称这次对iOS的更新是“苹果史上最大的iOS更新”。数组
HomeKit:iOS 10新增智能家庭应用,支持一键场景模式,HomeKit能够与Siri相链接。 服务器
苹果电话:苹果更新了电话功能,来电时能够区别出骚扰电话。 微信
如下是我关于关于iOS 10中变化比较大的推送通知的学习笔记。app
让咱们先来看看用户推送在iOS X中的样子,以下图ide
上图这是在锁屏界面下的推送。支持抬起手机唤醒功能。函数
上图是Banner,能够看到这个推送更加的易读,而且包含更多的内容。post
上图是通知中心。从上面三种图能够看到,它们都长一个样。学习
在iOS 8 中,咱们能够给推送增长用户操做,这样使推送更加具备交互性,而且容许用户去处理用户推送更加的迅速。到了iOS 9 中,苹果又再次增长了快速回复功能,进一步的提升了通知的响应性。开发者能够容许用户经过点击推送,并用文字进行回复。再就到了iOS 10 中,推送变得更加给力。由于在iOS X中,推送对iOS系统来讲,是很重要的一部分。在平常使用中,咱们会常常和推送打交道。推送是咱们和设备进行互动很是重要的方式。动画
在iOS X 中,你能够按压推送,推送就会被展开,展现出更加详细的用户界面。展现出来的详细界面对用户来讲,提供了更加有用的信息。用户能够经过点击下面的按钮,来处理一些事件,而且推送的详细界面也会跟着用户的操做进行更新UI界面。ui
iOS 8 中iMessage支持了快速回复功能,可是你只能看见一条信息,而且你也只能回复一条信息。可是在iOS X中,你能够展开推送,这个时候你就能够看到整个对话的内容了。你能够等待你的朋友回复,你再回复他,而且能够回复不少条。
以上就是iOS X的强大功能。以上的全部功能都能经过iOS X的新API来实现。全部的新特性都能在咱们开发者开发的app里面有所体现。
若是常用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,而后知道去你本身的服务器哪里去下载内容。
经过设置完上述的部分,推送就被推送到了每一个设备的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 系统就会展现给用户。
经过以上的设置,咱们就能在推送中看到丰富的媒体信息了。用户并不须要去打开app,也不用去点击下载。
简单的概述一下Media Attachments:
经过以上能够看出,Media Attachments很是的酷,它为咱们提供了更加丰富的推送内容。
接下来咱们再来看看如何自定义推送的用户界面
要想建立一个自定义的用户界面,须要用到Notification content extension。
先来讲说下面这个例子的应用场景:
好比有个朋友在日历中给我了一个聚会的邀请,这个时候就来了推送,推送里面的内容就是包含了聚会的时间地点信息,推送下面有三个按钮,接受,谢绝。下面的例子都以此为例。
Notification content extension容许开发者加入自定义的界面,在这个界面里面,你能够绘制任何你想要的东西。可是有一个最重要的限制就是,这个自定义的界面没有交互。它们不能接受点击事件,用户并不能点击它们。可是推送通知仍是能够继续与用户进行交互,由于用户可使用notificaiton的actions。extension能够处理这些actions。
接下来咱们就来讲说如何自定义界面
先来看一个日历的推送例子:
上图,整个推送分4段。用户能够经过点击Header里面的icon来打开app,点击取消来取消显示推送。Header的UI是系统提供的一套标准的UI。这套UI会提供给全部的推送通知。
Header下面是自定义内容,这里就是显示的Notification content extension。在这里,就能够显示任何你想绘制的内容了。你能够展现任何额外的有用的信息给用户。
content extension下面就是default content。这里是系统的界面。这里的系统界面就是上面推送里面payload里面附带的内容。这也就是iOS 9 以前的推送的样子。
最下面一段就是notification action了。在这一段,用户能够触发一些操做。而且这些操做还会相应的反映到上面的自定义的推送界面content extension中。
接下来咱们就来看看如何建立一个Notification content extension
第一件事就是去建立一个新的target。建立好了以后,Xcode会自动帮咱们生成一个template。template会在新的target里面生成3个文件,一个新的ViewController,main Interface storyboard,info.plist。info.plist中就是能够定义化一些target的配置。
打开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。
接下来,咱们须要作的是,告诉iOS系统,推送送达以后,iOS系统如何找到你自定义的Notification content extension。
Notification content extension和咱们注册notification actions同样,注册的相同的category。这个例子中,咱们使用event-invite。值得提到的一点是,这里的extension是能够为一个数组的,里面能够为多个category,这样作的目的是多个category共用同一套UI。
上图中,event-invite 和 event-update就共用了一套UI。这样咱们就能够把他们打包到一个extension里面来。可是不一样的category是独立的,他们能够相应不一样的actions。
经过以上设置,iOS系统就知道了咱们的target了。
接下来咱们来自定义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没法展现的,好比说位置信息等等。
代码完成以后就是如上的样子,中间就是咱们自定义的UIView了。可是这样子会有2个问题。第一个问题就是这个自定义的View实在太大了。大量的空白不须要显示出来。第二个问题就是咱们自定义的内容和下面默认的推送内容重复了。咱们须要去掉一份。
咱们先来改进上面说的第二个问题。 这个问题很简单,其实就是一个plist的设置。咱们能够在plist里面把默认的content隐藏。设置以下图。
再来讲说第一个问题,界面大小的问题。 咱们能够经过平时咱们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。
解决完上面2个问题,界面就会变成这个样子。看上去比以前好不少了。正常的尺寸,没有多余的空白。没有重复信息。可是这又出现了另一个问题。当通知展现出来以后,它的大小并非正常的咱们想要的尺寸。iOS系统会去作一个动画来Resize它的大小。以下图,系统会先展示出第一张图,而后紧接着展现第二张图,这个用户体验不好。
会出现上面这张图的缘由是,在推送送达的那一刻,iOS系统须要知道咱们推送界面的最终大小。可是咱们自定义的extension在系统打算展现推送通知的那一刻,并尚未启动。因此这个时候,在咱们代码都尚未跑起来以前,咱们须要告诉iOS系统,咱们的View最终要展现的大小。
如今问题又来了。这些通知会跑在不一样的设备上,不一样的设备的屏幕尺寸不一样。为了解决这个问题,咱们须要设置一个content size ratio。
这个属性定义了宽和高的比例。固然设置了这个比例之后,也并非万能的。由于你并不知道你会接受到多长的content。当你仅仅只设置比例,仍是不能完整的展现全部的内容。有些时候若是咱们能够知道最终的尺寸,那么咱们固定尺寸会更好。
咱们能够给这个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就变成下面这个样子了。
说道这里,咱们不得不说一下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。
这里还有一个问题,就是用户点完评论按钮以后,以前的接受和谢绝的按钮就消失了。这个时候用户可能有这个需求,想又评论,又接受或者谢绝。那么咱们就须要在下面键盘上加入这两个按钮。以下图这样子。
这里并无新的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的全部新特性,经过上文,咱们学到的如下的知识,总结一下:
最后,请你们多多指教。新浪微博@halfrost。