[閱讀難度:簡單]
準備工作
技術儲備:
需對Xcode、Swift免都、SwiftUI 有一定了解
環(huán)境要求:
iOS >= 16.1
Xcode >= 14.1
設備(iPhone14Pro/iPhone14ProMax,模擬器也可以)
靈動島簡介
一句話總結(jié):“靈動島是展示在鎖屏界面與主屏幕狀態(tài)欄的“特殊”小組件”
靈動島的狀態(tài):
默認狀態(tài):可展示左右兩個小視圖(
LeadingSide
脓规、TrailingSide
)
展開狀態(tài):用戶長按靈動島的展開狀態(tài)(Leading
侨舆、Center
挨下、Trailing
脐湾、Bottom
)沥割,
緊湊狀態(tài):當有多個APP靈動島時展示分離狀態(tài),依創(chuàng)建順序展示(MinimalDetached
)
靈動島的生命周期:
靈動島主要分為
Start
机杜、Update
椒拗、End
三種狀態(tài)获黔,可由ActivityKit
與遠程推送
控制其狀態(tài)玷氏。
一個靈動島默認展示8
小時盏触,結(jié)束后可繼續(xù)在鎖屏界面存在4
個小時后由系統(tǒng)徹底移除块饺,也可由開發(fā)者自行移除。
那么辨嗽,我們開始吧·····
接入Widget Extension
info.plist 文件中添加
NSSupportsLiveActivities
字段糟需,設置為YES
-
選中項目添加target谷朝,創(chuàng)建
Widget Extension
,假設將其命名為“DynamicDemo”:
target.png 在Widget中诅诱,主要關注
WidgetsExtensionBundle
,該結(jié)構(gòu)體下的body返回所有小組件的Widget驶沼,Xcode 會默認生成小組件卡片的初始代碼争群,不需要的話可以移除。
@main
struct WidgetsExtensionBundle: WidgetBundle {
var body: some Widget {
// DynamicDemo() //原本的小組件卡片Widget
DynamicDemoLiveActivity() // 靈動島Widget
}
}
Tips:模擬器下偶現(xiàn)APP動態(tài)小組件權限被關閉玉雾,從“設置-對應APP-實時活動”打開即可
2. 配置靈動島Widget
在Xcode14.1
及之后的版本引入 WidgetsExtension
后复旬,會默認創(chuàng)建XXXXXXLiveActivity.swift
文件:
struct DynamicDemoAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here!
var value: Int
}
// Fixed non-changing properties about your activity go here!
var name: String
}
struct DynamicDemoLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DynamicDemoAttributes.self) { context in
// Lock screen/banner UI goes here
VStack {
Text("Hello")
}
.activityBackgroundTint(Color.cyan)
.activitySystemActionForegroundColor(Color.black)
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom")
// more content
}
} compactLeading: {
Text("L")
} compactTrailing: {
Text("T")
} minimal: {
Text("Min")
}
.widgetURL(URL(string: "http://www.apple.com"))
.keylineTint(Color.red)
}
}
}
DynamicDemoLiveActivity
: 靈動島的Widget驹碍,通過ActivityConfiguration
來配置所有狀態(tài)下的視圖,包括不支持靈動島設備的設備在鎖屏頁面的視圖怔球。
DynamicDemoAttributes
: 靈動島屬性屬性結(jié)構(gòu)體浮还,用于數(shù)據(jù)更新,由對應的Activity使用担汤。
3. 控制靈動島
在需要控制靈動島的地方漫试,引入ActivityKit
碘赖。針對“Start”、“Update”播掷、“End”三種狀態(tài)分別舉個簡單的例子:
開啟:
Task{
// 配置Attributes
let data = DynamicDemoAttributes(value: 100)
let state = DynamicDemoAttributes.ContentState()
// 根據(jù) Attributes 開啟一個靈動島
do {
// reqeust 指定 pushType 為 .token 可獲取遠程推送所需的 token
try await MainActor.run {
let activity = try Activity<DynamicDemoAttributes>.request(attributes: data, contentState: state)
}
} catch (let error) {
print(error.localizedDescription)
}
}
更新:
do {
let data = DynamicDemoAttributes(value:100)
let state = DynamicDemoAttributes.ContentState(progressMsg: "更新的文案")
if let activity = Activity<DynamicDemoAttributes>.activities.first {
await activity.update(using: state)
} else {
try Activity<DynamicDemoAttributes>.request(attributes: data, contentState: state)
}
} catch (let error) {
print(error.localizedDescription)
}
代碼中取的 activities.first
歧匈,實際開發(fā)中應該根據(jù)數(shù)據(jù)來區(qū)分并獲取需要更新的靈動島實例件炉。
通過獲取當前需要更新的靈動島實例斟冕,通過調(diào)用update方法傳入最新的Attributes更新缅阳。
Tips: 靈動島可由宿主APP與遠程推送更新,普通APP會在退到后臺后10s內(nèi)被掛起秀撇,例如音樂向族、健身、定位虏等、通話等可常駐后臺的應用可直接通過宿主APP及時更新。對于退出APP后需要更新的場景候引,可由遠程推送實現(xiàn)敦跌。具體可參考文末。
結(jié)束:
//移除所有
Activity<DynamicDemoAttributes>.activities.forEach { item in
Task{
// await item.end() //默認結(jié)束后麸俘,會在鎖屏界面等待4小時徹底移除
await item.end(dismissalPolicy:.immediate) // 立即結(jié)束
}
}
Tips: 指定
.immediate
可以立即移除
Q&A
Q1:如何識別用戶點擊的視圖:
當前的靈動島不支持非系統(tǒng)自定義交互(截止20221121)从媚,支持DeepLink
患整、Link
跳轉(zhuǎn)。點擊靈動島默認跳轉(zhuǎn)我們的宿主APP紧憾,我們可以在靈動島視圖中配置widgetURL()
昌渤、Link()
來達到識別用戶點擊的效果、以Link()
舉例:
···············
DynamicIslandExpandedRegion(.bottom) {
Link(destination: URL(string: "xxxxxx://hotel/contact")!) {
Label("Contact Hotel", systemImage: "phone").padding()
}.background(Color.accentColor)
.clipShape(RoundedRectangle(cornerRadius: 15))
}
···············
上面代碼可以在用戶點擊了靈動島的bottom視圖跳轉(zhuǎn)進APP時在 AppDelegate
或 SceneDelegate
方法中識別其所帶入的 url
般眉,以此來做對應處理煤篙。
Q2:多個靈動島展示的優(yōu)先級
- 單個APP的多個靈動島:默認視圖(依次展示)
- 多個APP的靈動島:緊湊視圖/分離視圖(依次展示)
Q3:如何在模擬器模擬遠程推送更新
常規(guī)的推送可查看《iOS 在模擬器上測試遠程推送》。在調(diào)研靈動島的過程中苛茂,需要模擬器測試遠程推送的功能。查閱一圈資料后成功實現(xiàn)胯究,具體步驟如下:
環(huán)境要求:
Xcode >= 14.1
裕循,
MacOS >= 13.0
準備工作
如何獲取遠程推送的token:
let activity = try
// 指定pushType為.token
Activity<OrderProgressWidgetAttributes>.request(attributes: data, contentState: state, pushType: .token)
orderProgressActivity = activity
Task {
for await data in activity.pushTokenUpdates {
let myToken = data.map {String(format: "%02x", $0)}.joined()
print(myToken);
// 將token告知后端用于遠程推送
}
}
運行CommandLine
配置所需要使用的宏:
$ export TEAM_ID={TEAM ID}
$ export TOKEN_KEY_FILE_NAME={Token Key file path}
$ export AUTH_KEY_ID={your Auth Key ID}
$ export DEVICE_TOKEN={myToken from the activity push token} 設備Token(靈動島是Activity start后返回的pushtoken)
$ export APNS_HOST_NAME=api.sandbox.push.apple.com
說明:
TEAM_ID:開發(fā)者TeamID(開發(fā)者賬戶可查)
TOKEN_KEY_FILE_NAME:開發(fā)者賬戶生成的Keys下載后的文件路徑(.p8格式)
AUTH_KEY_ID:開發(fā)者賬戶生成的keys的ID(開發(fā)者賬號內(nèi)可查、下載的.p8文件默認后綴前的十個字符)
DEVICE_TOKEN:設備Token(靈動島場景下是Activity指定pushType為.token后start返回的pushtoken)
配置命令行推送(命令行逐行運行硅则,注意中英字符):
$ export JWT_ISSUE_TIME=$(date +%s)
$ export JWT_HEADER=$(printf '{ "alg": "ES256", "kid": "%s" }' "${AUTH_KEY_ID}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
$ export JWT_CLAIMS=$(printf '{ "iss": "%s", "iat": %d }' "${TEAM_ID}" "${JWT_ISSUE_TIME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
$ export JWT_HEADER_CLAIMS="${JWT_HEADER}.${JWT_CLAIMS}"
$ export JWT_SIGNED_HEADER_CLAIMS=$(printf "${JWT_HEADER_CLAIMS}" | openssl dgst -binary -sha256 -sign "${TOKEN_KEY_FILE_NAME}" | openssl base64 -e -A | tr -- '+/' '-_' | tr -d =)
$ export AUTHENTICATION_TOKEN="${JWT_HEADER}.${JWT_CLAIMS}.${JWT_SIGNED_HEADER_CLAIMS}"
發(fā)送推送:
curl -v \
--header "apns-topic:po.test.push-type.liveactivity" \
--header "apns-push-type:liveactivity" \
--header "authorization:bearer $AUTHENTICATION_TOKEN" \
--data \
'{"Simulator Target Bundle": "po.test",
"aps": {
"timestamp":1668764100,
"event": "update",
"content-state": {
"progressMsg": "酒店訂單已確認",
}
}}' \
--http2 \
https://${APNS_HOST_NAME}/3/device/$DEVICE_TOKEN
apns-topic
:固定為{BundleId}.push-type.liveactivity
apns-push-type
:固定為liveactivity
Simulator Target Bundle
:測試模擬器推送怎虫,設置為對應應用的{BundleId}
timestamp
:刷新時間戳大审。需設置正確徒扶,否則靈動島的推送不會生效
event
:可填入update根穷、end,對應靈動島的更新與結(jié)束溶浴。
content-state
:對應靈動島的Attributes
參考資料
本文只是簡單介紹了基礎功能的實現(xiàn)管引,更豐富的探索可查閱官方文檔:
https://developer.apple.com/documentation/activitykit/displaying-live-data-with-live-activities
https://developer.apple.com/documentation/widgetkit/dynamicisland/
https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications
https://developer.apple.com/design/human-interface-guidelines/components/system-experiences/live-activities
https://developer.apple.com/news/?id=mis6swzt