Save 37% off PRO during our Black Friday Sale! »

告白推播的 Push Notification

告白推播的 Push Notification

介紹 Apple Push Notification,實作基本的 App 推播功能,利用 Node.js 發送推播,iOS 11 的隱藏推播訊息(hidden notification content),利用 silent notification 更新資料,利用 Notification Service Extension 下載修改推播內容

Transcript

  1. 告白推播的 Push Notification Peter Pan

  2. 彼得潘簡介 App程式設計入⾨門:iPhone.iPad 彼得潘的 Swift 程式設計入⾨門 正職: 作家 副業: 專欄欄作家,⼯工程師,講師,顧問,家教,App評審, App接案,企業包班,創業家,iOS

    APP ⾦金金牌擺渡⼈人 iOS App⼯工程師/外包廠商的⾯面試鑑賞師
  3. ⽣生動有趣的App教學 說真⼼心話的電腦書 ⾒見見書如⾒見見⼈人,看了了書就可以了了解彼得潘 博客來來單⽉月Top 1, 2012年年度 百⼤大電腦類 Top 6 歡迎寄照片給我,我可以把你們的照片放到我的新書

  4. 相關教學資源 • FB粉絲團: 愛瘋⼀一切為蘋果的彼得潘
 http://www.facebook.com/iphone.peterpan • 個⼈人網站
 http://apppeterpan.strikingly.com • medium:

    彼得潘的App Neverland
 https://medium.com/@apppeterpan • GitBook: 彼得潘的iOS App開發便便利利貼
 https://www.gitbook.com/book/apppeterpan/iosappcodestickies/ • FB社團: 彼得潘的蘋果App開發教室
 https://www.facebook.com/groups/peterpanappclass/
  5. 相關教學資源 • iOS App 開發電⼦子報
 http://strikingly.us12.list-manage.com/subscribe?u=afa877b4a57124636b33826b0&id=4dd6b92dbb • SlideShare
 http://www.slideshare.net/deeplovepan •

    email: apppeterpan@gmail.com • FB: https://www.facebook.com/deeplove.pan • LINE: deeplovepeterpan
  6. ⼤大綱 • 推播原理理 • 搭配 APNs Key 實作推播功能 • Hidden

    Notification Content • Silent Notification • Notification Service Extension
  7. None
  8. local notification & remote notification • local notification • 和時間或位置有關,需要事先設定

    • 直接由本機端觸發 • 下午六點提醒我們該下班的推播,或是靠近 101 Apple Store 出現 的 iPhone X 廣告推播 • remote notification • 透過網路路發送傳到⼿手機 • 比較彈性,任何時候你⼼心⾎血來來潮,都可以發送新的推播訊息
  9. remote notification 的傳送對象 • 某個⼈人 • 全部的⼈人 • 滿⾜足某條件的⼈人,比⽅方最棒星座的⼈人

  10. remote notification 的限制 • 付費成為開發會員才能發送推播 • 推播要透過 Apple 的 server

    發送 • 不能在模擬器測試 • 有可能沒收到推播訊息 • Apple 的 server 會保留留傳送失敗的推播,之後再重 送,但它只會保留留⼀一段時間,過期的推播將被清掉。
  11. 類似郵差寄信的推播傳信送程 • provider server: ⾃自⼰己的後台 • APNs(Apple Push Notification Service)

    server: Apple 的 server • 寄信時我們透過地址指定接收的對象,APNs 利利⽤用 device token 找到要接 受推播的對象,某某⼿手機上的某某 App
  12. 開始實作告⽩白 App

  13. 讓 App 具備接收推播的能⼒力力

  14. 設定 Team 付費帳號

  15. 打開 Capabilities 的 Push Notifications • 以 Xcode 裡設定的 Bundle

    ID,在 Apple 的開發網 站建立 App ID。 • 產⽣生推播需要的 entitlements 檔
  16. 新增的 Apple ID

  17. 推播需要的 entitlements 檔

  18. 取得推播權限 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) ->

    Bool { // Override point for customization after application launch. UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in } return true } import UserNotifications
  19. 取得推播權限 open func requestAuthorization(options: UNAuthorizationOptions = [], completionHandler: @escaping (Bool,

    Error?) -> Swift.Void) public struct UNAuthorizationOptions : OptionSet { public init(rawValue: UInt) public static var badge: UNAuthorizationOptions { get } public static var sound: UNAuthorizationOptions { get } public static var alert: UNAuthorizationOptions { get } public static var carPlay: UNAuthorizationOptions { get } } badge: App Icon 上顯⽰示的數字
  20. None
  21. 請求推訊權限的訊息 ⼀一旦使⽤用者做了了選擇,不管允許還是不允許,之後通 知請求的訊息都不會再出現,除非她將 App 移除重新 安裝。 若若是她想改變⼼心意,比⽅方原本允許,後來來發現你是邊 緣⼈人,想要封鎖你,也是可以的。只是不能從原來來的 App 做改變,⽽而要從設定

    App 的通知⾴頁⾯面變更更。
  22. 跟 Apple 註冊,請求接收推播 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey:

    Any]?) -> Bool { // Override point for customization after application launch. UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in } UIApplication.shared.registerForRemoteNotifications() return true } 成功的話可以得到 token,觸發呼叫 protocol UIApplicationDelegate 的 function application(_:didRegisterForRemoteNotificationsWithDeviceToken:) func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { } 在 AppDelegate 裡定義
  23. device token App 得到後會傳給⾃自⼰己的後台,到時候 provider server 發送推播時才能指定推播的對象 https://medium.com/彼得潘的-swift-ios-app-開發問題解答集/將-push-notification-data-型別的-device-token-變成-string-a89e08736bdf

  24. device token • 對很多 App 來來說,token 會對應到某個使⽤用者。 • token 對應到某個

    iOS 裝置的某個 App • iOS 裝置上不同的 App,會得到不同的 token。 • 當使⽤用者不同意接收通知時,App 無法取得 token。 • token 可能會變,記得更更新。 • 在每次 App 啟動時呼叫 registerForRemoteNotifications
  25. 請求接收推播失敗時觸發的 function func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {

    }
  26. 現在 App 已經具備接 收推播的能⼒力力

  27. 發送推播的設定

  28. 產⽣生 APNs Key

  29. 點選 Certificates, Identifiers & Profiles 從 provider server 發送推播,需要 APNs

    key
  30. 點選 Keys 下的 All 後,點選右上 ⾓角的 + 新增 key

  31. 輸入 key 的名字,勾選 APNs 後,點擊 Continue 按鈕 •One key is

    used for all of yours apps,App 可以共⽤用 同⼀一個 key • 不會過期
  32. 點選 Confirm 按鈕 完成 key 的新增

  33. 點選 Download 下載 key 只有⼀一次下載的機會 如果弄弄丟只能重新產⽣生新 key After downloading your

    key, it cannot be re-downloaded as the server copy is removed. .p8 檔
  34. APNs Key

  35. 上限兩兩個

  36. Delete APNs Key Revoke delete 後原本的 key 不能再送推播

  37. 利利⽤用 Node.js 程式當 provider server 發送推播

  38. provider server 程式 • ⾃自⼰己的後台程式,搭配套件 • php,ruby on rails,Node.js •

    第三⽅方的後台 • Firebase
  39. 確認是否有安裝 Node.js command not found: 尚未安裝

  40. 安裝 Node.js https://nodejs.org/en/download/ 點選 macOS Installer 下載 Node.js 的 pkg

  41. 安裝 Node.js

  42. 安裝成功,可以執⾏行行 node 指令

  43. 建立傳送推播的 Node.js 程式資料夾 apns mkdir apns cd apns

  44. 安裝發送推播的套件,apn npm init --yes npm install apn --save https://github.com/node-apn/node-apn

  45. 將 key(p8 檔)放到 apns 資料夾下

  46. 在 apns 資料夾下撰寫發送推播的 js 檔,設定 key, keyId, teamId var apn

    = require('apn'); var options = { token: { key: "AuthKey_9943HQBBPS.p8", keyId: "9943HQBBPS", teamId: "G4HL98LX6L" }, production: false }; var apnProvider = new apn.Provider(options); push.js
  47. Key Id

  48. team Id 在 Apple 開發網⾴頁的 Membership ⾴頁⾯面,可找到 Team ID。

  49. APNs server 有兩兩種,development(sandbox) 和 distribution(production) • development • Xcode 安裝

    • api.development.push.apple.com:443
 • distribution • App Store, TestFlight , AdHoc • api.push.apple.com:443 production: false
  50. entitlements Archive 製作 App Store, TestFlight , AdHoc 版本時, 會⾃自動被轉換成

    production
  51. Archive 製作 App Store, TestFlight , AdHoc 版本時,會⾃自動產⽣生 distribution profile

  52. 設定 device token 和 推播的內容後發送 var note = new apn.Notification();

    let deviceToken = "d75e441c494714ec89f57b58bf685bf4166032214715eef86c790536ac60c63e" note.alert = "Wendy, I love you";
 note.sound = "default"; note.badge = 1; note.topic = "com.pegterpan.LoveConfession"; apnProvider.send(note, deviceToken).then( (result) => { // see documentation for an explanation of result }); • topic: 設成 App 的 bundle ID • alert: 推播的⽂文字內容。 • sound: 推播的聲⾳音檔名。 • default 表⽰示預設的推播聲。 • 沒有指定 sound 的話,將變成無聲推播。 • ⾃自訂的聲⾳音檔不能超過 30 秒 • badge: App Icon 上顯⽰示的數字。
  53. 在 terminal 輸入 node push.js 發送推播

  54. 推播內容的真⾯面⽬目 JSON Dictionary { "aps" : { "alert" : "Wendy,

    I love you", "badge" : 1, "sound" : "default" } "link": "http://apppeterpan.strikingly.com" }
  55. 推播內容的真⾯面⽬目 JSON Dictionary • aps 的 key • https://apple.co/2eTJu3U •

    可加入⾃自訂的欄欄位,放在跟 aps 同⼀一層 • 有 size 限制,⼀一般的推播是 4 KB,VoIP 的推播是 5 KB
  56. 加入⾃自訂的 key { "aps" : { "alert" : "Wendy, I

    love you", "badge" : 1, "sound" : "default" } "link": "http://apppeterpan.strikingly.com" } 使⽤用者點選推播,打開 App 時,讀取 link 欄欄位顯⽰示網⾴頁
  57. 呈現聲⾳音,圖片和影片的 Rich Notification https://www.appcoda.com.tw/ios10-user-notifications/ http://bit.ly/2sODqSB http://bit.ly/2ovtYOT UNNotificationAttachment 只能指定本機的檔案

  58. 聲⾳音,圖片和影片如何包含在推播 的 JSON 裡 ? • 4 KB 的限制 •

    利利⽤用待會介紹的 Notification Service Extension
  59. 點選推播和收到播推觸發的 function 觸發的 function 來來 ⾃自 protocol UNUserNotificationCenterDelegate UNUserNotificationCenter.current().delegate =

    self 將 AppDelegate 物件設為 UNUserNotificationCenter 的代理理⼈人。
  60. 當使⽤用者點選推播打開 App 時 func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse,

    withCompletionHandler completionHandler: @escaping () -> Void) { print("didReceive", response.notification.request.content) completionHandler() } App 在背景或是沒被啟動時 要點選推播才會觸發
  61. 在前景收到推播 觸發的 function func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification,

    withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { print("willPresent", notification.request.content) completionHandler([]) } completionHandler([]) 不顯⽰示推播
  62. 在前景收到推播並顯⽰示推播 func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler:

    @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.alert]) } 如果點擊推播,userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) 也會呼叫
  63. 以上內容到時會整理理成 ⽂文章,發表在 AppCoda

  64. hidden notification content iOS 11

  65. hidden notification content

  66. hidden notification content

  67. 來來⾃自⼩小三的訊息,不能被看到

  68. 客製 hidden 訊息 let category = UNNotificationCategory(identifier: "love", actions: [],

    intentIdentifiers: [], hiddenPreviewsBodyPlaceholder: "不給你看", options: []) UNUserNotificationCenter.current().setNotificationCategories([category])
  69. 設定推播的 category note.category = "love" aps 的 key

  70. None
  71. actions 讓推播出現客製的 button https://www.appcoda.com.tw/ios10-user-notifications/ let likeAction = UNNotificationAction(identifier: "like", title:

    "好感動", options: [.foreground]) let dislikeAction = UNNotificationAction(identifier: "dislike", title: "沒感覺", options: []) let category = UNNotificationCategory(identifier: "luckyMessage", actions: [likeAction, dislikeAction], intentIdentifiers: [], options: []) UNUserNotificationCenter.current().setNotificationCategories([category])
  72. silent notification

  73. silent notification • 實現使⽤用者看不⾒見見的背景更更新推播 • App 在背景收到通知後,執⾏行行某項⼯工作,
 比⽅方連到後台抓取新資料 (不需要點推播即可執⾏行行程式)

  74. content-available aps 的 content-available 設為 1 note.contentAvailable = 1 •

    ⼀一般不會設定 alert 欄欄位,不然還是會顯⽰示推播 • 如果設定 alert 欄欄位,推播還是會顯⽰示,但不⽤用 點推播即可執⾏行行程式。
  75. 勾選 Background Modes 的 Remote Notification

  76. application(_:didReceiveRemoteNo tification:fetchCompletionHandler:) 當 App 在前景或在背景,收到推播時呼叫。 func application(_ application: UIApplication, didReceiveRemoteNotification

    userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { print("didReceiveRemoteNotification \(userInfo)") completionHandler(.newData) }
  77. application(_:didReceiveRemoteNo tification:fetchCompletionHandler:) • 不⽤用點推播即可執⾏行行程式 •最多做 30 秒 • 如果 App

    曾被啟動過,但現在沒有啟動,收到 silent notification 時,可以被啟動在背景做事。但如果使⽤用者強 制把 App 殺掉,它將不會被啟動。 • 收到 silent notification 時,可以利利⽤用 local notification 顯 ⽰示推播 The ability of APNs to deliver remote notifications to a nonrunning app requires the app to have been launched at least once. On an iOS device, if a user force-quits your app using the app multitasking UI, the app does not receive remote notifications until the user relaunches it.
  78. 顯⽰示推播前, 修改推播的內容

  79. Notification Service Extension • 在顯⽰示推播前,修改推播的內容 • 下載推播的圖片或影片後,顯⽰示在推播上 • 解密被加密的推播訊息 •

    修改顯⽰示的⽂文字 • Service extension 不能執⾏行行太久 • 下載的東⻄西不能太⼤大,不然會花太多時間
  80. mutable-content note.mutableContent = 1 note.payload = {"imageUrl": "https://images-na.ssl-images-amazon.com/images/I/ 51f-7KjjFeL._SX317_BO1,204,203,200_.jpg" }

    例例 1 例例 2
  81. Notification Service Extension

  82. None
  83. None
  84. class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var

    bestAttemptContent: UNMutableNotificationContent? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent { // Modify the notification content here... bestAttemptContent.title = "\(bestAttemptContent.title) [modified]" contentHandler(bestAttemptContent) } } override func serviceExtensionTimeWillExpire() { // Called just before the extension will be terminated by the system. // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent { contentHandler(bestAttemptContent) } } }
  85. 下載推播圖片 override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent)

    -> Void) { self.contentHandler = contentHandler bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestAttemptContent = bestAttemptContent, let urlString = bestAttemptContent.userInfo["imageUrl"] as? String { download(url: urlString, completion: { (fileUrl) in if let fileUrl = fileUrl, let attachment = try? UNNotificationAttachment(identifier: "image", url: fileUrl, options: nil) { bestAttemptContent.attachments = [attachment] contentHandler(bestAttemptContent) } }) } }
  86. 下載推播圖片 func download(url: String, completion: @escaping (URL?) -> ()) {

    if let url = URL(string: url) { let task = URLSession.shared.downloadTask(with: url, completionHandler: { (fileUrl, response, error) in if let fileUrl = fileUrl { var newUrl = fileUrl newUrl.deletePathExtension() newUrl.appendPathExtension(url.pathExtension) try? FileManager.default.moveItem(at: fileUrl, to: newUrl) completion(newUrl) } }) task.resume() } } fileUrl "file:///private/var/mobile/Containers/Data/PluginKitPlugin/794AC351-A3D3-4A05-9940-E973D2C9B9D4/tmp/ CFNetworkDownload_G7kg66.tmp"
  87. 附檔名 tmp 無法⽣生成 UNNotificationAttachment, 所以要重新命名,變成附檔名 jpg var newUrl = fileUrl

    newUrl.deletePathExtension() newUrl.appendPathExtension(url.pathExtension) try? FileManager.default.moveItem(at: fileUrl, to: newUrl) fileUrl "file:///private/var/mobile/Containers/Data/PluginKitPlugin/794AC351-A3D3-4A05-9940-E973D2C9B9D4/tmp/ CFNetworkDownload_G7kg66.tmp" newUrl "file:///private/var/mobile/Containers/Data/PluginKitPlugin/794AC351-A3D3-4A05-9940-E973D2C9B9D4/tmp/ CFNetworkDownload_G7kg66.jpg"
  88. debug Notification Service Extension 1. 設 Notification Service Extension 的中斷點後啟動

    App 2. 發送推播
  89. debug Notification Service Extension 3. Debug > Attach to Process

    > Notification Service Extension 的名字
  90. debug Notification Service Extension 4. 發送推播

  91. 改變通知的 UI Notification Content Extension