Swift - UserNotifications框架使用详解6(ServiceExtension、多媒体内容推送)

十二、使用 Notification Service Extension 拦截并修改通知

iOS 10 中添加了两个与通知相关的 extension:Service Extension 和 Content Extension。本文先介绍下前者。

1,基本介绍

Service Extension 目前只对远程推送的通知有效。

Service Extension 可以让我们有机会在收到远程推送通知后,展示之前对通知内容进行修改。

通过本机截取推送并替换内容的方式,我们可以实现端到端 (end-to-end) 的推送加密:

我们在服务器推送 payload 中加入加密过的文本,在客户端接到通知后使用预先定义或者获取过的密钥进行解密,然后立即显示。

这样一来,即使推送信道被第三方截取,其中所传递的内容也还是安全的。使用这种方式来发送密码或者敏感信息,对于一些金融业务应用和聊天应用来说,应该是必备的特性。

2,使用说明

(1)首先我们点击"File" -> "New" -> "Target...",使用 NotificationService 的模板来创建一个 NotificationService。

(2)NotificationService 的模板已经自动为我们生成了一些基本代码,这里对其稍作修改(自动给通知内容后面加上一个小尾巴)。NotificationService 里特别要注意如下两个方法:

1,didReceive

该方法中有一个等待发送的通知请求。我们通过修改这个请求中的 content 内容,然后在限制的时间内将修改后的内容通过调用 contentHandler 返还给系统,就可以显示这个修改过的通知了。

2,serviceExtensionTimeWillExpire

在一定时间内如果没有调用 contentHandler 的话,系统会调用这个方法,来告诉我们时间到了:

我们可以什么都不做,这样的话系统便当作什么都没发生,简单地显示原来的通知。

或许我们已经设置好了绝大部分内容,只是有很少一部分没有完成。这时我们也可以像例子中这样调用 contentHandler 来显示一个变更到一半的通知。

import UserNotifications


class NotificationService: UNNotificationServiceExtension {


    var contentHandler: ((UNNotificationContent) -> Void)?

    var bestAttemptContent: UNMutableNotificationContent?


    //我们可以在后台处理接收到的推送,让后传递修改后的的内容给contentHandler进行展示

    override func didReceive(_ request: UNNotificationRequest,

        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {

        self.contentHandler = contentHandler

        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)


        if let bestAttemptContent = bestAttemptContent {

            //给通知内容添加个小尾巴

            bestAttemptContent.body = "\(bestAttemptContent.body) 【来自hangge.com】"


            contentHandler(bestAttemptContent)

        }

    }


    //如果我们获取消息后一段时间内没有调用 contentHandler 的话,系统会调用这个方法

    override func serviceExtensionTimeWillExpire() {

        //如果消息没处理好,我们也将这个没处理完毕的消息进行展示

        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {

            contentHandler(bestAttemptContent)

        }

    }

}

(3)如果需要调试这个通知扩展类,注意 Target 要选择 NotificationService,然后编译运行时选择我们的程序。

(4)同时 Service Extension 的发布版本要低于设备的版本(比如我手机是 10.3.1,那么这里可以直接设置为 10)。

(5)最后我们在远程通知的 payload 中增加一个 mutable-content 值为 1 的项来启用内容修改(这个一定要有,否则可能会拦截通知失败)。

{

    "aps": {

        "alert": {

            "title": "最新资讯",

            "body": "2017全国文明城市公布"

        },

        "sound": "default",

        "badge": 1,

        "mutable-content": 1

    },

}

(6)可以看到客户端这边收到通知后,会自动在内容尾部增加一个段小尾巴(【来自hangge.com】)

十三、为本地通知添加多媒体内容

多媒体推送是 iOS10 新增加的一个功能。我们可以在通知中嵌入图片或者视频,这极大地丰富了推送内容的可读性和趣味性。


1,使用说明

为本地通知添加多媒体内容十分简单,只需要通过本地磁盘上的文件 URL 创建一个 UNNotificationAttachment 对象,然后将这个对象放到数组中赋值给 content 的 attachments 属性就可以了。

attachments 虽然是一个数组,但是系统只会展示第一个 attachment 对象的内容。不过我们依然可以发送多个 attachments,然后在要展示的时候再重新安排它们的顺序,以显示最符合情景的图片或者视频。另外,我们也可能会在自定义通知展示 UI 时用到多个 attachment,这个我们下文会进行演示。

系统在创建 attachement 时会根据提供的 url 后缀确定文件类型,如果没有后缀,或者后缀不正确的话,我们可以在创建时通过 UNNotificationAttachmentOptionsTypeHintKey 来指定资源类型。

2,多媒体文件的格式、尺寸限制

(1)支持的最大尺寸:

图片:10MB

音频:5MB

视频:50MB

(2)支持的文件格式:

图片:kUTTypeJPEG、kUTTypeGIF、kUTTypePNG

音频:kUTTypeAudioInterchangeFileFormat、kUTTypeWaveformAudio、kUTTypeMP3、kUTTypeMPEG4Audio

视频:kUTTypeMPEG、kUTTypeMPEG2Video、kUTTypeMPEG4、kUTTypeAVIMovie

3,使用样例

(1)下面代码我们给通知附带上一张图片:

import UIKit

import UserNotifications


class ViewController: UIViewController {


    override func viewDidLoad() {

        super.viewDidLoad()


        //设置推送内容

        let content = UNMutableNotificationContent()

        content.title = "hangge.com"

        content.body = "囤积iPhoneX的黄牛赔到怀疑人生?"


        //给通知添加图片附件

        if let imageURL = Bundle.main.url(forResource: "image", withExtension: "png"),

            let attachment = try? UNNotificationAttachment(identifier: "imageAttachment",

                                                           url: imageURL, options: nil) {

            content.attachments = [attachment]

        }


        //设置通知触发器

        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)


        //设置请求标识符

        let requestIdentifier = "com.hangge.testNotification"


        //设置一个通知请求

        let request = UNNotificationRequest(identifier: requestIdentifier,

                                            content: content, trigger: trigger)


        //将通知请求添加到发送中心

        UNUserNotificationCenter.current().add(request) { error in

            if error == nil {

                print("Time Interval Notification scheduled: \(requestIdentifier)")

            }

        }

    }


    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

    }

}

(2)效果图

上面代码运行后,在通知显示时,横幅或者弹窗将附带有设置的图片。

使用 3D Touch pop 通知或者下拉通知显示详细内容时,图片也会被放大展示。

除了图片以外,通知还支持音频以及视频。我们可以将 MP3 或者 MP4 这样的文件提供给系统,从而在通知中进行展示和播放。

 

4,访问已创建的 attachment 的内容

我们可以访问一个已经创建的 attachment 的内容,但是要注意权限问题。可以使用 startAccessingSecurityScopedResource 来暂时获取已创建的 attachment 的访问权限。

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()

    }

}

十四、为远程推送添加多媒体内容

1,实现原理

对于远程推送,我们也可以显示图片等多媒体内容。不过需要通过上面介绍的 Notification Service Extension 来修改推送通知内容的技术。具体流程如下:

我们在推送的 payload 中指定需要加载的图片资源地址,这个地址可以是应用 bundle 内已经存在的资源,也可以是网络的资源。

客户端收到通知后,根据资源地址创建相应的 UNNotificationAttachment。由于只能使用本地资源创建 UNNotificationAttachment,所以如果多媒体还不在本地的话,我们需要先将其下载到本地。

在完成 UNNotificationAttachment 创建后,我们就可以像本地通知一样,将它设置给 attachments 属性,然后调用 contentHandler 了。

2,使用样例

(1)假设我们的远程通知 payload 报文如下,其中:

mutable-content:表示我们会在接收到通知时需要对内容进行更改。

image:表示需要显示的图片的地址。

{

    "aps": {

        "alert": {

            "title": "最新资讯",

            "body": "2017全国文明城市公布"

        },

        "sound": "default",

        "badge": 1,

        "mutable-content": 1

    },

    "image": "https://img1.gtimg.com/ninja/2/2017/05/ninja149447456097353.jpg"

}

(2)项目这边创建一个 NotificationService,作用是接收到上面这样的通知时会自动提取图片地址、下载,并生成 attachment,然后进行通知展示。

import UserNotifications


class NotificationService: UNNotificationServiceExtension {


    var contentHandler: ((UNNotificationContent) -> Void)?

    var bestAttemptContent: UNMutableNotificationContent?


    //我们可以在后台处理接收到的推送,让后传递修改后的的内容给contentHandler进行展示

    override func didReceive(_ request: UNNotificationRequest,

                             withContentHandler contentHandler:

        @escaping (UNNotificationContent) -> Void) {

        self.contentHandler = contentHandler

        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)


        if let bestAttemptContent = bestAttemptContent {

            //将远程推送通知中的图片下载到本地,并显示

            if let imageURLString = bestAttemptContent.userInfo["image"] as? String,

                let URL = URL(string: imageURLString) {

                downloadAndSave(url: URL) { localURL in

                    if let localURL = localURL {

                        do {

                            let attachment = try UNNotificationAttachment(identifier: "download",

                                                                          url: localURL,

                                                                          options: nil)

                            bestAttemptContent.attachments = [attachment]

                        } catch {

                            print(error)

                        }

                    }

                    contentHandler(bestAttemptContent)

                }

            }

        }

    }


    //如果我们获取消息后一段时间内没有调用 contentHandler 的话,系统会调用这个方法

    override func serviceExtensionTimeWillExpire() {

        //如果消息没处理好,我们也将这个没处理完毕的消息进行展示

        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {

            contentHandler(bestAttemptContent)

        }

    }


    //将图片下载到本地临时文件夹中

    private func downloadAndSave(url: URL, handler: @escaping (_ localURL: URL?) -> Void) {

        let task = URLSession.shared.dataTask(with: url, completionHandler: {

            data, res, error in

            var localURL: URL? = nil

            if let data = data {

                //取得当前时间的时间戳

                let timeInterval = Date().timeIntervalSince1970

                let timeStamp = Int(timeInterval)

                //文件后缀

                let ext = (url.absoluteString as NSString).pathExtension

                let temporaryURL = FileManager.default.temporaryDirectory

                let url = temporaryURL.appendingPathComponent("\(timeStamp)")

                    .appendingPathExtension(ext)


                if let _ = try? data.write(to: url) {

                    localURL = url

                }

            }

            handler(localURL)

        })

        task.resume()

    }

}

(3)具体效果如下。可以看到即使是远程通知,附带的也是网络图片,但也是可以正常显示的。

原文出自:www.hangge.com  转载请保留原文链接:https://www.hangge.com/blog/cache/detail_1852.html

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