講述之前首先看下demo效果圖:
然后再展示幾個效果不錯的 Widget app
一、Widget總覽
- Widget 是 iOS8 推出第一版,在iOS 10 進行大幅度的優化
- Widget可以讓用戶更快地訪問到其感興趣的內容,官方的說法是用來呈現功能比較簡單的,交互性不強的東西,在不打擾或者中斷用戶使用當前應用的前提下完成自己的功能點.對于這個說法,國內的開發者表示呵呵,因為幾乎所有的 Widget都綁定了對應的點擊事件
二、Widget代碼實現
-
因為 Widget 屬于單獨的進程,因此需要再新建一個target:File -> New ->target
初次構建 UI 時,運行 Widget 后會發現,Widget左側距離屏幕左側始終有一段距離,導致效果不佳,可以通過下面的代理方法消除間距
// 取消widget默認的inset,讓應用靠左
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets {
return UIEdgeInsetsZero;
}
- Widget 的收起、展開 則是通過這個代理方法:
/**
activeDisplayMode有以下兩種
NCWidgetDisplayModeCompact, // 收起模式
NCWidgetDisplayModeExpanded, // 展開模式
*/
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
if(activeDisplayMode == NCWidgetDisplayModeCompact) {
// 尺寸只設置高度即可,因為寬度是固定的,設置了也不會有效果
self.preferredContentSize = CGSizeMake(0, 110);
} else {
self.preferredContentSize = CGSizeMake(0, 310);
}
}
-
在設置 UI 的過程中,若想使用本體 Target 中的類:
在對應類的 Target Membership 勾選 Widget 即可 -
如果想使用Pod 管理的第三方庫,那么只需要以下三步就可以愉快地玩耍了(比如我想使用 Masonry 布局)
1、 在podfile文件中
2、 按照如圖所示配置configurations
3、 最后分別配置兩個 Target 的 link Binanry
當然有些第三方包含 source 文件的可能還需要別的操作,最簡單粗暴的方式就是-->拖進去!
-
使用圖片也是必不可少,然而 imageNamed: 和 imageWithContentsOfFile: 兩種方式加載都不行,即使設置了文件的 target 為 Widget Extension,后來在其target 內部建立一個 .xcassets 文件即可加載圖片
-
然而在 Widget Extension 里面新建類又出現了如下報錯
- 造成這個的原因是新建的時候默認是 C header,而且沒有指向對應的target,按照下圖所示修改一下type,選一下target,再次編譯就木有問題了
- 如果需要網絡請求,記住在 Extension 的plist文件中添加App Transport Security Settings 屬性
- 在開發過程中,那么怎么一直有個“Hello World”顯示,最后看了一下原來是 Storyboard 加載,去 Storyboard 文件刪除對應 label 即可
-
如果你的項目中要求純代碼
- 刪除 Storyboard 文件和plist 對應鍵值對
-
添加 NSExtensionPrincipalClass 字段并設置為 TodayViewController
三、與 App 本體交互
與本體 app 進行交互之前,要明白的一個概念是:Widget 與 app 本身 是兩個target,appId 也是獨立的,因此 Widget 與本體 app 是通過 app group 進行交互
1、設置群組關系
在 本體 App 的 target > Capabilities添加 container 標識符
- 報錯信息:[_NCWidgetExtensionContext openURL:completionHandler:]_block_invoke failed: Error Domain=NSOSStatusErrorDomain Code=-50 "(null) 如果報這個錯說明 urlScheme有問題,沒有標準對應,比如下劃線識別等
2、設置 scheme 進行交互
-
設置 app 的 scheme 標識符
在plist 文件內添加以下鍵值對 然后!就可以在 Widget 對應的點擊事件里面
// 掃一掃按鈕的點擊事件
- (void)scanBtnTapped:(UIButton *)sender {
[self.extensionContext openURL:[NSURL URLWithString:@"wpfWidgetTest://action=richScan"] completionHandler:^(BOOL success) {
NSLog(@"scanBtnTapped open url result:%d",success);
}];
}
- 在 app 本體的 AppDelegate 方法里面
// 處理 Widget 相關事件
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
NSString* prefix = @"wpfWidgetTest://action=";
NSString *urlString = [url absoluteString];
if ([urlString rangeOfString:prefix].location != NSNotFound) {
NSString *action = [urlString substringFromIndex:prefix.length];
if ([action isEqualToString:@"richScan"]) {
// 進入到掃一掃頁面
[self.rootVC transferToRichScanVC];
} else if ([action isEqualToString:@"web"]) {
// 進入到 web 活動頁
[self.rootVC transferToWebVCWithUrlString:@"webTest"];
}
}
return YES;
}
- 數據共享:widget項目必然經常要和主項目共享數據,可以通過NSUserDefault,注意和平時用有些不同,創建UserDefault的時候,要指定groupid。上代碼:
// widget項目里取數據
+ (NSString*)widgetStringForKey:(NSString*)defaultName {
NSUserDefaults*shared = [[NSUserDefaultsalloc] initWithSuiteName:@"group.com.widgetTest"];
return[shared stringForKey:defaultName];
}
// 主項目里存數據
+ (void)widgetSetObject:(id)value forKey:(NSString*)defaultName {
NSUserDefaults*shared = [[NSUserDefaultsalloc] initWithSuiteName:@"group.com.widgetTest"];
[shared setObject:value forKey:defaultName];
[shared synchronize];
}
#warning 涉及到大量數據交互也可以使用 NSFileManager 進行數據共享
在demo中,實現了從Widget入口 點擊未讀消息后,下次不再展示該未讀消息項
四、關于刷新時機
- Widget 自身的更新機制,是進入到 Widget 頁面后(iOS 10 左滑,之前是下拉),先執行 viewDidLoad 方法,然后是 viewWillAppear 方法,但是經測驗,Widget 頁面在屏幕消失超過兩秒后(手機沒有停留在 Widget 頁面 或者 停留在別的app 的Widget頁面,自己的沒顯示)
- 由于以上特性,更新代碼最好寫在 viewWillAppear 方法里面,對于更新時效性特別強的,比如天氣類 app,這種最好就是 在該方法里面添加一個 NSTimer 定時進行刷新,在 viewWillDisAppear 方法中 進行 取消NSTimer invalidate定時更新即可
- 知乎、得到 app的 Widget,只要走 viewDidLoad 方法就會閃一下(如下圖),因為每次Widget加載請求的數據后會進行替換造成的。這里可以做個緩存優化,判斷如果請求來的數據和當前數據內容一致,那么就不進行刷新列表操作
不信你看
五、關于 iOS8 適配
- iOS8、9是老式的下拉刷新,并沒有折疊和展開功能,默認的Widget高度為self.preferredContentSize設置的高度
- iOS8 默認的背景是黑色磨砂效果,iOS10默認的背景色是白色磨砂效果。因此在控件顏色上做下適配
- iOS8下所有組件默認右移30pt
六、其他注意點
當程序內存不足時,蘋果優先會殺死擴展,因此需要注意內存的管理。
在配置team是賬號需要一致(免費賬號不行,需要付費的賬號),上傳包的時候一定注意選擇 Product -> Archive -> ** 選擇 distribution 模式!**
-
3D touch 對應的也有Widget!?答案是 YES!,只要設置了3D touch,Widget的第一欄就會自動顯示。但是如果有多個widget的話,還需要在 info.plist 指定相應的main target!
Extension 證書配置指南
官網說明
一直很心儀的app --> Things 關于widget的介紹
幾個精致的 Widget app
在模擬器上進行3D touch 測試
再次附上 demo Github 地址,歡迎star