IOS开发入门之IOS 10 适配 系列_0_ iOS10 UserNotifications 框架解析
白羽 2018-11-23 来源 :网络 阅读 934 评论 0

摘要:本文将带你了解IOS开发入门IOS 10 适配 系列_0_ iOS10 UserNotifications 框架解析,希望本文对大家学IOS有所帮助。

    本文将带你了解IOS开发入门IOS 10 适配 系列_0_ iOS10 UserNotifications 框架解析,希望本文对大家学IOS有所帮助。


        

iOS 10 中以前杂乱的和通知相关的 API 都被统一了,现在开发者可以使用独立的 UserNotifications.framework 来集中管理和使用 iOS 系统中通知的功能。在此基础上,Apple 还增加了撤回单条通知,更新已展示通知,中途修改通知内容,在通知中展示图片视频,自定义通知 UI 等一系列新功能,非常强大。

对于开发者来说,相较于之前版本,iOS 10 提供了一套非常易用通知处理接口,是 SDK 的一次重大重构。而之前的绝大部分通知相关 API 都已经被标为弃用 (deprecated)。

这篇文章将首先回顾一下 Notification 的发展历史和现状,然后通过一些例子来展示 iOS 10 SDK 中相应的使用方式,来说明新 SDK 中通知可以做的事情以及它们的使用方式。

您可以在 WWDC 16 的  Introduction to Notifications 和  Advanced Notifications 这两个 Session 中找到详细信息;另外也不要忘了参照  UserNotifications 的官方文档以及本文的<a href="https://github.com/onevcat/UserNotificationDemo" target="_blank">实例项目 UserNotificationDemo。

Notification 历史和现状

碎片化时间是移动设备用户在使用应用时的一大特点,用户希望随时拿起手机就能查看资讯,处理事务,而通知可以在重要的事件和信息发生时提醒用户。完美的通知展示可以很好地帮助用户使用应用,体现出应用的价值,进而有很大可能将用户带回应用,提高活跃度。正因如此,不论是 Apple 还是第三方开发者们,都很重视通知相关的开发工作,而通知也成为了很多应用的必备功能,开发者们都希望通知能带来更好地体验和更多的用户。

但是理想的丰满并不能弥补现实的骨感。自从在 iOS 3 引入 Push Notification 后,之后几乎每个版本 Apple 都在加强这方面的功能。我们可以回顾一下整个历程和相关的主要 API:

iOS 3 - 引入推送通知UIApplication 的 registerForRemoteNotificationTypes 与 UIApplicationDelegate 的 application(_:didRegisterForRemoteNotificationsWithDeviceToken:),application(_:didReceiveRemoteNotification:)iOS 4 - 引入本地通知scheduleLocalNotification,presentLocalNotificationNow:,application(_:didReceive:)iOS 5 - 加入通知中心页面iOS 6 - 通知中心页面与 iCloud 同步iOS 7 - 后台静默推送application(_:didReceiveRemoteNotification:fetchCompletionHandle:)iOS 8 - 重新设计 notification 权限请求,Actionable 通知registerUserNotificationSettings(_:),UIUserNotificationAction 与 UIUserNotificationCategory,application(_:handleActionWithIdentifier:forRemoteNotification:completionHandler:) 等iOS 9 - Text Input action,基于 HTTP/2 的推送请求UIUserNotificationActionBehavior,全新的 Provider API 等 

有点晕,不是么?一个开发者很难在不借助于文档的帮助下区分 application(_:didReceiveRemoteNotification:) 和 application(_:didReceiveRemoteNotification:fetchCompletionHandle:),新入行的开发者也不可能明白 registerForRemoteNotificationTypes 和 registerUserNotificationSettings(_:) 之间是不是有什么关系,Remote 和 Local Notification 除了在初始化方式之外那些细微的区别也让人抓狂,而很多 API 都被随意地放在了 UIApplication 或者 UIApplicationDelegate 中。除此之外,应用已经在前台时,远程推送是无法直接显示的,要先捕获到远程来的通知,然后再发起一个本地通知才能完成现实。更让人郁闷的是,应用在运行时和非运行时捕获通知的路径还不一致。虽然这些种种问题都是由一定历史原因造成的,但不可否认,正是混乱的组织方式和之前版本的考虑不周,使得 iOS 通知方面的开发一直称不上“让人愉悦”,甚至有不少“坏代码”的味道。

另一方面,现在的通知功能相对还是简单,我们能做的只是本地或者远程发起通知,然后显示给用户。虽然 iOS 8 和 9 中添加了按钮和文本来进行交互,但是已发出的通知不能更新,通知的内容也只是在发起时唯一确定,而这些内容也只能是简单的文本。 想要在现有基础上扩展通知的功能,势必会让原本就盘根错节的 API 更加难以理解。

在 iOS 10 中新加入 UserNotifications 框架,可以说是 iOS SDK 发展到现在的最大规模的一次重构。新版本里通知的相关功能被提取到了单独的框架,通知也不再区分类型,而有了更统一的行为。我们接下来就将由浅入深地解析这个重构后的框架的使用方式。

UserNotifications 框架解析

基本流程

iOS 10 中通知相关的操作遵循下面的流程:

首先你需要向用户请求推送权限,然后发送通知。对于发送出的通知,如果你的应用位于后台或者没有运行的话,系统将通过用户允许的方式 (弹窗,横幅,或者是在通知中心) 进行显示。如果你的应用已经位于前台正在运行,你可以自行决定要不要显示这个通知。最后,如果你希望用户点击通知能有打开应用以外的额外功能的话,你也需要进行处理。

权限申请

通用权限

iOS 8 之前,本地推送 (UILocalNotification) 和远程推送 (Remote Notification) 是区分对待的,应用只需要在进行远程推送时获取用户同意。iOS 8 对这一行为进行了规范,因为无论是本地推送还是远程推送,其实在用户看来表现是一致的,都是打断用户的行为。因此从 iOS 8 开始,这两种通知都需要申请权限。iOS 10 里进一步消除了本地通知和推送通知的区别。向用户申请通知权限非常简单:


   

UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {

    granted, error in

    if granted {

        // 用户允许进行通知

    }

}

   

当然,在使用 UN 开头的 API 的时候,不要忘记导入 UserNotifications 框架:


   

import UserNotifications

   

第一次调用这个方法时,会弹出一个系统弹窗。

要注意的是,一旦用户拒绝了这个请求,再次调用该方法也不会再进行弹窗,想要应用有机会接收到通知的话,用户必须自行前往系统的设置中为你的应用打开通知,而这往往是不可能的。因此,在合适的时候弹出请求窗,在请求权限前预先进行说明,而不是直接粗暴地在启动的时候就进行弹窗,会是更明智的选择。

远程推送

一旦用户同意后,你就可以在应用中发送本地通知了。不过如果你通过服务器发送远程通知的话,还需要多一个获取用户 token 的操作。你的服务器可以使用这个 token 将用向 Apple Push Notification 的服务器提交请求,然后 APNs 通过 token 识别设备和应用,将通知推给用户。

提交 token 请求和获得 token 的回调是现在“唯二”不在新框架中的 API。我们使用 UIApplication 的  registerForRemoteNotifications 来注册远程通知,在 AppDelegate 的  application(_:didRegisterForRemoteNotificationsWithDeviceToken) 中获取用户 token:



   

// 向 APNs 请求 token:

UIApplication.shared.registerForRemoteNotifications()

 

// AppDelegate.swift

 func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {

    let tokenString = deviceToken.hexString

    print("Get Push token: \(tokenString)")

}

   

获取得到的 deviceToken 是一个 Data 类型,为了方便使用和传递,我们一般会选择将它转换为一个字符串。Swift 3 中可以使用下面的 Data 扩展来构造出适合传递给 Apple 的字符串:


   

extension Data {

    var hexString: String {

        return withUnsafeBytes {(bytes: UnsafePointer<uint8>) -> String in

            let buffer = UnsafeBufferPointer(start: bytes, count: count)

            return buffer.map {String(format: "%02hhx", $0)}.reduce("", { $0 + $1 })

        }

    }

}

</uint8>

   

权限设置

用户可以在系统设置中修改你的应用的通知权限,除了打开和关闭全部通知权限外,用户也可以限制你的应用只能进行某种形式的通知显示,比如只允许横幅而不允许弹窗及通知中心显示等。一般来说你不应该对用户的选择进行干涉,但是如果你的应用确实需要某种特定场景的推送的话,你可以对当前用户进行的设置进行检查:


   

UNUserNotificationCenter.current().getNotificationSettings {

    settings in 

    print(settings.authorizationStatus) // .authorized | .denied | .notDetermined

    print(settings.badgeSetting) // .enabled | .disabled | .notSupported

    // etc...

}

   

 

关于权限方面的使用,可以参考 Demo 中  AuthorizationViewController 的内容。

发送通知

UserNotifications 中对通知进行了统一。我们通过通知的内容 (UNNotificationContent),发送的时机 (UNNotificationTrigger) 以及一个发送通知的 String 类型的标识符,来生成一个 UNNotificationRequest 类型的发送请求。最后,我们将这个请求添加到 UNUserNotificationCenter.current() 中,就可以等待通知到达了:


   

// 1. 创建通知内容

let content = UNMutableNotificationContent()

content.title = "Time Interval Notification"

content.body = "My first notification"

 

// 2. 创建发送触发

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

 

// 3. 发送请求标识符

let requestIdentifier = "com.onevcat.usernotification.myFirstNotification"

 

// 4. 创建一个发送请求

let request = UNNotificationRequest(identifier: requestIdentifier, content: content, trigger: trigger)

 

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

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

    if error == nil {

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

    }

}

   

iOS 10 中通知不仅支持简单的一行文字,你还可以添加 title 和 subtitle,来用粗体字的形式强调通知的目的。对于远程推送,iOS 10 之前一般只含有消息的推送 payload 是这样的:


   

{

  "aps":{

    "alert":"Test",

    "sound":"default",

    "badge":1

  }

}

   

如果我们想要加入 title 和 subtitle 的话,则需要将 alert 从字符串换为字典,新的 payload 是:


   

{

  "aps":{

    "alert":{

      "title":"I am title",

      "subtitle":"I am subtitle",

      "body":"I am body"

    },

    "sound":"default",

    "badge":1

  }

}

   

好消息是,后一种字典的方法其实在 iOS 8.2 的时候就已经存在了。虽然当时 title 只是用在 Apple Watch 上的,但是设置好 body 的话在 iOS 上还是可以显示的,所以针对 iOS 10 添加标题时是可以保证前向兼容的。

另外,如果要进行本地化对应,在设置这些内容文本时,本地可以使用 String.localizedUserNotificationString(forKey: "your_key", arguments: []) 的方式来从 Localizable.strings 文件中取出本地化字符串,而远程推送的话,也可以在 payload 的 alert 中使用 loc-key 或者 title-loc-key 来进行指定。关于 payload 中的 key,可以参考这篇文档。

触发器是只对本地通知而言的,远程推送的通知的话默认会在收到后立即显示。现在 UserNotifications 框架中提供了三种触发器,分别是:在一定时间后触发 UNTimeIntervalNotificationTrigger,在某月某日某时触发 UNCalendarNotificationTrigger 以及在用户进入或是离开某个区域时触发 UNLocationNotificationTrigger。

请求标识符可以用来区分不同的通知请求,在将一个通知请求提交后,通过特定 API 我们能够使用这个标识符来取消或者更新这个通知。我们将在稍后再提到具体用法。

在新版本的通知框架中,Apple 借用了一部分网络请求的概念。我们组织并发送一个通知请求,然后将这个请求提交给 UNUserNotificationCenter 进行处理。我们会在 delegaet 中接收到这个通知请求对应的 response,另外我们也有机会在应用的 extension 中对 request 进行处理。我们在接下来的章节会看到更多这方面的内容。

在提交通知请求后,我们锁屏或者将应用切到后台,并等待设定的时间后,就能看到我们的通知出现在通知中心或者屏幕横幅了:

 

关于最基础的通知发送,可以参考 Demo 中  TimeIntervalViewController 的内容。

取消和更新

在创建通知请求时,我们已经指定了标识符。这个标识符可以用来管理通知。在 iOS 10 之前,我们很难取消掉某一个特定的通知,也不能主动移除或者更新已经展示的通知。想象一下你需要推送用户账户内的余额变化情况,多次的余额增减或者变化很容易让用户十分困惑 - 到底哪条通知才是最正确的?又或者在推送一场比赛的比分时,频繁的通知必然导致用户通知中心数量爆炸,而大部分中途的比分对于用户来说只是噪音。

iOS 10 中,UserNotifications 框架提供了一系列管理通知的 API,你可以做到:

取消还未展示的通知更新还未展示的通知移除已经展示过的通知更新已经展示过的通知 

其中关键就在于在创建请求时使用同样的标识符。

比如,从通知中心中移除一个展示过的通知:


   

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

let identifier = "com.onevcat.usernotification.notificationWillBeRemovedAfterDisplayed"

let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)

 

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

    if error != nil {

        print("Notification request added: \(identifier)")

    }

}

 

delay(4) {

    print("Notification request removed: \(identifier)")

    UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [identifier])

}

   

类似地,我们可以使用 removePendingNotificationRequests,来取消还未展示的通知请求。对于更新通知,不论是否已经展示,都和一开始添加请求时一样,再次将请求提交给 UNUserNotificationCenter 即可:


   

// let request: UNNotificationRequest = ...

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

    if error != nil {

        print("Notification request added: \(identifier)")

    }

}

 

delay(2) {

    let newTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)

 

    // Add new request with the same identifier to update a notification.

    let newRequest = UNNotificationRequest(identifier: identifier, content:newContent, trigger: newTrigger)

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

        if error != nil {

            print("Notification request updated: \(identifier)")

        }

    }

}

   

远程推送可以进行通知的更新,在使用 Provider API 向 APNs 提交请求时,在 HTTP/2 的 header 中 apns-collapse-id key 的内容将被作为该推送的标识符进行使用。多次推送同一标识符的通知即可进行更新。

 

对应本地的 removeDeliveredNotifications,现在还不能通过类似的方式,向 APNs 发送一个包含 collapse id 的 DELETE 请求来删除已经展示的推送,APNs 服务器并不接受一个 DELETE 请求。不过从技术上来说 Apple 方面应该不存在什么问题,我们可以拭目以待。现在如果想要消除一个远程推送,可以选择使用后台静默推送的方式来从本地发起一个删除通知的调用。关于后台推送的部分,可以参考我之前的一篇关于  iOS7 中的多任务的文章。

 

关于通知管理,可以参考 Demo 中  ManagementViewController 的内容。为了能够简单地测试远程推送,一般我们都会用一些方便发送通知的工具,Knuff 就是其中之一。我也为 Knuff 添加了 apns-collapse-id 的支持,你可以在这个  fork 的 repo 或者是原 repo 的  pull request 中找到相关信息。

处理通知

应用内展示通知

现在系统可以在应用处于后台或者退出的时候向用户展示通知了。不过,当应用处于前台时,收到的通知是无法进行展示的。如果我们希望在应用内也能显示通知的话,需要额外的工作。

UNUserNotificationCenterDelegate 提供了两个方法,分别对应如何在应用内展示通知,和收到通知响应时要如何处理的工作。我们可以实现这个接口中的对应方法来在应用内展示通知:

  

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之IOS频道!


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程