语言: swift, 版本:4.2,XCode:10.1
写作时间:2018-12-14
Push的作用是提醒用户,你要用俺开发的APP啦,提升日活和使用频率的神器。无论APP是在后台,还是进程已经被结束了,只要用户手机联网即可。比如你要少用微信,关掉微信的推送试试(当然卸载更绝)。
Push的工作流程如下:
Provider Server是发送Payload内容 (其实是Json,包含device、APP、内容信息)到APNs (Apple负责推送消息的服务器),只要你的iPhone在线,那么就一直跟APNs保持长连接。所以一有消息过来,iPhone就会显示推送消息。
带着下面的问题往下看:
iOS 12,Push notifications可以做以下事情:
完成教程,需要下面的条件方能测试:
Provider发送,APP接收push notifications, 你只要完成下面三件事:
证书配置是为了保证用户下载的是正版的APP,Push证书是为了保证Provider是对应自己APP的才有权限推送。
为了便于理解,从零开始演示配置的信息,网页打开开发者首页https://developer.apple.com,–》Account, --》 Certificates, Identifiers & Profiles
推送不能用通配符,点击最下面的Continue按钮,下一页点击最下面Register按钮,下一页点击最下面的Done按钮。
点击Certificates, IDs & Profiles -> Identifiers -> App IDs 选择刚刚建立的id --> com.zgpeace.demo
,
你可以看到服务列表Application Services available, 其中 Push Notifications显示的是可配置的configurable:
这里创建Development SSL Certificate --》 点击Create Certificate. (Production SSL Certificate的创建是类似的). --》 打勾 Apple Push Notification service SSL(Sandbox) --> 点击Continue
选择创建的App ID com.zgpeace.demo
, 点击Continue。这里会明确说明,不能用通配符的App ID才能创建Apple Push Notification service SSL certificate.
创建Certificate Signing Request(CSR). 这个界面是显示如何创建CSR的步骤。下面会一步一步创建CSR,这个时候要开小差了,不着急点Continue哦。(下面以8.?表示分步骤,待到9.才是点Continue下来的。)
8.1. 打开Keychain Access软件,路径为 Application folder --> Utilities folder --> 打开 Keychain Access. 或者用快键键CMD + Space打开Spotlight,输入Keychain Access。
8.2. 点击Keychain左上角的下拉按钮, 选择 Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority.
注意:这里有个尿点,很多人采坑,注意Category 一定要选择Certificates,否则出各种诡异的错误。
8.3. 在Certificate Information窗口, 填写下面的信息:
恭喜你!这个步骤很长,但是值得。查看App ID的Development证书状态,已经变为Enabled,路径为 Certificates, IDs & Profiles -> Identifiers -> App IDs Push Notifications :
上面生成的证书是给Provider Server用的。 因为是新创建的App ID, project需要运行的话,需要先建立客户端的Development证书(CSR可以用之前的),接在在设备里面加入iPhone的DeviceId,证书跟DeviceId一起创建Profile。
证书实际上是包含公钥跟私钥,加密以及身份验证用的;Device Id表明哪些设备可以用于调试; Profile表示档案,最终打包上传App Store,Apple根据这些信息校验App是否是正版。
因为已经跑题,罗列了一下结果图。
Development Certificate 创建结果图, 下载并安装证书 :
Device Id添加结果图:
Profile生成后,点击下载,双机安装,就装到XCode里面去了。
下载demo工程https://github.com/zgpeace/WenderCast-Starter,运行结果图
修改Bundle Identifier为新建com.zgpeace.demo
, WenderCast target > General > change Bundle Identifier
开启Push Notification, WenderCast target > the Capabilities tab > Push Notifications 选择ON:
iPhone的体验很好,做任何事情都要经过用户同意才能处理。
推送也一样,第一步先征得用户是否需要推送这个功能。
import UserNotifications
AppDelegate
的最下面:func registerForPushNotifications() {
UNUserNotificationCenter.current() // 1
.requestAuthorization(options: [.alert, .sound, .badge]) { // 2
granted, error in
print("Permission granted: \(granted)") // 3
}
}
分析上面代码:
注解: options 参数 requestAuthorization(options:completionHandler:) 可以是下面的任何组合 UNAuthorizationOptions:
.badge: 显示推送书在 app’s icon.
.sound: 播放声音.
.alert: 显示文字.
.carPlay: 显示推送在车载系统.
.provisional: 发布不会被拒绝的推送. 比如静默推送.
.providesAppNotificationSettings: 表示App有自己的推送设置UI.
.criticalAlert: 忽略静音,并且不会被打断。你需要向Apple申请者特殊的权利, 并说明这是必要的. .
return
之前加入以下代码:registerForPushNotifications()
构建 > 运行。当APP运行起来后,弹框问用户是否允许发推送。
AppDelegate
:func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
}
}
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) {
[weak self] granted, error in
print("Permission granted: \(granted)")
guard granted else { return }
self?.getNotificationSettings()
}
用户授权成功后,接下来获取Push的token,需要发送给Provider Server,存入数据库。
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
}
func application(
_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
token例子:
取个名字, 比如 Push Notification Key. 在 Key Services 下面, 勾选 Apple Push Notifications service (APNs).
点击 Continue ,接着 Confirm 在下个页面,点击下载. 文件名字类似于 AuthKey_4SVKWF966R.p8. 保存好该文件,你需要用它来发推送! 4SVKWF966R 文件名字的一部分是Key ID. 你也需要它.
你最后需要的是your Team ID. 点击链接跳转到Membership Details 页面,你就会找到.
唷!配置的道路好长,接下来就可以发送推送信息了。
点击链接下载 PushNotifications .
打开 PushNotifications,并配置下面的信息:
{
"aps": {
"alert": "Breaking News!",
"sound": "default",
"link_url": "https://raywenderlich.com"
}
}
payload就是JSON,必须包含的key是 aps
, 它也是个字典 dictionary.
apt
预置了7对keys,以下是具体说明:
alert
: 可以试字符串string, 也可以是字典dictionary. 作为 dictionary, 它可以国际化文字或者改变通知的样子,类似于CSS(猜的,不确定对不对).badge
: 显示推送书在icon的右上角. 去掉数字显示,设置为0即可.sound
: 声音预置在app里面. 定制的声音要小于30秒,还有一些限制(细节要看官方文档了).thread-id
: 对推送消息分组.category
: 给推送消息分类, 用于定制化相应推送. 接下来有栗子?.content-available
: 设置这项为1, 推送就是静默推送. 在下面你将学到静默推送.mutable-content
: 设置这项为1, app可以先显示之前修改内容.除了上面预置的keys, 你可以加其它字段,只要payload小于 4,096 bytes.
处理推送通知都在UIApplicationDelegate
类的delegate里面,根据APP所处的状态分为两类:
application(_:didFinishLaunchingWithOptions:)
.application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
. 如果用户点击推送消息,iOS会再次调用该方法, 你可以更新UI和显示相关信息.WenderCast
会建立新的消息子项, 并打开News tab. 在方法application(_:didFinishLaunchingWithOptions:)
的末尾在 return
之前加上一下代码:// Check if launched from notification
let notificationOption = launchOptions?[.remoteNotification]
// 1
if let notification = notificationOption as? [String: AnyObject],
let aps = notification["aps"] as? [String: AnyObject] {
// 2
NewsItem.makeNewsItem(aps)
// 3
(window?.rootViewController as? UITabBarController)?.selectedIndex = 1
}
上面代码说明:
UIApplication.LaunchOptionsKey.remoteNotification
是否存在. 如果存在,则说明APP是点击推送唤醒的. 这就是payload的内容.aps
dictionary,并创建NewsItem.点击WenderCast
scheme并选择编辑 Edit Scheme
…. 左侧栏选择Run
, 接着在Info tab
选择 Wait for executable to be launched
:
这个设置使调试在等待,第一次点击推送消息才唤醒APP.
构建并运行. 当APP安装好后, 发送多个推送. 点击推送,APP就好打开news tab:
AppDelegate
增加下面的方法:func application(
_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler:
@escaping (UIBackgroundFetchResult) -> Void
) {
guard let aps = userInfo["aps"] as? [String: AnyObject] else {
completionHandler(.failed)
return
}
NewsItem.makeNewsItem(aps)
}
这个方法通过push message创建了一个新NewsItem.
在进程存在的情况下,这个方法会调用。修改 scheme
回到 launching > automatically. 路径为:WenderCast
scheme > Edit Scheme
> Run
> Info tab
> automatically
.
构建Build并运行run. app运行z在前台并停留在News tab. 发送推送通知可以看到消息item在增加:
That’s it! 你的app现在可以神奇地接收推送消息了.
Actionable notifications 可以加定制化按钮在推送消息里面,比如可以看到email APP显示回复按钮,Tweets显示点赞按钮.
在方法registerForPushNotifications()
里面, 在guard
下面,getNotificationSettings()
的上面加入以下方法:
// 1
let viewAction = UNNotificationAction(
identifier: viewActionIdentifier, title: "View",
options: [.foreground])
// 2
let newsCategory = UNNotificationCategory(
identifier: newsCategoryIdentifier, actions: [viewAction],
intentIdentifiers: [], options: [])
// 3
UNUserNotificationCenter.current().setNotificationCategories([newsCategory])
代码说明:
newsCategory
" 在payload里面, 区分不同的category.setNotificationCategories
, 注册 actionable notification.That’s it! 构建并运行 app去注册新的 notification settings.
app推到后台,接着用PushNotifications
发送下面的 payload
:
{
"aps": {
"alert": "Breaking News!",
"sound": "default",
"link_url": "https://raywenderlich.com",
"category": "NEWS_CATEGORY"
}
}
如果一切顺利,下拉推送通知,你可以看到下面的结果View action:
Nice! 点击会唤醒WenderCast
, 但是它没有任何响应事件. 为了使其调整的News tab,需要完善delegate.
当触发了notification action
, UNUserNotificationCenter
通知 delegate
. 在文件AppDelegate.swift
的最下面, 增加 class extension:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// 1
let userInfo = response.notification.request.content.userInfo
// 2
if let aps = userInfo["aps"] as? [String: AnyObject],
let newsItem = NewsItem.makeNewsItem(aps) {
(window?.rootViewController as? UITabBarController)?.selectedIndex = 1
// 3
if response.actionIdentifier == viewActionIdentifier,
let url = URL(string: newsItem.link) {
let safari = SFSafariViewController(url: url)
window?.rootViewController?.present(safari, animated: true,
completion: nil)
}
}
// 4
completionHandler()
}
}
custom action
唤醒APP后,callback
做了很熟悉的解析payload
的操作:
aps
dictionary.NewsItem
,并跳转到News tab.action identifier
, 是否存在identifier
. 如果是 “View” action并且链接是有效的, 它会展示显示链接页面在 SFSafariViewController
.completion handler
.最后需要设置delegate为UNUserNotificationCenter
. 在application(_:didFinishLaunchingWithOptions:)
最上面加下面的代码:
UNUserNotificationCenter.current().delegate = self
Build and run. 再次结束app的进程, 接着发送推送用下面的payload:
{
"aps": {
"alert": "New Posts!",
"sound": "default",
"link_url": "https://raywenderlich.com",
"category": "NEWS_CATEGORY"
}
}
下拉 notification并点击 View action,你可以看到WenderCast present 显示Safari View controller
, 当APP启动以后:
Congratulations, 你已经实现了actionable notification!
当数据库有新数据的时候,发个静默推送(Silent Push Notifications), 后台帮用户更新数据就好。 这样的好处是,不用客户端间断性轮询更新数据。
需要在Background Modes开启 Remote notifications, 路径为 WenderCast target > Capabilities tab > Background Modes (打开) > Remote notifications(勾选):
现在,APP可以在后台,获取到静默推送了
在类 AppDelegate
中, 找到 application(_:didReceiveRemoteNotification:)
. 把 NewsItem.makeNewsItem()
替换为下面代码:
// 1
if aps["content-available"] as? Int == 1 {
let podcastStore = PodcastStore.sharedStore
// 2
podcastStore.refreshItems { didLoadNewItems in
// 3
completionHandler(didLoadNewItems ? .newData : .noData)
}
} else {
// 4
NewsItem.makeNewsItem(aps)
completionHandler(.newData)
}
解析代码:
content-available
是否为 1
, 是则表示silent notification
.podcast list
.completion handler
让系统更新数据(没新数据则不处理).silent notification
, 采取是新内容创建新的 news item
.build and run, App保持在前台foreground, PushNotifications
推送下面的payload :
{
"aps": {
"content-available": 1
}
}
静默推送,除非服务端返回新的数据,否则界面看不出变化,可以用调试的方式,看看走的逻辑是否符合预期。
Congratulations! 你已经完成了推送的知识:证书配置、Project设置、Provider模拟推送、App处理推送payload、定制化按钮Actionable notifications、 静默推送!
代码在:https://github.com/zgpeace/WenderCast-Starter
分支说明:master是初始化代码, finish是按照上面的例子完成的。
真机测试,需要修改所有的BundleId, Certificate, Profile。
感谢阅读:如有任何问题,请留言,谢谢!
参考文章:
https://www.raywenderlich.com/8164-push-notifications-tutorial-getting-started
https://medium.com/flawless-app-stories/ios-remote-push-notifications-in-a-nutshell-d05f5ccac252
https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html