Apple Watch 应用开发系列」Apple Watch 上的推送告知

本地告知和远端告知是告知用户信息的好办法。假如咱们的 watchOS App 有支撑告知的 iOS App,则默许情况下,咱们的 Apple Watch 会在恰当的时候显现告知。不过,还能够做进一步的优化。

告知是一个大话题,本节将只重视咱们在运用 watchOS 时需求留意的一些差异。你能够在另一篇文章 【iOS】朴素 Push 普识——了解 Push Notifications 全貌 中了解有关推送告知的更多信息。

接纳告知的最佳设备

Apple 会测验确定接纳告知的最佳目标设备。假如咱们只需 Apple Watch,告知就会呈现在那里。可是假如咱们运用手表和其他设备,则目标不仅取决于告知的类型,还取决于其来源。

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

咱们会留意到图中的两个方位,Apple 询问是否将告知直接发送到手表。在 watchOS 6 及更高版别中,Apple Watch 是 remote 和 background 告知的有用目标。 Apple Watch extension 在注册 remote 告知时会收到一个仅有的 device token,就像在 iOS 中一样。

Short look

当 Apple Watch 收到告知时,它会经过振动告知用户。假如用户经过抬起手腕来检查告知,Apple Watch 会显现一个缩略版别,称为 Short look。假如用户检查告知的时间超越一秒,Apple Watch 将供给更详细的版别 Long look。

Short look 告知是展现给用户的快速摘要。简短的外观以预界说的布局显现应用程序的图标和称号,以及可选的告知标题。可选的告知标题是关于告知的简短阐明,例如“新账单”、“提醒”或“分数警报”,并增加到 alert 键的值中。这让用户能够决定是否接着运用 Long look。

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

Long look

Long look 是一个能够自界说的翻滚界面,带有默许的静态界面,或可选的动态创建的界面。与 Short look 界面不同,Long look 供给了更多的定制才能。

窗扇(Sash)是顶部水平的 Bar。默许情况下它是半透明的,但咱们能够将其设置为任何色彩和不透明度值。咱们能够经过完成 SwiftUI 视图来自界说内容区域,稍后咱们将了解它。

尽管咱们能够完成多个 UNNotificationAction 选项,但多项需求用户进行翻滚操作,会有糟糕的用户体会。系统供给的 Dismiss 按钮始终位于界面底部。点击关闭会隐藏告知而不告知 Apple Watch App。

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

运用模拟器测验告知

构建 Pawsome 项目

新建项目,将其命名为 Pawsome,由于 Xcode 14 的更新,这儿咱们无法再挑选 Notification Scene:

developer.apple.com/documentati…

Xcode 14 包括一个用于 watchOS 应用程序的默许模板,它将 WatchKit 应用程序和 WatchKit 应用程序扩展目标组合到一个 Watch 应用程序目标中,然后简化了代码、财物和本地化管理。

WatchKit 故事板在 watchOS 7.0 及更高版别中已弃用。请迁移到 SwiftUI 和 SwiftUI 生命周期。

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

为应用增加图标、图片资源:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

修正 ContenView 代码,测验展现图片资源,运转项目,你会看到猫咪列表展现:

struct ContentView: View {
    var body: some View {
        List(1..<21) { i in
            Image("cat\(i)")
                .resizable()
                .scaledToFit()
        }
        .listStyle(CarouselListStyle())
    }
}

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

增加 LocalNotifications

新增 Notifications Scheme

咱们新增一个 PushNotificationPayload.apns 文件:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

{
  "aps": {
    "alert": {
      "body": "Tap me to see an adorable kitty cat.",
      "title": "Giggle Time!",
    },
    "category": "myCategory"
  }
}

咱们接着新增 Scheme “Pawsome WatchKit App (Notification)”:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

修改 Schema,在 Run Tab 进行调整,切换至咱们新增的 Scheme 并运转:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

咱们会看到呈现了权限申请、允许后呈现 Short Look告知,点击后呈现 Long Look 告知,再次点击进入 App:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

运用 WKUserNotificationHostingController 绑定 SwiftUI View

假如咱们想自界说用户点击告知后的 App 的行为,该怎么做呢?

首先,咱们创建一个 NotificationView.swift,暂时无需做额外的调整:

import SwiftUI
struct NotificationView: View {
    var body: some View {
        Text("Hello, World!")
    }
}
struct NotificationView_Previews: PreviewProvider {
    static var previews: some View {
        NotificationView()
    }
}

接着创建 NotificationController.swift,并将内容替换为:

import WatchKit
import SwiftUI
import UserNotifications
class NotificationController: WKUserNotificationHostingController<NotificationView> {
  override var body: NotificationView {
    return NotificationView()
  }
  override func willActivate() {
    // This method is called when watch view controller is about to be visible to user
    super.willActivate()
  }
  override func didDeactivate() {
    // This method is called when watch view controller is no longer visible
    super.didDeactivate()
  }
  override func didReceive(_ notification: UNNotification) {
    // This method is called when a notification needs to be presented.
    // Implement it if you use a dynamic notification interface.
    // Populate your dynamic notification interface as quickly as possible.
  }
}

回到 PawsomeApp.swift,将内容替换为:

import SwiftUI
@main
struct PawsomeApp: App {
  @SceneBuilder var body: some Scene {
    WindowGroup {
      NavigationView {
        ContentView()
      }
    }
    WKNotificationScene(
      controller: NotificationController.self,
      category: "myCategory"
    )
  }
}

调用 WKNotificationScene 是告知 Apple Watch 为有用负载中标识的每个类别显现什么视图。

重新构建项目,在呈现 Short Look 后,点击告知,将会展现咱们之前增加的 NotificationView.swift:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

为告知增加 UNNotificationAction

新建 LocalNotifications.swift 文件,并增加代码:

import Foundation
import UserNotifications
final class LocalNotifications: NSObject {
    static let categoryIdentifier = "Pawsome"
    private let actionIdentifier = "viewCatsAction"
}

categoryIdentifier、actionIdentifier 将在后续运用。

在 LocalNotifications 中增加代码:

override init() {
    super.init()
    Task {
        do {
            try await self.register()
            try await self.schedule()
        } catch {
            print("⌚️ local notification: \(error.localizedDescription)")
        }
    }
}
func register() async throws {
}
func schedule() async throws {
}

这儿咱们重写了 init 办法,并运用 **async **语法测验 register、schedule 本地告知。

在 resign 办法中增加代码:

let current = UNUserNotificationCenter.current()
try await current.requestAuthorization(options: [.alert, .sound])
current.removeAllPendingNotificationRequests()
let action = UNNotificationAction(
    identifier: self.actionIdentifier,
    title: "More Cats!",
    options: .foreground)
let category = UNNotificationCategory(
    identifier: Self.categoryIdentifier,
    actions: [action],
    intentIdentifiers: [])
current.setNotificationCategories([category])
current.delegate = self

咱们恳求了告知权限,然后运用刚刚的 categoryIdentifier 、actionIdentifier 结构了一个 UNNotificationAction 和 UNNotificationCategory,并将其设置为 NotificationCategory,并将自己设置为代理。

在代码最终弥补代码,完成 UNUserNotificationCenterDelegate:

extension LocalNotifications: UNUserNotificationCenterDelegate {
  func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
    return [.list, .sound]
  }
}

这儿是怎么处理 App 行时到达的告知。

持续弥补 schedule 办法中的代码:

let current = UNUserNotificationCenter.current()
let settings = await current.notificationSettings()
guard settings.alertSetting == .enabled else { return }
let content = UNMutableNotificationContent()
content.title = "Pawsome"
content.subtitle = "Guess what time it is"
content.body = "Pawsome time!"
content.categoryIdentifier = Self.categoryIdentifier
let components = DateComponents(minute: 30)
let trigger = UNCalendarNotificationTrigger(
    dateMatching: components,
    repeats: true)
let request = UNNotificationRequest(
    identifier: UUID().uuidString,
    content: content,
    trigger: trigger)
try await current.add(request)

判别权限后,咱们创建了一个 UNMutableNotificationContent 并进行装备,结构一个 UNCalendarNotificationTrigger ,在每日凌晨 0 点 30 触发了。进而结构一个 UNNotificationRequest,并增加恳求。

回到 PawsomeApp.swift,持续修正文件:

import SwiftUI
@main
struct PawsomeApp: App {
    private let local = LocalNotifications()
    @SceneBuilder var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
            }
        }
        WKNotificationScene(
            controller: NotificationController.self,
            category: "myCategory"
        )
        WKNotificationScene(
            controller: NotificationController.self,
            category: LocalNotifications.categoryIdentifier
        )
    }
}

咱们增加了 LocalNotifications 实例,同时增加了新的 category,并将 PushNotificationPayload.apns 中的 category 的值改为 Pawsome,构建项目:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

现在,咱们新增的 Action 展现出来了。

自界说 Long Look 款式

仔细看看 NotificationController.swift,咱们会看到 body 回来了一个 NotificationView 的实例。 Controller 是咱们接纳和解析告知的当地。咱们能够在视图中运用 Controller 搜集的数据。

切换到 NotificationView.swift 以使告知以你想要的方法显现。 将默许文件的全部内容替换为:

import SwiftUI
struct NotificationView: View {
  let message: String
  let image: Image
  var body: some View {
    ScrollView {
      Text(message)
        .font(.headline)
      image
        .resizable()
        .scaledToFit()
    }
  }
}
struct NotificationView_Previews: PreviewProvider {
  static var previews: some View {
    NotificationView(
      message: "QWQ",
      image: Image("cat\(Int.random(in: 1...20))")
    )
  }
}

咱们需求为要显现的视图供给消息和图画。然后只是在 ScrollView 中显现这两个特点。回来 NotificationController.swift 并将类的内容替换为:

class NotificationController: WKUserNotificationHostingController<NotificationView> {
    var image: Image!
    var message: String!
    override var body: NotificationView {
        return NotificationView(message: message, image: image)
    }
    override func didReceive(_ notification: UNNotification) {
        let content = notification.request.content
        message = content.body
        let num = Int.random(in: 1...20)
        image = Image("cat\(num)")
    }
    override func willActivate() {
        super.willActivate()
    }
    override func didDeactivate() {
        super.didDeactivate()
    }
}

咱们存储标题和图画,然后为要显现的视图调用初始化程序,并传入恰当的参数。再次构建并运转:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

这次,咱们的小猫咪呈现啦~在这儿,咱们也能够彻底不运用随机视图,而是修正为由 apns 指定,修正 PushNotificationPayload.apns:

{
  "aps": {
    "alert": {
      "body": "Tap me to see an adorable kitty cat.",
      "title": "Giggle Time!",
    },
    "category": "Pawsome"
  },
  "imageNumber": 5
}

修正 NotificationController.swift 读取 imageNumber 字段:

override func didReceive(_ notification: UNNotification) {
    let content = notification.request.content
    message = content.body
    let num = content.userInfo["imageNumber"] as! Int
    image = Image("cat\(num)")
}

交互式告知

点击显现详细信息。 是不是发生了什么意想不到的事情?默许情况下,推送告知不是交互式的。

将以下行增加到 NotificationController:

override class var isInteractive: Bool { true }

再次构建并运转。 这一次,当你点击喵喵图片,将不会有任何相应,不会跳转到 App:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

WKUserNotificationHostingController<T> 的 isInteractive 类型特点指定告知是否应承受用户输入。 默许值为 false,这意味着你只能与按钮交互。 经过将值更改为 true,你告知 watchOS 告知应该承受用户输入。

这儿咱们并没有增加事情,所以没有任何相应。咱们能够为其增加所需求的事情。

尽管咱们处理了一个问题,但引入了另一个问题。 点击不再翻开应用程序。 假如用户点击应用程序图标或窗扇上的任何方位,应用程序仍将翻开。

自界说窗扇款式

修改 NotificationController.swift 并在类的结束增加以下行:

override class var sashColor: Color? { Color.green }
override class var titleColor: Color? { Color.purple }
override class var subtitleColor: Color? { Color.orange }
override class var wantsSashBlur: Bool { true }

修正模拟器 watchOS 版别为 9.0 以前,再次构建并运转。 你会留意到色彩现已变成了可怕的东西:

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

「Apple Watch 应用开发系列」Apple Watch 上的推送通知

请记住,watchOS 9.0 及以后,该功能失效。

Remote push notifications 根底

大多数应用程序运用远程推送告知而不是本地告知。在 iOS 中,咱们有必要创建一个扩展来修正传入的有用负载,假如咱们想要一个自界说界面,则还有必要创建另一个扩展。更糟糕的是,除非从 iOS 15 开始,否则咱们无法运用 SwiftUI 进行自界说界面。然而在 watchOS 中,推送告知的工作方法与本地告知彻底一样!咱们学到的关于运用 WKUserNotificationHostingController 解析有用负载并回来自界说 SwiftUI 视图的一切内容都是一样的。

获取令牌

假定咱们现已创建了推送告知令牌,这是咱们从 Apple Developer 门户下载的以 p8 扩展名结束的文件。这样,咱们能够经过告知东西进行推送的发送。

创建 WKExtensionDelegate

在 iOS 中,咱们运用 AppDelegate 注册推送告知。 watchOS 上不存在该类。 咱们会运用 WKExtensionDelegate。 创建一个名为 ExtensionDelegate.swift 的新文件并张贴:

import WatchKit
import UserNotifications
final class ExtensionDelegate: NSObject, WKExtensionDelegate {
    func applicationDidFinishLaunching() {
        Task {
            do {
                let success = try await UNUserNotificationCenter
                    .current()
                    .requestAuthorization(options: [.badge, .sound, .alert])
                guard success else { return }
                // 4
                await MainActor.run {
                    WKExtension.shared().registerForRemoteNotifications()
                }
            } catch {
                print(error.localizedDescription)
            }
        }
    }
    func didRegisterForRemoteNotifications(withDeviceToken deviceToken: Data) {
        print(deviceToken.reduce("") { $0 + String(format: "%02x", $1) })
    }
}

咱们声明一个完成 WKExtensionDelegate 的类。 由于该协议基于 NSObjectProtocol,因此你还需求从 NSObject 派生。就像在 iOS 中一样,只需注册发生,咱们就能够获取 deviceToken。 在非实例的 App 中会存储和运用令牌,而不只是打印它。

咱们运用 applicationDidFinishLaunching 来恳求运用推送告知的权限。假如授予权限,则运用 WKExtension 单例注册推送告知,假如成功则调用 didRegisterForRemoteNotifications(withDeviceToken:)。

要告知 watchOS 它应该运用咱们的 ExtensionDelegate,请将以下代码增加到 PawsomeApp.swift 中 PawsomeApp 结构的顶部:

@WKExtensionDelegateAdaptor(ExtensionDelegate.self) private var extensionDelegate

运用远程推送

回到这部分开头,watchOS 上的远程推送告知和本地告知没什么两样!欢迎参阅另一篇文章 【iOS】朴素 Push 普识——了解 Push Notifications 全貌 了解有关远程推送告知的更多信息。

现在咱们知道了在 watchOS 上显现自界说告知的根底知识,咱们还能够从这儿做更多的事情,包括处理用户从告知中挑选的操作。也欢迎参阅 Apple 的用户告知 (apple.co/3hFtwKR) 文档。

有关创建 Scheme 和 JSON 有用负载以及直接在手表上进行测验的更多详细信息,请参阅 Apple 开发人员文档中的测验自界说告知 (apple.co/3tTDE5F)。

附件

  • Pawsome 项目的一切文件请参阅:github.com/LLLLLayer/A…