初探 Widget Extension in iOS 14
Widget為一 Extension,使用SwiftUI建立。使用者一天平均會按 90 次 Home 鍵,但只會花很短的時間在 Home Screen 上,因此 UI 需快速呈現,而非一直出現載入中的圖示影響體驗。
另外 widget 特性為只能讓使用者讀取,無法有 scroll 或 switch 等 UI 互動,意即不能使用 List,也無法播放動態圖片或影片,只能接受點擊行為。
設計特點
- Glanceable:可讓使用者一下子就清楚得到資訊
- Relevant:資訊與本身 App 有關聯
- Personalted:與使用者相關的,個人化設定可吸引使用者加入 widget
支援尺寸(Families)
函式架構
預設顯示畫面 (PlaceHolderUI / snapshot)
顯示時機:系統環境改變時,例如系統設定時區改變,或者在工具列選擇 widgets 時。
應避免使用數據,而是使用灰影方塊等來表示會字串或圖片顯示區域
更新時機 (Reload)
- Networking:Background Notification 呼叫時
- Timeline:Developers 給予時間軸去自動更新
- App-based:當使用者進入 App 做改變時
時間軸 (Timeline)
使用時間軸告知下次 reload 時間,iOS 系統會依照設定背景下載。
當需要更新時,iOS將會訪問 TimelineProvider,並依據 TimelineProvider 所提供的 TimelineEntry 去執行,每個 TimelineEntry 物件將會含有 date and time 以及顯示 widget UI 所需的額外資訊。
Policy:
- atEnd:當 TimelineProvider 提供 TimelineEntry 為陣列的最後一個時,系統將會要求新的 Timeline。
- after(Date):當時間到期時抓取新的 Timeline。
- never:不再抓取,直到 WidgetCenter 要求 reload。
Networking & App-based
當使用者進入 App 並產生會改變 Widget 狀態的行為時,通知 WidgetCenter 更新 timeline 與 widget 介面。
若 App 支援複數的 widgets 時,可將支援的 widgets 全部更新
當 widget 有提供使用者設定,根據設定不一定要更新時,可以先檢查設定再去 reloadTimelines
顯示動態時間
因為 widget 畫面並非隨時更新,但如果要顯示倒數或計時,使用 “Text“ view,讓 widget 顯示正確的時間在螢幕上。
使用者點選 (Deep Linking)
使用者點選 Widget 時,導入至 App 互動顯示更多資訊。iOS 將會傳送一 URL,啟動 App 後根據 URL 做處理
使用者設定 (Configuration)
可提供選項讓使用者客製化要顯示在 widget 上的資訊
- Static Configuration:預設值,不提供使用者設定
- Intent Configuration:提供 SiriKit Intent Definition File
當新增 setting 在實機測試時,無法叫出使用者選單或沒有正確顯示更新,可將 Widget 從桌面移除再重新加入。
多個 Widgets 堆疊 (Smart Stacks)
可讓使用者將不同 widgets 堆疊在一起,由 iOS 系統根據時間且使用者習慣及相關性決定最上顯示的 widget。
單個 App 支援複數 Widgets
Main App 共享資料
當不同 App 間或 App 與其 Extension 需要共享資料時,可開 AppGroup(注意必須在同一個開發者帳號下),並有以下三種方式可共享資料。(AppGroup建立)
當在實機測試時,記得至 TARGET -> Capabilities -> App Group 設定 shared container。
*UserDefaults:
let wantToGet = UserDefaults(suiteName: <your_app_group>)!.string(forKey: "keyName")
*File Container:
With the AppGroup entitlement you get access to the shared File Container
存入
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "yourapp.contents")?.appendingPathComponent("widget")
let data = Data("Widget Title".utf8)
try! data.write(to: url!)
讀取
let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "yourapp.contents")?.appendingPathComponent("widget")
let data = try! Data(contentsOf: url!)
let string = String(data: data, encoding: .utf8)!
print(string) // 印出 "Widget Title"
*CoreData:
Core Data with SwiftUI Tutorial
let storeURL = containerURL.appendingPathComponent("DataModel.sqlite")
let description = NSPersistentStoreDescription(url: storeURL)
let container = NSPersistentContainer(name: "DataModel")
container.persistentStoreDescriptions = [description]
container.loadPersistentStores { ... }
圖片異步下載
SwiftUI 畫畫面的時候為 synchronous,所以無法在顯示畫面時執行異步任務,例如下載圖片。故使用第三方套件如 SDWebImageSwiftUI、Kingfisher 等皆無法正常運作。
若要在 Widget 顯示網頁下載的圖片,有兩個方式:
- 在製作 TimelineProvider 時,預先把圖片載好,直接給予畫面 Image。