實時活動(Live Activity) - 在鎖定螢幕和靈動島上顯示應用程式的實時資料

語言: CN / TW / HK

我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第1篇文章,點選檢視活動詳情

本文參考、翻譯並實現 Apple‘s documentation activitykit displaying live data with live activitiesUpdating and ending your Live Activity with remote push notifications 內容,文章涉及的專案程式碼可以從這裡獲取。

iShot_2022-09-19_01.42.20.gif

概述

實時活動(Live Activity) 在 iPhone 鎖定螢幕和靈動島中顯示 App 的實時資料,能幫助使用者跟蹤 App 的內容。

要提供 Live Activity,開發者需要將程式碼新增到新的或現有的小元件中。Live Activity 使用了 WidgetKit 的功能,並使用 SwiftUI 編寫介面。而 ActivityKit 的作用是處理 Live Activity 的生命週期:開發者使用它的 API 來進行請求、更新和結束實時活動。

實時活動功能和 ActivityKit 將包含在今年晚些時候推出的 iOS 16.1 中。

實時活動要求或限制

  • 時間

時活動還會一直保留在鎖定螢幕上,直到使用者主動將其移除,或交由系統在四小時後將其移除。即實時活動會靈動上島最多保留八小時,在鎖定螢幕上最多保留十二小時

  • 更新

每個實時活動執行在自己的沙盒中,與小元件不同的是,它無法訪問網路或接收位置更新。開發者若要更新實時活動的動態資料,請在 App 中使用 ActivityKit 框架或允許實時活動接收遠端推送通知。但需要注意,ActivityKit 更新和遠端推送通知更新的更新動態資料大小不能超過 4KB

  • 樣式

實時活動針對鎖定螢幕和靈動島提供了不同的檢視。鎖定螢幕可以出現在所有支援 iOS 16 的裝置上。而靈動島在支援裝置上,使用以下檢視顯示實時活動:緊湊前檢視、緊湊尾檢視、最小檢視和擴充套件檢視。

當用戶觸控靈動島,且靈動島中有緊湊或最小檢視,同時實時活動更新時,會出現擴充套件檢視。在不支援靈動島的裝置上,擴充套件檢視顯示為實時活動更新的橫幅。

為確保系統可以在每個位置顯示 App 的實時活動,開發者必須支援所有檢視

為 App 新增對實時活動的支援

描述實時活動介面的程式碼是 App 的小元件的一部分。如果開發者已經在 App 中提供小元件,則可以將實時活動的介面程式碼新增到現有的小元件中,並且可以在小元件和實時活動之間重用部分程式碼。但儘管實時活動利用了 WidgetKit 的功能,但它們並不是小元件。與更新小元件介面的 timeline 機制相比,開發者只能使用 ActivityKit 或遠端推送通知來更新實時活動。

開發者也可以建立一個小元件來實現實時活動,而無需提供小部件。但請儘可能考慮同時提供小元件和實時活動,以滿足使用者的需求。

本文將參考 Apple 的開發文件,實現實時活動。

建立專案併為 App 新增對實時活動的支援

建立專案 LiveActivities 併為專案新增新 Target,選擇 Widget Extension:

| image-20220916214530113.png | image-20220916215239411.png | image-20220916215036844.png | | :----------------------------------------------------------: | ------------------------------------------------------------ | ------------------------------------------------------------ |

可以將其命名 LiveActivitiesWidget,暫時不需要勾選 Include Configuration Intent,單擊 Finsih 並同意 Activate scheme 對話方塊:

| image-20220916215416018.png | image-20220916215448651.png | | ------------------------------------------------------------ | ------------------------------------------------------------ |

info.plist 新增 NSSupportsLiveActivities key,並設定為YES

image-20220916220349762.png

定義實時活動的靜態和動態資料

在為 App 的實時活動建立配置物件之前,首先通過實現 ActivityAttributes (描述實時活動的內容的協議)來描述實時活動將展示的資料。它的宣告如下:

```swift public protocol ActivityAttributes : Decodable, Encodable {

/// The associated type that describes the dynamic content of a Live Activity.
///
/// The dynamic data of a Live Activity that's encoded by `ContentState` can't exceed 4KB.
associatedtype ContentState : Decodable, Encodable, Hashable

} ```

除了包括實時活動中出現的靜態資料,開發者還可以使用 ActivityAttributes 來宣告所需的自定義 Activity.ContentState 型別,該型別描述 App 的實時活動的動態資料。

我們將以一個披薩外賣行程為例,新建一個 PizzaDeliveryAttributes.swift 檔案並新增以下內容:

```swift import Foundation import ActivityKit

struct PizzaDeliveryAttributes: ActivityAttributes { public typealias PizzaDeliveryStatus = ContentState

public struct ContentState: Codable, Hashable {
    var driverName: String
    var deliveryTimer: ClosedRange<Date>
}

var numberOfPizzas: Int
var totalAmount: String
var orderNumber: String

} ```

在上面的示例中,PizzaDeliveryAttributes 描述了以下靜態資料:訂購的比薩餅數量、客戶需要支付的金額以及訂單號。

注意程式碼是如何定義 Activity.ContentState來封裝動態資料的:送披薩的司機的名字和預計送達時間。

此外,該示例定義了類型別名 PizzaDeliveryStatus 以使程式碼更具描述性和易於閱讀。

建立實時活動配置

接著,我們需要新增程式碼,在小元件的實現中返回 ActivityConfiguration。以下使用上一個示例中的 PizzaDeliveryAttributes 結構來配置我們的事實活動:

```swift import SwiftUI import WidgetKit

@main struct LiveActivitiesWidget: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in // Create the view that appears on the Lock Screen and as a // banner on the Home Screen of devices that don't support the // Dynamic Island. // ... } dynamicIsland: { context in // Create the views that appear in the Dynamic Island. // ... } } } ```

假如我們的 App 已經提供了小元件,請將實時活動新增到 WidgetBundle 裡。 如果沒有 WidgetBundle,例如 App 當前只提供一個小元件,請按照建立 Widget Extension 中的描述建立一個 WidgetBundle,然後將實時活動新增到其中,例如:

```swift @main struct LiveActivitiesWidgets: WidgetBundle { var body: some Widget { FavoritePizzaWidget()

    if #available(iOS 16.1, *) {
        PizzaDeliveryLiveActivity()
    }
}

} ```

建立鎖定螢幕檢視

要建立實時活動的介面,我們可以在之前建立的 Widget Extension 中使用 SwiftUI。與小元件類似,我們無需為實時活動提供介面的大小,而是讓系統確定適當的尺寸

新建檔案 LockScreenLiveActivityView.swift,並新增以下程式碼,使用 SwiftUI 檢視描述PizzaDeliveryAttributes 的資訊:

```swift import WidgetKit import SwiftUI

struct LockScreenLiveActivityView: View { let context: ActivityViewContext

var body: some View {
    VStack {
        Spacer()
        Text("\(context.state.driverName) is on their way with your pizza!")
        Spacer()
        HStack {
            Spacer()
            Label {
                Text("\(context.attributes.numberOfPizzas) Pizzas")
            } icon: {
                Image(systemName: "bag")
                    .foregroundColor(.indigo)
            }
            .font(.title2)
            Spacer()
            Label {
                Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                    .multilineTextAlignment(.center)
                    .frame(width: 50)
                    .monospacedDigit()
            } icon: {
                Image(systemName: "timer")
                    .foregroundColor(.indigo)
            }
            .font(.title2)
            Spacer()
        }
        Spacer()
    }
    .activitySystemActionForegroundColor(.indigo)
    .activityBackgroundTint(.cyan)
}

} ```

這裡需要注意,如果其高度超過 160,系統可能會截斷鎖定螢幕上的實時活動。

建立緊湊和最小的檢視

在支援實時活動的裝置的靈動島上,當 App 開始一個實時活動並且它是唯一一個活躍的實時活動時,緊湊前檢視和尾檢視一起出現,在靈動島中形成一個有凝聚力的檢視。當多個實時活動處於活動狀態時(無論是來自我們的 App 還是來自多個 App),系統會選擇哪些實時活動可見,並顯示兩個最小檢視:一個最小檢視顯示附加到靈動島,而另一個顯示為分離的樣式。

預設情況下,靈動島中的緊湊檢視和最小檢視使用黑色背景顏色和白色文字。 使用 keylineTint(_:) 修改器將可選的色調應用到靈動島,例如青色,稍後我們會看到。

以下示例展示了披薩外賣應用程式如何使用 SwiftUI 檢視提供所需的緊湊和最小檢視:

```swift @main struct LiveActivitiesWidget: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in // Create the view that appears on the Lock Screen and as a // banner on the Home Screen of devices that don't support the // Dynamic Island. // ... } dynamicIsland: { context in // Create the views that appear in the Dynamic Island. DynamicIsland { // Create the expanded view. // ...

        } compactLeading: {
            Label {
                Text("\(context.attributes.numberOfPizzas) Pizzas")
            } icon: {
                Image(systemName: "bag")
                    .foregroundColor(.indigo)
            }
            .font(.caption2)
        } compactTrailing: {
            Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                .multilineTextAlignment(.center)
                .frame(width: 40)
                .font(.caption2)
        } minimal: {
            VStack(alignment: .center) {
                Image(systemName: "timer")
                Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                    .multilineTextAlignment(.center)
                    .monospacedDigit()
                    .font(.caption2)
            }
        }
        .keylineTint(.cyan)
    }
}

} ```

建立擴充套件檢視

除了緊湊和最小檢視之外,我們還必須支援擴充套件檢視。當用戶觸控並持有一個緊湊或最小的檢視時,擴充套件檢視會出現,並且也會短顯示實時活動更新。當我們更新實時活動時,沒有靈動島的裝置也會將擴充套件檢視顯示為橫幅。 使用 DynamicIslandExpandedRegionPosition 指定我們希望的靈動島擴充套件區域位置。以下示例顯示了披薩外賣應用程式如何建立其擴充套件檢視:

```swift @main struct PizzaDeliveryWidget: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in // Create the view that appears on the Lock Screen and as a // banner on the Home Screen of devices that don't support the // Dynamic Island. LockScreenLiveActivityView(context: context) } dynamicIsland: { context in // Create the views that appear in the Dynamic Island. DynamicIsland { // Create the expanded view. DynamicIslandExpandedRegion(.leading) { Label("(context.attributes.numberOfPizzas) Pizzas", systemImage: "bag") .foregroundColor(.indigo) .font(.title2) }

            DynamicIslandExpandedRegion(.trailing) {
                Label {
                    Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                        .multilineTextAlignment(.trailing)
                        .frame(width: 50)
                        .monospacedDigit()
                } icon: {
                    Image(systemName: "timer")
                        .foregroundColor(.indigo)
                }
                .font(.title2)
            }

            DynamicIslandExpandedRegion(.center) {
                Text("\(context.state.driverName) is on their way!")
                    .lineLimit(1)
                    .font(.caption)
            }

            DynamicIslandExpandedRegion(.bottom) {
                Button {
                    // Deep link into your app.
                } label: {
                    Label("Call driver", systemImage: "phone")
                }
                .foregroundColor(.indigo)
            }
        } compactLeading: {
            // Create the compact leading view.
            // ...
        } compactTrailing: {
            // Create the compact trailing view.
            // ...
        } minimal: {
            // Create the minimal view.
            // ...
        }
        .keylineTint(.yellow)
    }
}

} ```

這裡也需要注意,如果靈動島的高度超過 160,系統可能會截斷靈動島中的實時活動。

為了呈現展開的實時活動中出現的檢視,系統將展開的檢視劃分為不同的區域。請注意上述示例如何返回一個指定多個 DynamicIslandExpandedRegion 物件的靈動島。傳遞以下 DynamicIslandExpandedRegionPosition 值以在展開檢視中的指定位置佈置內容:

  • center 將內容置於 TrueDepth 攝像頭下方。

  • leading 將內容沿展開的實時活動的前沿放置在 TrueDepth 攝像頭旁邊,並在其下方包裹其他內容。

  • trailing 將內容沿展開的實時活動的後緣放置在 TrueDepth 攝像頭旁邊,並在其下方包裹其他內容。

  • bottom 將內容置於 leading、trailing 和 center 之下。

image-20220917033045844.png

為了呈現展開的實時活動中出現的內容,系統首先確定 center 內容的寬度,同時考慮 leading 和 trailing 內容的最小寬度。 然後系統根據其垂直位置放置 leading 和 trailing 內容並確定其大小。預設情況下, leading 和 trailing 接相等水平空間。

image-20220917033103851.png

我們可以通過將優先順序傳遞給 init(_:priority:content:) 初始化程式來告訴系統優先考慮 DynamicIslandExpandedRegion 檢視之一。 系統以靈動島的全寬呈現具有最高優先順序的檢視。

如果內容太寬而無法出現在 TrueDepth 相機旁邊的 leading 位置,請使用 belowIfTooWide 修飾符來渲染 TrueDepth 相機下方的 leading 內容。

使用自定義顏色

預設情況下,系統使用預設的文字色和最適合使用者鎖定螢幕的實時活動的背景顏色。如果要設定自定義色調顏色,請使用 activityBackgroundTint(_:) 檢視修飾符。此外,使用 activitySystemActionForegroundColor(_:) 檢視修飾符來定義系統在鎖定螢幕上實時活動旁邊顯示的輔助操作按鈕的文字顏色。

要設定自定義的半透明背景色,請使用 opacity(_:) 檢視修飾符或指定一個不透明背景顏色。

在 Always-On Retina 顯示屏的裝置上,系統會調暗螢幕以延長電池壽命,並在鎖定螢幕上呈現實時活動,就像在暗模式下一樣。 使用 SwiftUI 的 isLuminanceReduced 環境值來檢測 Always On 並使用在 Always On 中看起來很棒的影象。

確保實時活動可用

實時活動僅在 iPhone 上可用。如果我們的 App 可在多個平臺上使用並提供小元件,請確保實時活動在執行時可用。此外,使用者可以在“設定”應用中選擇停用應用的實時活動。

要檢視實時活動是否可用以及使用者是否允許我們的 App 使用實時活動:

一個應用可以啟動多個實時活動,而一個裝置可以執行多個 App 的實時活動。除了確保實時活動可用之外,在開始、更新或結束實時活動時請注意處理可能出現的錯誤。例如,啟動實時活動可能會失敗,因為使用者的裝置可能已達到其執行實時活動的限制。

開始實時活動

當應用程式在前臺時,我們可以在應用程式程式碼中使用 request(attributes:contentState:pushType:) 函式啟動實時活動。它將開發者建立的 attributescontentState 作為引數來提供顯示在實時活動中的初始值,並告訴系統哪些資料是動態的。如果我們使用遠端推送通知來更新實時活動,還需要提供 pushType 引數。

更新 LiveActivities 中的 ContentView.swift 以下程式碼示例從前面的示例中為披薩外賣啟動了一個新的實時活動:

```swift import SwiftUI import ActivityKit

struct ContentView: View { let minutes = 12 @State var deliveryActivity: Activity? = nil var body: some View { VStack { Text("Hello, world!") } .onAppear { if #available(iOS 16.1, *) { let future = Calendar.current.date(byAdding: .minute, value: (minutes), to: Date())! let date = Date.now...future let initialContentState = PizzaDeliveryAttributes.ContentState(driverName: "Layer", deliveryTimer:date) let activityAttributes = PizzaDeliveryAttributes(numberOfPizzas: 3, totalAmount: "$66.66", orderNumber: "12345") do { deliveryActivity = try Activity.request(attributes: activityAttributes, contentState: initialContentState) } catch (let error) { } } } } } ```

請注意上面的程式碼片段不傳遞 pushType 引數,在不使用遠端推送通知的情況下更新其內容。它還將返回的deliveryActivity 儲存,可用於更新和結束實時活動。有關使用遠端推送通知更新您的實時活動的更多資訊,請參閱 Updating and ending your Live Activity with remote push notifications

我們只能在應用程式處於前臺時從 App 啟動實時活動。 但是我們可以在 App 在後臺執行時更新或結束實時活動,例如通過使用後臺任務。

iShot_2022-09-17_15.27.00.gif

啟動多個實時活動:

iShot_2022-09-17_15.29.08.gif

更新實時活動

當我們從 App 啟動實時活動時,使用啟動實時活動時收到的 Activity 物件的 update(using:) 函式更新顯示在實時活動中的資料。要檢索 App 當前活動的實時活動,請使用 activities

例如,披薩配送可以更新顯示配送狀態的實時活動,其中包含新的配送時間和新的司機。它還可以使用 update(using:alertConfiguration:) 函式在 iPhone 和 Apple Watch 上顯示提示,告訴使用者新的實時活動內容,在 Button("Order pizza!"){} 後新增以下程式碼:

swift Button("Update!") { if #available(iOS 16.1, *) { let future = Calendar.current.date(byAdding: .minute, value: (Int(minutes / 2)), to: Date())! let date = Date.now...future let updatedDeliveryStatus = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "Layer's brother", deliveryTimer: date) let alertConfiguration = AlertConfiguration(title: "Delivery Update", body: "Your pizza order will immediate delivery.", sound: .default) Task { try? await Task.sleep(nanoseconds: 5_000_000_000) await deliveryActivity?.update(using: updatedDeliveryStatus, alertConfiguration: alertConfiguration) } } }

在點選“Update!”的 5 秒後,配送員和時間都發生了改變,同時有錄屏無法很好表現的提醒效果:

iShot_2022-09-17_15.43.29.gif

在 Apple Watch 上,系統使用提醒的標題和正文。在 iPhone 上,系統不會顯示常規提醒,而是顯示靈動島中展開的實時活動。 在不支援靈動島的裝置上,系統會在主螢幕上顯示一個橫幅,該橫幅使用 App 的實時活動的擴充套件檢視。

帶有動畫的內容更新

當我們定義實時活動的介面時,系統會忽略任何動畫修飾符——例如,withAnimation(_:_:)animation(_:value:)——並改用系統的動畫時間。但當實時活動的動態內容發生變化時,系統會執行一些動畫。

  • 文字檢視通過模糊的內容過渡動畫展示內容變化,並且系統為影象和 SF Symbols 做動畫的內容過渡。

  • 如果開發者根據內容或狀態更改使用者介面,進行新增或刪除檢視,檢視會淡入淡出。

  • 可以使用以下檢視轉換來配置這些內建轉換:opacitymove(edge:)slidepush(from:)

  • 使用 numericText(countsDown:) 實現計時效果的文字。

在配備 Always On Retina 顯示屏的裝置上,為例保持 Always On 時的電量,系統不會執行動畫。可以在動畫內容更改之前使用 SwiftUI 的 isLuminanceReduced 環境值來檢測是否開啟 Always On。

在 App 中結束實時活動

始終在關聯的任務或實時事件結束後,結束實時活動的展示。已結束的實時活動將保留在鎖定螢幕上,直到使用者將其刪除或系統自動將其刪除。自動刪除取決於開發者提供給 end(using:dismissalPolicy:) 函式的解除策略。

此外,始終包含更新的 Activity.ContentState 以確保實時活動在結束後顯示最新和最終的內容。這很重要,因為實時活動可能還會在鎖定螢幕上保持一段時間的可見。

繼續在 Button("Update!") {} 後新增程式碼:

swift Button("I do not want it!!") { if #available(iOS 16.1, *) { let finalDeliveryStatus = PizzaDeliveryAttributes.PizzaDeliveryStatus(driverName: "Anne Johnson", deliveryTimer: Date.now...Date()) Task { try? await Task.sleep(nanoseconds: 5_000_000_000) await deliveryActivity?.end(using:finalDeliveryStatus, dismissalPolicy: .default) } } }

上面的示例使用預設解除策略。因此,實時活動結束後會在鎖定螢幕上顯示一段時間,以便使用者瀏覽手機以檢視最新資訊。使用者可以隨時選擇移除實時活動,或者系統在活動結束四小時後自動移除。要立即刪除鎖定螢幕的結束實時活動,請使用 .immediate。或者,使用 after(_:) 指定四小時內的日期。

使用者可以隨時從鎖定螢幕中刪除 App 的實時活動。這隻會結束 App 的實時活動的展示,但不會結束或取消使用者啟動實時活動的操作。例如使用者可以從鎖定螢幕中刪除他們的披薩外賣的實時活動,但這不會取消披薩訂單。

當用戶或系統移除實時活動時,ActivityState 更改為 ActivityState.dismissed

使用遠端推送通知更新或結束實時活動

除了使用 ActivityKit 從 App 更新和結束實時活動之外,我們還可以使用從伺服器傳送到 Apple 推送通知服務 (APN) 的遠端推送通知更新或結束實時活動。 要了解有關使用遠端推送通知更新實時活動的更多資訊,請參閱 Updating and ending your Live Activity with remote push notifications

跟蹤更新

像上面的例子,當我們啟動實時活動時,ActivityKit 返回一個 Activity 物件。 除了唯一標識每個實時活動的 id 之外,還提供序列來觀察內容狀態、活動狀態和 Push token 的更新。使用相應的序列在 App 中接收更新,使 App 和 實時活動保持同步:

  • 要觀實時活動的狀態,例如確定它是處於活動狀態還是已經結束,使用 activityStateUpdates

  • 要觀察實時活動內容的變化,使用 contentState

  • 要觀察實時活動的 Push token 的變化,使用 pushTokenUpdates

獲取實時活動列表

一個 App 可以啟動多個實時活動。例如,體育應用程式可能允許使用者為他們感興趣的每個現場體育比賽啟動一個 實時活動。如果 App 啟動多個實時活動,請使用 activityUpdates 函式獲取有關 App 正在進行的活動的通知。跟蹤正在進行的實時活動以確保 App 的資料與 ActivityKit 跟蹤的正在執行的實時活動同步。

以下程式碼段顯示了披薩外賣如何檢索正在進行的活動列表:

swift // Fetch all ongoing pizza delivery Live Activities. for await activity in Activity<PizzaDeliveryAttributes>.activityUpdates { print("Pizza delivery details: \(activity.attributes)") }

獲取所有活躍的實時活動列表的另一個方案是開發者手動維護正在進行的實時活動資料,並確保開發者不會讓任何活動執行超過需要的時間。例如系統可能會停止我們的 App,或者我們的 App 可能會在實時活動處於活躍狀態時崩潰。當應用下次啟動時,檢查是否有任何實時活動仍然處於活躍狀態,更新儲存的實時活動列表資料,並結束任何不再相關的實時活動。

使用遠端推送通知更新實時活動

ActivityKit 向我們提供了從 App 啟動、更新和結束實時活動的功能。 此外,它還提供接收 Push token 的功能,開發者可以使用遠端推送通知來更新 App 的實時活動,這些遠端推送通知從開發者的伺服器傳送到 Apple 推送通知服務 (APNs)。

檢視 User Notification 文件

接收遠端推送通知更新實時活動,類似於我們使用常規的通知推送方式。我們使用 User Notifications 來請求使用通知許可權,並且必須設定遠端通知伺服器。對於實時活動,此任務與在使用者裝置上顯示普通通知的遠端推送通知相同。

如果我們不熟悉遠端推送通知,請檢視 User Notifications 框架的文件。 確保閱讀了Registering Your App with APNsAsking Permission to Use Notifications

要使用遠端推送通知來更新 App 的實時活動,我們需要使用 Push token 通過 APNs 進行身份驗證,如 Establishing a Token-Based Connection to APNs 中所述。

瞭解推送的更多資訊,歡迎檢視筆者的另一篇文章 樸實 Push 普識——瞭解 Push Notifications 全貌

更新 App 程式碼並建立 Push Notification Server

在我們的 Xcode 專案中,首先將推送通知功能新增到 App 中,如使用 APNs 註冊我們的應用程式中所述,但不要使用 registerForRemoteNotifications() 為遠端推送通知註冊實時活動。請使用 ActivityKit 獲取 Push token 。

要使用推送通知更新或結束實時活動:

  1. 如果我們尚未實現遠端推送通知伺服器,請建立一個使用 APNs 傳送遠端推送通知的伺服器應用程式。
  2. 在我們的 App 中啟動實時活動,並確保將 pushType 引數傳遞給 request(attributes:contentState:pushType:) 函式。
  3. 成功啟動實時活動後,我們會收到一個帶有 pushToken 的 Activity 物件。將其傳送到我們的推送通知伺服器,並使用它來發送更新或結束實時活動的遠端推送通知。
  4. 使用你儲存在伺服器上的 pushToken 向實時活動傳送推送通知。我們必須設定 content-state key 以匹配自定義的 Activity.ContentState 型別,以確保系統可以解碼 JSON 有效負載並更新實時活動。
  5. 將我們傳送給 APNs 的請求的 apns-push-type 標頭欄位的值設定為 liveactivity
  6. 使用以下格式設定我們傳送到 APNs 的請求的 apns-topic 標頭欄位:<your bundleID>.push-type.liveactivity
  7. 要更新實時活動,請將有效負載的 event key 的值設定為 update。要結束實時活動,請將其設定為 end。如果我們結束實時活動,請包含最終 content-sate,以確保實時活動在結束後顯示最新資料。
  8. 使用 pushTokenUpdates 觀察 Push token 的更改,將任何新推送令牌傳送到我們的伺服器,並使伺服器上的舊令牌無效。
  9. 當我們的實時活動結束時,使伺服器上的 Push token 無效。

要在模擬器中測試實時活動的遠端推送通知,請使用配備 Apple T2 安全晶片的 Mac 或配備執行 macOS 13 或更高版本的 Apple 晶片的 Mac

以下有效負載會更新比薩配送的司機姓名和配送時間。content-state 的內容必須與我們在 ActivityAttributes 實現中宣告的自定義 Activity.ContentState 型別的屬性相匹配。

在以下示例中,content-state 與使用實時活動顯示實時資料中的示例中的自定義 PizzaDeliveryStatus 型別的屬性相匹配。 此外,示例有效負載包括一個 alert,讓使用者知道更新的實時活動內容:

JSON { "aps": { "timestamp": 1168364460, "event": "update", "content-state": { "driverName": "Anne Johnson", "estimatedDeliveryTime": 1659416400 }, "alert": { "title": "Delivery Update", "body": "Your pizza order will arrive soon.", "sound": "example.aiff" } } }

請注意, sound 不是必須提供的。上面的示例為保持簡單,沒有使用本地化字的符串作為 alert title 和 body。在我們 App 的警報實現中,請考慮本地化這兩個字串。有關顯示遠端通知警報的更多資訊,請參閱 Generating a remote notification

需要注意的是,使用者的裝置可能不會收到遠端推送通知,例如使用者沒有網路連線。同樣,如果推送通知在實時活動結束後到達,系統也會忽略它。這兩種情況都可能導致實時活動顯示過時的資訊。為了幫助減少顯示過時資訊的機會,除了使用遠端推送通知之外,還可以從 App 更新我們的實時活動。

該系統允許每小時有一定的通知預算,以允許頻繁更新——例如現場體育比賽。但是,如果我們超出預算,系統可能會限制推送通知。為避免超出每小時通知預算,我們可以傳送不計入預算的低優先順序推送通知。

一個語音通話的 Demo

如果你對以下 Demo 感興趣,同樣可以參考這裡

2.gif | 1.gif | 3.gif| | :------------: | :------: | :------: | | 語音通話 App 靈動島 | 多靈動島 | 鎖屏 |