iOS 靈動島Dynamic Island實現(xiàn)+模擬器測試遠程推送

靈動島展示.jpeg

[閱讀難度:簡單]

準備工作

技術儲備:

需對Xcode、Swift免都、SwiftUI 有一定了解

環(huán)境要求:

iOS >= 16.1
Xcode >= 14.1
設備(iPhone14Pro/iPhone14ProMax,模擬器也可以)

靈動島簡介

一句話總結(jié):“靈動島是展示在鎖屏界面與主屏幕狀態(tài)欄的“特殊”小組件

靈動島的狀態(tài)

靈動島不同狀態(tài).png

默認狀態(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

  1. info.plist 文件中添加 NSSupportsLiveActivities 字段糟需,設置為YES

  2. 選中項目添加target谷朝,創(chuàng)建Widget Extension,假設將其命名為“DynamicDemo”:

    target.png

  3. 在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時在 AppDelegateSceneDelegate 方法中識別其所帶入的 url 般眉,以此來做對應處理煤篙。

Q2:多個靈動島展示的優(yōu)先級

  1. 單個APP的多個靈動島:默認視圖(依次展示)
  2. 多個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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末褥伴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饥臂,更是在濱河造成了極大的恐慌似踱,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囚戚,死亡現(xiàn)場離奇詭異驰坊,居然都是意外死亡拳芙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門分飞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浆竭,“玉大人,你說我怎么就攤上這事删窒∷衬遥” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵诚亚,是天一觀的道長站宗。 經(jīng)常有香客問我益愈,道長,這世上最難降的妖魔是什么敏释? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任钥顽,我火速辦了婚禮靠汁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘县爬。我一直安慰自己添谊,他們只是感情好斩狱,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泌枪,像睡著了一般秕岛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上修壕,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天慈鸠,我揣著相機與錄音灌具,去河邊找鬼。 笑死督笆,一個胖子當著我的面吹牛诱贿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咸作,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼记罚,長吁一口氣:“原來是場噩夢啊……” “哼壳嚎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起说庭,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刊驴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舅柜,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡致份,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年氮块,在試婚紗的時候發(fā)現(xiàn)自己被綠了诡宗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡锰提,死狀恐怖芳悲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谅年,我是刑警寧澤肮韧,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布弄企,位于F島的核電站,受9級特大地震影響拘领,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜届良,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一士葫、第九天 我趴在偏房一處隱蔽的房頂上張望送悔。 院中可真熱鬧爪模,春花似錦呻右、人聲如沸鞋喇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至散吵,卻和暖如春蟆肆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炎功。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛇损,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓股囊,卻偏偏與公主長得像稚疹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贫堰,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容