簡介
在通知中心的Today的視圖中顯示的 extension 叫做 widget ,widget 可以方便用戶快速的得到想要的信息,不用再通過復雜的步驟打開app才能找到自己想要的東西,只要下拉就可方便的看到,iOS設備中即使在鎖屏的狀態下也能查看。例如,用戶下拉滑到Today查看現在的股票信息、天氣、日程等等,如今越來越多的用戶會頻繁的通過Today來快速的得到信息。
Today Widget
在創建widget的時候應該注意:
1、確保每次查看時都要更新,顯示最新的內容
2、交互流程合理
3、注意性能問題,確保使用流暢
由于用戶在使用Today widget是快速短暫的,所以在設計widget的時候應注意UI簡潔,限制顯示的內容,只把用戶最感興趣的內容顯示出來。
在不同的平臺上的widget有所不同:
iOS:widget不支持鍵盤輸入,用戶需要在 containing app 中去設置對應的 widget 要顯示的內容。例如,天氣的 widget 要是用戶想添加要顯示的城市天氣,那就需要打開天氣應用,在城市列表中添加要顯示的城市。
macOS:當 Today widget 運行時,用戶可以去修改配置改變要顯示的內容。例如天氣widget可以添加想要顯示的城市天氣。
當用戶安裝了帶有Today widget 的應用,可以在通知中心Today的視圖內添加該應用的widget,可以在編輯widget的頁面添加、刪除、修改widget。
創建Today Extension
這里在iOS上創建個Today Extension的小例子用來顯示主應用里面的內容。
注意本例子是在 iOS10以上的系統上創建,之前的系統要另行適配。
新建一個項目,在項目里添加target,選擇Today Extension:
- Xcode的Today模板會生成默認的實現文件,在Today的Info.plist文件中默認的NSExtension值如下:
<key>NSExtension</key>
<dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widget-extension</string>
</dict>
如不想使用系統默認的 storyboard 可以刪除NSExtensionMainStoryboard 鍵值對,添加自己的實現文件 ,NSExtensionPrincipalClass 對應的是自定義的view controller名。
NSExtensionPointIdentifier對應的值是Extension的反向DNS名稱。
創建好之后運行項目,在通知中心中編輯widget添加到剛創建的widget就可看到,如下圖:
圖標就是你的應用圖標,標題默認的創建的Today名稱,可以在Info.plist中修改CFBundleDisplayName顯示的標題的內容。
然后就可以在TodayViewController中添加想要顯示的內容了。常見的widget,在右上角帶有展開/折疊按鈕,這個在TodayViewController中設置好屬性就可以實現
//支持展開、折疊
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
Apple 為擴展提供了一個
NSExtensionContext
類來與host app應用進行交互。用戶在host app中啟動擴展后,host app提供一個上下文給擴展,里面最主要的是包含了 inputItems 這樣的待處理的數據。
當點擊展開或者折疊時會調用下面的方法,從而去改變widget的高度,默認的為展開的widget的高度是固定的110。
Xcode的TodayExtension的模板中生成的TodayViewController中默認的遵循NCWidgetProviding協議,協議里有下面幾個方法:
-
widgetPerformUpdateWithCompletionHandler
在TodayViewController中實現這個方法后,當用戶操作顯示通知中心Today視圖,就會調用這個方法只要以顯示就會調用,從而可以及時的去更新狀態。即使你的擴展現在不可見 (也就是用戶沒有拉開通知中心),系統也會時不時地調用實現了的這個方法,來要求擴展刷新界面。在這個方法中我們一般可以做一些像 API 請求之類的事情,在獲取到了數據并更新了界面,或者是失敗后都使用提供的 completionHandler 來向系統進行報告。 -
widgetActiveDisplayModeDidChange
當widget的展開或折疊的狀態改變時就會調用該方法,在這個方法里可以通過改變self.preferredContentSize
的值改變widget的高度,從而適應不同的狀態。展開折疊是在iOS10以后才加入的功能,所以這個方法只有在iOS10之后才有。
Extension和Containing App間共享代碼
在App里創建的某個類,要想在Extension中也能使用,在Target Membership中勾選就可以了。同樣的要想在Extension中使用App Assets.xcassets里面的圖片,勾選就可以了。
Extension和Containing App間共享數據
沙盒限制了我們在設備上隨意讀取和寫入。但是從iOS8開始有了新的功能,App Groups 為同一個 vender 的應用或者擴展定義了一組域,在這個域中同一個 group 可以共享一些資源。對于我們的例子來說,我們只需要使用同一個 group 下的 NSUserDefaults 就能在主體應用不活躍時向其中存儲數據,然后在擴展初始化時從同一處進行讀取就行了。
對于應用和其對應的擴展來說,可以使用 App Groups 來進行數據共享。
首先我們需要開啟 App Groups。選擇containing app 的 target,打開它的 Capabilities ,找到 App Groups 并打開開關,然后添加一個 group 名字。接下來你還需要為 TodayExtension 這個 target 進行同樣的配置,只不過不再需要新建 group,而是勾選剛才創建的 group 就行。
然后在App中保存要在widget中顯示的數據:
NSUserDefaults * groupDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.myzTodayGroup"];
[groupDefault setObject:dataArray forKey:@"myzTodayDataArray"];
在TodayExtension中就可以獲取到數據,從而顯示在widget中:
NSUserDefaults * groupDefault = [[NSUserDefaults alloc] initWithSuiteName:@"group.myzTodayGroup"];
NSArray * dataArray = [groupDefault objectForKey:@"myzTodayDataArray"];
從TodayExtension啟動應用
接著來處理在widget點擊內容后,然后跳轉到 containing app 查看詳細信息。也就是從 widget 啟動 containing app ,還要向 app 傳遞數據??梢允褂孟到y提供的 NSExtensionContext 類,通過NSExtensionContext 來調用 openURL(URL:completionHandler:)
啟動 containing app :
[self.extensionContext openURL:[NSURL URLWithString:[NSString stringWithFormat:@"myzwidget://open]] completionHandler:nil];
然后選擇containing app 的 target,設置對應的 URL Scheme:
然后在 AppDelegate 中獲取打開的事件,做出相應的反應:
- (BOOL)application:(UIApplication *)application openURL:(nonnull NSURL *)url options:(nonnull NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([[url scheme] isEqualToString:@"myzwidget"]) {
//...
return YES;
}
return NO;
}
小例子實現了在widget中顯示應用中前三行的內容,效果如下:
Demo地址:https://github.com/MA806P/MYZAppExtension
Reference
App Extension Programming Guide - Today
https://onevcat.com/2014/08/notification-today-widget/