「Apple Watch 應用開發系列」複雜功能實踐
在本節,我們將實現一個簡單的複雜功能。
新建專案
新建專案,RandomAddress,字如其名,我們後續可能將在 App 中獲取隨機地址:
Complication DataSource
在專案中新增檔案 ComplicationController.swift
:
```Swift import ClockKit
class ComplicationController: NSObject, CLKComplicationDataSource { func currentTimelineEntry( for complication: CLKComplication ) async -> CLKComplicationTimelineEntry? { return nil } } ```
在 Xcode 14 之前的版本,當我們建立一個 watchOS 專案時,Xcode 會自動生成 ComplicationController.swift
。而 Xcode14 後,我們需要手動進行建立。如果用的是 Xcode 14 之前的版本,我們會發現在上述程式碼中,刪除了除 CLKComplicationDataSource
所需的一種方法之外的所有內容。大多數樣板程式碼都是不必要的,這裡為了簡單起見,我們不進行保留。
當前 Timeline entry
當 watchOS 想要更新複雜功能顯示的資料時,它會呼叫 currentTimelineEntry(for:)
。我們應該立即返回要顯示的資料或返回 nil。
如果我們無法提供當前時間的資料,watchOS 將在 App 的擴充套件程式的 Assets.xcassets 中查詢。如果你使用 Xcode 14 前的版本,可能已經注意到 Assets.xcassets 中有一個之前沒有使用過的 Complication 資料夾。
當 currentTimelineEntry(for:)
返回 nil 時,watchOS 將使用Assets.xcassets 中存在的適當命名的影象。
模擬器的預設錶盤是 Meridian,它使用 .graphicCircular
複雜功能系列來處理大多數可配置的複雜功能。對於你第一次嘗試在 App 中支援複雜功能,請將方法主體替換為:
Swift
func currentTimelineEntry(
for complication: CLKComplication
) async -> CLKComplicationTimelineEntry? {
guard complication.family == .circularSmall else {
return nil
}
let template = CLKComplicationTemplateGraphicCircularStackText(
line1TextProvider: .init(format: "line1"),
line2TextProvider: .init(format: "line2"))
return .init(date: Date(), complicationTemplate: template)
}
在上述程式碼中:
-
如果是不支援的複雜功能系列型別,則返回 nil。
-
建立適當型別的複雜功能模板並配置要顯示的文字。
-
返回一個
CLKComplicationTimelineEntry
,它指定資料的時間和要顯示的模板。指定的日期不應該是將來,但它可以是過去。
在第二步中,請注意模板採用裡兩個文字。每個模板使用不同的文字或圖形元素,請查閱各種模板的文件以確定哪些適合我們的。
配置複雜功能
如果你使用的是 Xcode 14 及以上版本,需要手動配置 Scheme。我們新建一個 Schema,並修改對應的名字:
接著編輯我們的 Scheme,將 Interface 修改為 Completion:
開啟專案的 Info,新增 ClockKit Complication - Principal Class
Key,並將 Value 設定為 $(PRODUCT\_MODULE\_NAME).ComplicationController
:
展示覆雜功能
將 Scheme 換到 Complication,然後再次構建並執行。使用該 Scheme 可確保在不使用快取的情況下使用 App 支援的系列。該 Scheme 還將模擬器直接啟動到錶盤,併為我們的 App 提供少量後臺處理時間。
找一個支援我們剛剛複雜功能程式碼的錶盤,長按錶盤,編輯器將出現,向左滑動兩次,以便選擇要替換的複雜功能:
點按我們希望替換的圓形複雜功能,檢視可以選擇的複雜功能列表:
滾動直到我們看到我們自己的 App,選擇我們剛剛建立的閃亮的新複雜功能。可實際上沒有看到我們的 App。 不好了! 什麼地方出了錯?
CLKComplicationDataSource
有一個名為 complicationDescriptors()
的可選方法。 這其實不是“可選的”。 如果我們不提供該方法,我們將不會看到列出的複雜功能。
早起版本的 watchOS 在 Info.plist 中查詢支援的複雜功能。這就是為什麼該方法是可選的。 根據 Apple 的建議,不要再使用 Info.plist。
在 ComplicationController.swift 裡,繼續新增以下程式碼:
Swift
func complicationDescriptors() async -> [CLKComplicationDescriptor] {
return [
.init(identifier: "layer.practice.RandomAddress",
displayName: "RandomAddress",
supportedFamilies: [.graphicCircular])
]
}
在上述程式碼中:
-
我們提供一個
CLKComplicationDescriptor
項陣列作為此方法的返回值。 每個CLKComplicationDescriptor
都出現在可供選擇的複雜功能列表中。 -
我們支援的每個複雜功能功能都應該有一個唯一的名稱。 確保名稱是確定性的,並且不會在應用程式啟動之間發生變化。
-
displayName 是使用者從應用支援的列表中選擇複雜功能時看到的內容。
-
複雜功能提供了他們支援的系列。
再次構建並執行。 這一次,當我們滾動複雜功能時,會看到我們的複雜功能被列為可供選擇的選項。
圈子裡的“--”是怎麼回事? 為什麼它沒有顯示我們在時間軸中指定的訊息?
樣本資料
currentTimelineEntry 既不會顯示在此列表中,也不會顯示在 iPhone 上的 Watch App 中。 當詢問 currentTimelineEntry 時,我們的 App 可能必須執行昂貴的操作或非同步執行某些東西。使用這裡的資料作為樣本資料是不妥的。
我們將使用一組樣本資料來使顯示,並避免執行其他程式碼,帶來 App 中的潛在副作用。 CLKComplicationDataSource
提供了另一個可選的——名為 localizableSampleTemplate(for:)
的方法,當 Apple Watch 需要在列表中顯示覆雜選擇器時 watchOS 呼叫該方法。
繼續新增以下程式碼:
Swift
func localizableSampleTemplate(
for complication: CLKComplication
) async -> CLKComplicationTemplate? {
guard complication.family == .graphicCircular,
let image = UIImage(systemName: "xmark")?.withTintColor(.white)
else { return nil }
return CLKComplicationTemplateGraphicCircularStackImage(
line1ImageProvider: .init(fullColorImage: image),
line2TextProvider: .init(format: "hhh"))
}
在該方法中:
-
它確係列是我們支援的系列。 同時確保你可以載入預設影象以顯示在複雜功能預覽中。
-
提供
CLKComplicationTemplateGraphicCircularStackImage
作為樣本資料。
再次構建並執行。 這一次,當我們嘗試選擇複雜功能時,我們會看到更好的顯示。接著擊該行以選擇複雜功能,然後返回 Apple Watch 的主螢幕。我們卻沒有看到我們的複雜功能顯示:
更新複雜功能的資料
Apple Watch 只會在我們指定新資料可用時嘗試更新錶盤上的複雜功能。 想象一下,如果 watchOS 必須每秒查詢 App 的複雜功能以檢視是否有新資料點可用,會消耗多少電量?
告訴 watchOS 有新資料
新增檔案 Model.swift,他將獲取隨機的城市:
```Swift import SwiftUI
struct Address: Decodable { var city: String }
class Model { static let shared = Model() var address: Address?
func getAddress() async {
let (data, _) = try! await URLSession.shared.data(from: URL(string: "https://random-data-api.com/api/v2/addresses")!)
address = try! JSONDecoder().decode(Address.self, from: data)
print(address!.city)
}
} ```
可以嘗試在 ContentView 展示時,拉取該資訊:
Swift
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
.task {
await Model.shared.getAddress()
}
}
}
切換 Scheme 執行專案,我們會看到控制檯有城市資訊輸出:
回到 Model.swift:
Swift
import ClockKit
修改 Model:
```Swift class Model { static let shared = Model() var address: Address?
func getAddress() async {
let (data, _) = try! await URLSession.shared.data(from: URL(string: "https://random-data-api.com/api/v2/addresses")!)
address = try! JSONDecoder().decode(Address.self, from: data)
print(address!.city)
DispatchQueue.main.async {
let server = CLKComplicationServer.sharedInstance()
server.activeComplications?.forEach {
server.reloadTimeline(for: $0)
}
}
}
} ```
一旦我們獲取到資料,我們告訴 watchOS 它需要重新載入時間線以處理當前錶盤上的任何複雜功能。
根據我們的 App 及其資料模型,重新載入整個時間線可能不是最有效的選擇。如果我們的複雜功能的時間線中的現有資料仍然有效,並且我們只是新增新資料,則應改為呼叫 extendTimeline(for:)
。
注意:如果我們已經超出了應用程式的預算的執行時間,那麼對任一方法的呼叫都不會執行任何操作。
為複雜功能提供資料
切換回 ComplicationController.swift 並將 currentTimelineEntry(for:) 的主體替換為:
Swift
func currentTimelineEntry(
for complication: CLKComplication
) async -> CLKComplicationTimelineEntry? {
guard complication.family == .graphicCircular,
let address = Model.shared.address else {
return nil
}
let template = CLKComplicationTemplateGraphicCircularStackImage(
line1ImageProvider: .init(fullColorImage: UIImage(systemName: "xmark")!.withTintColor(.white)),
line2TextProvider: .init(format: address.city))
return .init(date: Date(), complicationTemplate: template)
}
在上述程式碼中,我們獲取了 Model 單例裡的 Address 並進行更新。
再次構建並執行。 請稍等片刻,從網路下載資料,然後切換回錶盤。 我們將看到現在顯示的真實資料:
支援多個系列
雖然我們現在擁有一個支援複雜功能的 App,但它的功能非常有限。為了讓使用者使用我們的複雜功能,他們必須使用支援 .graphicCircular
的錶盤。每當我們為 Apple Watch 設計複雜功能時,我們都應該努力支援各種型別的系列。
回想一下,當我們在 complicationDescriptors()
中生成CLKComplicationDescriptor
時,我們為 supportedFamilies
引數指定了一個系列。雖然我們需敲幾下鍵就可以新增其餘型別,甚至只需指定 CLKComplicationFamily.allCases
,單我們仍然必須處理每個不同的模板型別。
我們在網上看到的大多數資源都告訴我們只需在每種方法中針對系列建立一個 switch 語句來確定要採取的操作。雖然我們可以這樣做,但控制器將變得非常臃腫並且難以維護。有一種常見的設計模式,稱為工廠方法,在這裡,它的效果很好,歡迎嘗試實現。
連結
-
你可以在這裡獲得文章專案:https://github.com/LLLLLayer/Apple-Watch-App-Development-Series
-
使用 https://random-data-api.com/ 提供的 Fake 資料。
- 深入淺出RPC服務 | 不同層的網路協議
- 淺析三款大規模分散式檔案系統架構設計
- 動轉靜兩大升級!一鍵轉靜成功率領先,重點模型訓練提速18%
- 文心ERNIE 3.0 Tiny新升級!端側壓縮部署“小” “快” “靈”!
- [Android禪修之路] 解讀Layer
- 35張圖,直觀理解Stable Diffusion
- Combine | (V) Combine 中的錯誤處理和 Scheduler
- Combine | (III) Combine Operator:時間操作|序列
- 「Apple Watch 應用開發系列」複雜功能(Complication)基礎
- 「Apple Watch 應用開發系列」複雜功能實踐
- 在 iOS 上實現使用者主動觸發的 App Icon 切換
- view和layer知識點整理
- 實時活動(Live Activity) - 在鎖定螢幕和靈動島上顯示應用程式的實時資料
- 「Apple Watch 應用開發系列」Dock 快照
- Layer 1新方向 Meta系公鏈Aptos、Sui 、Linera盤點
- Synapse Chain:多鏈世界的Layer0跨鏈基礎設施
- V神:什麼樣的layer 3才有意義?
- 以太坊升級將如何影響Layer2的發展?
- Vitalik:什麼樣的 Layer3 才有意義
- 從系統架構分析安全問題及應對措施