【Swift 5.6】RichPushNotification - 有錢人的推播功能?
受人點滴當湧泉以報,這次來介紹推播的功能,其實除了顯示文字之外,在iOS 12之後加上顯示圖片的功能,不過這個功能在Android好像很久以前就有了?而這個功能也是網頁不可取代的必備功能,在此特別感謝Nick大大的簡報,話不多說,就來實作一下吧。
作業環境
項目 | 版本 |
---|---|
CPU | Apple M1 |
macOS | Big Sur 12.4 arm64 |
Xcode | 13.4.1 arm64 |
iOS | 支援iOS 13以上 |
作一個長得像這樣的推播功能
基本推播設定
加入推播功能
-
該功能開啟之後,就會產生一個.entitlements的設定檔,就可以使用APNS的功能了…
-
然後就詢問一下使用者要不要讓APP使用推播 / 註冊的功能,而在application(_:didRegisterForRemoteNotificationsWithDeviceToken:)可以取得APPLE所傳來的Token值,這就不多做說明了…
import UIKit
import WWPrint
@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var pushToken: String?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Utility.shared.userNotificationSetting(delegate: self) {
wwPrint("Granted")
} rejectedHandler: {
wwPrint("Reject")
} result: { (status) in
wwPrint(status.rawValue)
}
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
pushToken = deviceToken._hexString()
}
}
import UIKit
// MARK: - 推播相關 (AppDelegate)
extension Utility {
func userNotificationSetting(delegate: UNUserNotificationCenterDelegate? = nil, grantedHandler: @escaping () -> Void, rejectedHandler: @escaping () -> Void, result: @escaping (UNAuthorizationStatus) -> Void) {
let center = UNUserNotificationCenter.current()
let authorizationOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
center.getNotificationSettings { (settings) in
let authorizationStatus = settings.authorizationStatus
switch (authorizationStatus) {
case .notDetermined:
center.requestAuthorization(options: authorizationOptions) { (isGranted, error) in
guard isGranted else { rejectedHandler(); return }
DispatchQueue.main.async { self.registerForRemoteNotifications() }
grantedHandler()
}
case .authorized:
DispatchQueue.main.async { self.registerForRemoteNotifications() }
center.delegate = delegate ?? self
case .ephemeral:
DispatchQueue.main.async { self.registerForRemoteNotifications() }
center.delegate = delegate ?? self
case .denied: print("denied")
case .provisional: print("provisional")
@unknown default: fatalError()
}
result(authorizationStatus)
}
}
}
推播測試
- 除了可以用實機取得Token測試之外,在Xcode 11.4之後可以直接使用模擬器去做測試,非常的方便,這裡就來測試,取得推送過來的資訊…
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
self.userInfo = notification.request.content.userInfo
completionHandler([.badge, .sound, .alert])
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
self.userInfo = response.notification.request.content.userInfo
completionHandler()
}
}
import UIKit
import WWLog
final class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func test(_ sender: UIButton) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
WWLog.shared.log(appDelegate.userInfo)
}
}
// Simulater.apns
{
"Simulator Target Bundle": "<BundleId>",
"aps": {
"alert": {
"title": "<標題>",
"subtitle": "<副標題>",
"body": "<內文>"
}
}
}
UNNotificationContentExtension
新增一個Notification Content Extension Target
- 它的功能就是可以自定義顯示推播畫面
設計畫面
- 這裡呢就加上一張的圖片,其實呢它也不能做太深入的功能
- 我們希望在展開推播視窗後 - didReceive(_ notification: UNNotification),能從網路上下載一張圖片,然後顯示。
import UIKit
import UserNotifications
import UserNotificationsUI
final class NotificationViewController: UIViewController, UNNotificationContentExtension {
@IBOutlet weak var myImageView: UIImageView!
@IBOutlet weak var heightConstraint: NSLayoutConstraint!
override func viewDidLoad() { super.viewDidLoad() }
func didReceive(_ notification: UNNotification) { notificationAction(notification) }
}
// MARK: - 小工具
private extension NotificationViewController {
func notificationAction(_ notification: UNNotification) {
guard let userInfo = notification.request.content.userInfo as? [String: Any] ,
let imageUrlString = userInfo["image"] as? String,
let imageUrl = URL(string: imageUrlString)
else {
return
}
self.downloadImage(url: imageUrl) { result in
switch result {
case .failure(let error): print(error)
case .success(let data):
guard let data = data,
let image = UIImage(data: data)
else {
return
}
DispatchQueue.main.async {
self.myImageView.image = image
self.heightConstraint.constant = self.view.frame.width * (image.size.height / image.size.width)
}
}
}
}
func downloadImage(url: URL, result: @escaping (Result<Data?, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { result(.failure(error)); return }
result(.success(data))
}.resume()
}
}
註冊Category
- 這裡有點像註冊UITableViewCell的感覺,可以去選擇要使用那一種長相的推播視窗
@main
final class AppDelegate: UIResponder, UIApplicationDelegate {
let CategoryId: String = "MyCategory"
var window: UIWindow?
var pushToken: String?
var userInfo: [AnyHashable: Any]?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.notificationCategorySetting(identifier: CategoryId)
return true
}
}
// MARK: - 推播相關
extension AppDelegate {
func notificationCategorySetting(identifier: String) {
let category = UNNotificationCategory(identifier: identifier, actions: [], intentIdentifiers: [])
UNUserNotificationCenter.current().setNotificationCategories([category])
}
}
模擬器測試
- 這裡使用模擬器去做測試,payload要注意的是category的名字,要跟上面設定的一樣才行。
- payload的部分,就要去跟後台人員溝通一下了。
{
"Simulator Target Bundle": "idv.william.RichPushNotification",
"aps": {
"alert": {
"title": "SPY×FAMILY 間諜家家酒",
"subtitle": "是由日本漫畫家遠藤達哉所創作的作品,在2019年3月25日起於日本《少年Jump+》上定期連載",
"body": "本作敘述一名身為間諜的男性、另一位工作是殺手的女性,以及一個能讀心的超能力者女孩,三人互相隱瞞真實身分所組成的虛假家庭間的家庭喜劇。"
},
"category": "MyCategory"
},
"image": "https://media.gq.com.tw/photos/628b2ec34824d010bb0b3cd4/master/pass/165306325038.jpeg"
}
本地推播
- 這裡再補充一個本地通知的功能…
- 其實在前景 - 使用APP時的時候,預設收不到推播的,要利用userNotificationCenter(_:willPresent:withCompletionHandler:)來打開這個功能…
- 其中可以選擇要接收哪些類型的訊息…
- 如果這時候只讓聲音有反應的話,那就可以自訂收到推播後的處理了…
- 另外也可以從UNNotification中去分辨推播的種類:UNLocationNotificationTrigger - 本地端 / UNPushNotificationTrigger - 遠端 / UNTimeIntervalNotificationTrigger - 定時用 / UNCalendarNotificationTrigger - 行事曆用
// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
var options: UNNotificationPresentationOptions = [.sound, .list, .badge, .banner]
self.userInfo = notification.request.content.userInfo
guard let triggerType = Constant.NotificationTriggerType.parse(with: notification) else { return }
switch triggerType {
case .push: options = [.sound]; localNotification()
case .location: break
case .timeInterval: break
case .calendar: break
}
WWLog.shared.log(self.userInfo?._jsonData()?._string())
completionHandler(options)
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
self.userInfo = response.notification.request.content.userInfo
completionHandler()
}
func localNotification() {
let imageURL = Bundle.main.url(forResource: "ダイ", withExtension: "png")
let attachment = try! UNNotificationAttachment(identifier: "", url: imageURL!, options: nil)
let content = UNMutableNotificationContent()
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
content.title = "DRAGON QUESTドラゴンクエスト - ダイの大冒険-"
content.subtitle = "勇者鬥惡龍 達伊的大冒險"
content.body = "(日語:DRAGON QUESTドラゴンクエスト - ダイの大冒険-)是採用遊戲《勇者鬥惡龍系列》之世界觀創作的日本漫畫作品,由三條陸負責原作,稻田浩司負責作畫。於集英社漫畫雜誌《週刊少年JUMP》1989年第45號至1996年第52號期間進行連載。單行本全37卷。系列漫畫的單行本累計發行量超過4700萬本。"
content.badge = 1
content.sound = UNNotificationSound.default
content.attachments = [attachment]
let request = UNNotificationRequest(identifier: "notification", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { error in
if let error = error { wwPrint(error); return }
wwPrint("Success")
}
}
}
範例程式碼下載
後記
終於撐到了WWDC 2022,可愛的MacBook Air M2也出現,萬眾注目的MagSafe 3 充電埠回來了,個人是Air的愛好者,除了輕便之外,加上個人不喜歡風扇太大聲,而M1 / M2的MacBook Air正好是無風扇的設計,而且效能也是夠強大的,加上自己寫程式的功力也一般般,用這個就好…