iOS Local Notifications 本地推播

推播通知 (Push Notification) 分為兩種:「本地 (Local)」和「遠端 (Remote)」。本地通知是由 App 自己發出的(例如鬧鐘),不需要架設伺服器。

請求權限

所有通知都必須先獲得使用者同意。

import UserNotifications

func requestPermission() {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, error in
        if granted {
            print("使用者同意通知")
        } else {
            print("使用者拒絕通知")
        }
    }
}

發送通知

發送通知包含三個部分:內容 (Content)觸發條件 (Trigger)請求 (Request)

func scheduleNotification() {
    // 1. 內容
    let content = UNMutableNotificationContent()
    content.title = "喝水時間到了"
    content.body = "該起來走動一下囉!"
    content.sound = .default
    // 群組功能:相同 ID 的通知會被疊在一起
    content.threadIdentifier = "health-reminder" 
    
    // 2. 觸發條件 (例如 5 秒後)
    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
    
    // 3. 建立請求
    let request = UNNotificationRequest(identifier: "water_reminder", content: content, trigger: trigger)
    
    // 4. 加入排程
    UNUserNotificationCenter.current().add(request)
}

可互動通知 (Actionable Notifications)

你可以讓使用者直接在通知上點擊按鈕,而不需要打開 App。

func registerActions() {
    // 定義動作
    let drinkAction = UNNotificationAction(identifier: "ACTION_DRINK", title: "我喝了", options: [])
    let laterAction = UNNotificationAction(identifier: "ACTION_LATER", title: "稍後提醒", options: [])
    
    // 定義分類
    let category = UNNotificationCategory(identifier: "WATER_CATEGORY", actions: [drinkAction, laterAction], intentIdentifiers: [])
    
    // 註冊
    UNUserNotificationCenter.current().setNotificationCategories([category])
}

// 在發送通知時指定 categoryIdentifier
content.categoryIdentifier = "WATER_CATEGORY"

管理通知

你可以取消尚未發出的通知,或移除已經顯示在通知中心的通知。

// 取消特定 ID 的待發送通知
UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["water_reminder"])

// 移除通知中心裡已顯示的通知
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["water_reminder"])

在前景收到通知

預設情況下,App 在前景執行時不會跳出通知橫幅。如果你希望在前景也能顯示,需要實作 UNUserNotificationCenterDelegate

class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        UNUserNotificationCenter.current().delegate = self
        return true
    }
    
    // 讓通知在前景也能顯示
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.banner, .sound, .badge])
    }
}