前言
iOS最近幾年新特性
iOS14 | 影片畫中畫 | AppLibrary | 桌面小元件 | 照片隱私加強 | 應用限免 | 智慧摺疊 | 全新siri懸浮顯示 |
---|---|---|---|---|---|---|---|
iOS15 | FaceTime支援螢幕共享 | 資訊和新增擬我表情 | 推出專注模式 | 通知重新設計,圖示變得更大 | 地圖公共交通路線置頂,增加時間顯示 | 識別圖片上文字資訊 | 支援照片資訊和照片上的文字進行搜尋 |
iOS16 | iOS 16 鎖定介面 | 鎖定介面小元件 | 鎖屏介面的實時活動 | iPhone鎖定全螢幕音樂播放器 | 電池百分比出來啦 | 影片實況文字 | 快速查詢Wi-Fi密碼 |
iOS17 | 設定您的待機螢幕 | 優先考慮互動式小部件 | 定製您的聯絡海報 | 建立您自己的貼紙 | 設定新的 Safari 配置檔案 | 開啟反追蹤 | 分享您的 iCloud 鑰匙串密碼 |
一、簡介
實時活動(Live Activity),是iOS16新增的擴充套件元件功能,可以在靈動島和鎖定螢幕上顯示應用程式的實時資料。用於追蹤事件和任務進度實時活動的開始和結束都是離散的,具體畫面場景如下:蘋果
蘋果在 iPhone 14 Pro 及 iPhone 14 Pro MAX 上推出了靈動島。靈動島將 iPhone 前置鏡頭和軟體通知結合在一起的全新設計,用出色的互動設計掩蓋硬體的缺陷,是一次互動玩法的革新。靈動島可以透過點按、長按、輕掃來進行互動,最多支援兩個應用同時“登島”。
靈動島全稱 Dynamic Island,作為 iOS 中實時活動(Live Activities)功能的一部分,用來展示需要實時更新的訊息。例如外賣配送資訊,地圖實時導航資訊等。靈動島有 3 種展現形式。
1.1 展現形式
1.1.1 緊湊(Compact)
當系統只有 1 個實時活動的內容時,靈動島預設使用緊湊模式。緊湊模式下UI由頭部(Leading side)和尾部(Trailing side)組成,如圖所示。使用者可以點選靈動島開啟 App 檢視實時活動的內容
1.1.2 最小化(Minimal)
當系統有多個實時活動的內容時,靈動島自動切換使用最小化模式。最小化模式下由附著的頭部(Leading(attached))和分割開的尾部(Trailing(detached))組成,如圖所示。和緊湊模式一樣,最小化模式也支援使用者點選開啟 App。
1.1.3擴充套件(Expanded)
當使用者在緊湊或最小化模式輕掃或長按靈動島時,靈動島可以切換成擴充套件模式。用於向使用者展示更多資訊。擴充套件模式的 UI
設計儘量保持和緊湊模式一致,使用者從緊湊模式切換到擴充套件模式會有一個平滑的體驗。
當我們向 App Store 提交了適配靈動島的 App 版本時,以上 3 種模式都需要適配。
二、場景限制
2.1樣式限制
1、實時活動針對鎖定螢幕和靈動島提供了不同的檢視。鎖定螢幕可以出現在所有支援 iOS 16 的裝置上。而靈動島在支援裝置上,使用以下檢視顯示實時活動:緊湊前檢視、緊湊尾檢視、最小檢視和擴充套件檢視。
2、當使用者觸控靈動島,且靈動島中有緊湊或最小檢視,同時實時活動更新時,會出現擴充套件檢視。在不支援靈動島的裝置上,擴充套件檢視顯示為實時活動更新的橫幅。
3、為確保系統可以在每個位置顯示 App 的實時活動,開發者必須支援所有檢視。
建議:同場景多卡片由於樣式趨同且摺疊,不建議同時建立多卡片
靈動島頁面需要實現的部分有4個:
a、不支援靈動島的機型 或 鎖屏時的 顯示
b、緊湊級展示(即左右貼合靈動島的展示)
c、多Live activity時的展示(即極小檢視,左貼合,右分離)
d、擴充套件檢視(長按靈動島時觸發)
備註:還有一個App同時存在的實時活動皮膚最多隻能建立5個,這也是一個場景約束條件。Error requesting delivery Live Activity The operation couldn’t be completed. Maximum number of activities for target already exists
2.2 時間限制
實時活動最多可以保持八小時的活動狀態,除非其應用程式或人員在此限制之前結束活動。超過八小時限制後,系統自動結束直播活動,並立即將其移出動態島。但是,實時活動會保留在鎖定螢幕上,直到有人將其刪除,或者在系統將其刪除之前最多再保留四個小時(以先到者為準)。因此,實時活動在鎖定螢幕上保留最多 12 小時。
官方表述:https://developer.apple.com/documentation/activitykit/displaying-live-data-with-live-activities
A Live Activity can be active for up to eight hours unless your app or a person ends it before this limit. After the 8-hour limit, the system automatically ends it. When a Live Activity ends, the system immediately removes it from the Dynamic Island. However, the Live Activity remains on the Lock Screen until a person removes it or for up to four additional hours before the system removes it — whichever comes first. As a result, a Live Activity remains on the Lock Screen for a maximum of twelve hours.
2.3 資料更新
每個實時活動執行在自己的沙盒中,與小元件不同的是,它無法訪問網路或接收位置更新。若要更新實時活動的動態資料,少量(不能超過4KB)資料可透過遠端推送通知傳送,或透過ActivityKit 框架後臺活動重新整理資料。
ActivityKit 更新和 ActivityKit 推送通知的更新動態資料大小不能超過 4 KB。
2.4 網路限制
a、卡片本身禁止定位以及網路請求,資料重新整理依賴本地重新整理,實施活動推送重新整理,同2)所述;
b、Live Activity內部禁用網路圖片,傳統的服務端傳圖片URL的方式無法滿足實際使用,但是希望傳入訂單圖片來個性化地表達並且區分不同訂單。
iOS 16 beta版建立時可以透過將圖片轉為Data格式傳入卡片,但是iOS16.1該方案僅限傳入4KB左右的圖片(API限制),因此暫時不考慮非本地圖片方案,採用內建圖片方式實現。
2.5 埋點限制
場景情況:由於預設情況點選是回主程式,而並不是固定頁面,因此有必要自定義widgetUrl(如用於回到訂單頁面),也可以透過Link實現分割槽域的跳轉,Link和widgetUrl共存時,點選Link區域會響應Link,因此兩者同時使用即可。
無法在widget內部直接新增埋點,並且靈動島收起時,僅支援新增同一個widgetUrl,對於收起狀態新增Link並沒有響應。
埋點方式:因為點選直接跳轉到主App,因此考慮將埋點引數加入URL引數即可,主App解析時埋點。但是無法記錄包括使用者檢視、使用者關閉(關閉卡片 繼續傳送推送也沒有報錯 因此無法判斷)等行為的埋點。
對於靈動島的區分,實際測試發現,在展開模式下,可以加入Link並且可以正常響應,這與官方文件中的描述一致。
三、適配
3.1 UI適配
1、尺寸:
目前只有 iPhone 14 Pro 及 iPhone 14 Pro MAX 具有靈動島功能。在兩種機型上,靈動島的圓角半徑都為 44Points,這個數值和前置深感攝像頭的半徑是一樣的。按照前述的 3 種模式,靈動島的具體引數如下表格所示(表格涉及的數值表示Points)。
機型 | 螢幕尺寸 | 緊湊模式(頭部) | 緊湊模式(尾部) | 最小化模式 | 展開模式 |
---|---|---|---|---|---|
iPhone 14 Pro | 393*852 | 52.33*36.67 | 52.33*36.67 | 36.67*36.67 | 371*(84-160) |
iPhone 14 Pro Max | 430*932 | 62.33*36.67 | 62.33*36.67 | 36.67*36.67 | 408*(84-160) |
2、顏色
開發者無法更改靈動島的背景顏色,只能更改文字顏色、素材顏色、靈動島邊框顏色等。UI 適配需要考慮系統的深色模式,必要情況可以使用兩套 UI。
3.2開發適配
3.2.1開發框架簡介
蘋果在 iOS 16.1 正式對外開放了靈動島適配框架ActivityKit
,第三方 App 可以使用這些ActivityKit
完成靈動島適配工作。注意ActivityKit
的 API 目前僅適用於 iPhone。靈動島使用WidgetKit
和SwiftUI
完成 UI 開發工作,ActivityKit
在其中扮演建立Activity
,請求資料,更新資料,結束Activity
的角色。
3.2.2許可權管理
靈動島作為實時活動的一部分,需要實時活動許可權才能正常展示。和通知許可權,相機許可權等類似,實時活動許可權需要 App
3.2.3 生命週期
Request
Update
Observe avtivity state
End
import ActivityKit
struct AdventureAttributes: ActivityAttributes {
//不可變
let hero: EmojiRanger
/// 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.
struct ContentState: Codable & Hashable {
let currentHealthLevel: Double
let eventDescription: String
}
}
let adventure = AdventureAttributes(hero: hero)
let initialState = AdventureAttributes.ContentState(
currentHealthLevel: hero.healthLevel,
eventDescription: "Adventure has begun!"
)
let content = ActivityContent(state: initialState, staleDate: nil, relevanceScore: 0.0)
let activity = try Activity.request(
attributes: adventure,
content: content,
pushType: nil
)
let heroName = activity.attributes.hero.name
let contentState = AdventureAttributes.ContentState(
currentHealthLevel: hero.healthLevel,
eventDescription: "\(heroName) has taken a critical hit!"
)
var alertConfig = AlertConfiguration(
title: "\(heroName) has taken a critical hit!",
body: "Open the app and use a potion to heal \(heroName)",
sound: .default
)
activity.update(
ActivityContent<AdventureAttributes.ContentState>(
state: contentState,
staleDate: nil
),
alertConfiguration: alertConfig
)
// Observe activity state asynchronously
func observeActivity(activity: Activity<AdventureAttributes>) {
Task {
for await activityState in activity.activityStateUpdates {
if activityState == .dismissed {
self.cleanUpDismissedActivity()
}
}
}
}
// Observe activity state synchronously
let activityState = activity.activityState
if activityState == .dismissed {
self.cleanUpDismissedActivity()
}
let hero = activity.attributes.hero
let finalContent = AdventureAttributes.ContentState(
currentHealthLevel: hero.healthLevel,
eventDescription: "Adventure over! \(hero.name) has defeated the boss! Congrats!"
)
let dismissalPolicy: ActivityUIDismissalPolicy = .default
activity.end(
ActivityContent(state: finalContent, staleDate: nil),
dismissalPolicy: dismissalPolicy)
}
3.2.4UI
import WidgetKit
import SwiftUI
@main
struct EmojiRangersWidgetBundle: WidgetBundle {
var body: some Widget {
EmojiRangerWidget()
LeaderboardWidget()
AdventureActivityConfiguration()
}
}
struct AdventureActivityConfiguration: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: AdventureAttributes.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.
// Leading region
// Expanded region
// Bottom region
} compactLeading: {
// Create the compact leading view.
// ...
} compactTrailing: {
// Create the compact trailing view.
// ...
} minimal: {
// Create the minimal view.
// ...
}
}
}
}
四、遠端通知更新資料
實時活動也支援遠端推送更新,根據文件以下9點要求實現(avtivity遠端推送每小時有通知預算(數量未明確),超出後系統將關閉通知)
1、確保啟動activity時[request(attributes:contentState:pushType:)
傳入pushType引數(.token);
2、獲取啟動後的activity的推送令牌pushToken,傳給服務端用來推送更新activity;(實時活動的pushToken不是訊息通知的token,這個是獨立出來的)
3、服務端推送的更新內容欄位需要和ActivityAttributes的ContentState
中定義的動態資料欄位對應;
4、設定推送的報頭apns-push-type的值為liveactivity;
5、設定推送的報頭apns-topic的值為.push-type.liveactivity;
6、正確的推送對應的內容和狀態;
7、使用pushTokenUpdates
監聽pushToken變化,如有變化,就令牌失效,需要將新的令牌傳給伺服器;
8、當Activity結束時,伺服器端的pushToken將失效;
{
"aps": {
"timestamp": 1685952000,
"event": "update",
"content-state": {
"currentHealthLevel": 0.0,
"eventDescription": "Power Panda has been knocked down!"
},
"alert": {
"title": "Power Panda is knocked down!",
"body": "Use a potion to heal Power Panda!",
"sound": "default"
}
}
}
注意:
1、不用為推送提供聲音 , 如果推送延遲,在activity結束後收到時將被忽略,avtivity每小時有通知預算(數量未明確),超出後系統將關閉通知;
2、實時活動的pushToken不是訊息通知的token,這個token上報到JDPush服務,需要單獨管理和歸類。
備註:
-
靈動島的實時資訊要有明確的開始和結束時間點
-
當一個實時資訊持續超過 8 小時,系統會從靈動島移除這個 App 的資訊
-
當一個實時活動結束時,靈動島上的展示資訊也會立即被系統移除
-
避免在靈動島上顯示廣告,畢竟引起使用者反感可以被直接關閉
-
App 要能夠響應靈動島的點選資訊,跳轉到 App 中的正確子頁面,而不是停留在 App 的首頁
運用場景
1、需在螢幕駐留的文字、影像為主的資訊:如地圖導航、airdrop 傳輸情況等;
2、後臺進行的音訊類:如接電話、放音樂、錄音、倒數計時等;
3、即時互動反饋:如充電、靜音、人臉識別等。超過這三類資訊後,桌面可能會變得雜亂無章
參考文章
https://developer.apple.com/videos/play/wwdc2023/10194
https://developer.apple.com/videos/play/wwdc2023/10184
https://www.jianshu.com/p/f410eba6c392
https://www.bilibili.com/read/cv18549307/
作者:京東零售 李豔敏
來源:京東雲開發者社群 轉載請註明來源