1.證書配置:http://blog.csdn.net/songchunmin_/article/details/51316806
2.編碼階段:http://blog.csdn.net/songchunmin_/article/details/51291752
3.官方文檔:https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Today.html
4.國外有篇比較好的文檔:https://www.appcoda.com/app-extension-programming-today/
5.widget幾個常用功能的實現:http://www.lxweimin.com/p/9b3d06236d19
術語:
主項目:往已有的A項目里添加widget,那么A是主項目
Start~
環境搭建
1.蘋果后臺配置證書,下載Provisioning Profiles文件,得到4個Provisioning Profiles文件(主項目和widget分別有兩個:dev的和release的)。
注意widget項目和主項目其實是兩個獨立的appID,而通過app group來相互交互。
2.對于主項目的配置:
(1)選擇新的pro證書
(2)打開targets-->Capabilities-->App Groups 選項,然后選擇后臺配置的group:
3.添加widget項目。
將Bundle
Identifier改為和蘋果后臺配置的appid一樣。并且注意當時命名的時候,必須遵循如下規則:前綴要包括主項目的Bundle
Identifier。后綴不能是widget關鍵字(。。。這里很坑,試了很多次)。然后因為我的XCode并沒有配置開發者賬號,所以將Automatically
manage singing的對勾去掉(反正證書文件都下載給你了)。
然后選擇好Provisioning Profile文件,dev和release的。當前頁的error就都應該消失了。
將Build Setting里的簽名設置好:
如果當時在蘋果后臺配置證書的時候,deviceid都加上了你的手機,那么直接真機運行,就可以在手機上看到一個“Hello World”的widget。
第一次運行,可能看到左上角名字是WIDGET,這個有點延遲,第二次就可以看到是“寶寶樹小時光”。
業務邏輯實現
1.純代碼實現布局:
通過以上步驟,添加的widget項目,使用的是Storyboard作為布局。使用純代碼需要:
(1)刪除Storyboard文件,widget項目的plist,刪除NSExtensionMainStoryboard
(2)添加NSExtensionPrincipalClass字段 并設為TodayViewController
2.TodayViewController生命周期:
經過測試:基本上超過2秒,widget元素在屏幕上消失(手機沒有停留在widget頁面或者停留在widget頁面,沒有滑動到自己項目的widget),widget再次出現的時候,都會重新調用ViewDidLoad。而短時間的消失再出現,不會執行ViewDidLoad而是執行ViewWillAppear。所以如果不進行處理,每次widget出現,就請求數據的話,即時兩次數據相同也會閃一下(知乎),想不閃可以把數據緩存,每次widget出現都加在緩存數據,并且發送請求,請求回的數據和當前展示數據做對比,如果一樣,則不用刷新列表,如果不一樣則刷新列表。然后緩存的機制,因為一般請求的數據都是json,可以解析成dic對象,使用plist進行緩存,比較方便。
3.展開/折疊,在這個系統方法中設置展開、折疊的高度即可,但是好像不能主動設置在什么情況下是展開的,在什么情況下的折疊的,也就是說不能代碼控制展開還是折疊,只能是用戶點擊按鈕來展開和折疊,蘋果沒有給出那么多的自由。
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
if(activeDisplayMode == NCWidgetDisplayModeCompact) {
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 105);
}else{
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 495);
}
}
4.使用主項目的自定義類,以及第三方庫:
widget開發時,肯定會遇到想使用主項目的類的情況,只需要將要使用的類的.m文件,多加上一個target,選擇widget項目即可。
使用第三方庫,我的項目里暫時沒有遇到這種需求,但是通過pods維護第三方庫的時候,Podfile文件會指定每個第三方庫要加入的target,那么在widget的target中,加上要使用的第三方庫,應該就可以,可以自己試試。
5.數據共享
widget項目必然經常要和主項目共享數據,可以通過NSUserDefault,注意和平時用有些不同,創建UserDefault的時候,要指定groupid。上代碼:
// widget項目里取數據
+ (NSString*)widgetStringForKey:(NSString*)defaultName {
NSUserDefaults*shared = [[NSUserDefaultsalloc] initWithSuiteName:@"group.com.appname"];
return[shared stringForKey:defaultName];
}
// 主項目里存數據
+ (void)widgetSetObject:(id)value forKey:(NSString*)defaultName {
NSUserDefaults*shared = [[NSUserDefaultsalloc] initWithSuiteName:@"group.com.appname"];
[shared setObject:value forKey:defaultName];
[shared synchronize];
}
還有通過NSFileManager數據共享的方式,項目里沒有用到,可以自己查閱資料試一試。
6.喚起主項目
通過URL Schemes的方式:
(1)在主項目里配置一個對應widget的URL。
(2)在需要喚起主項目的地方:
NSString*schemeString = [NSStringstringWithFormat:@"HMWidget://jumptab?number=%@",[NSStringencodeBase64String:@"3"]];
[self.extensionContext openURL:[NSURLURLWithString:schemeString] completionHandler:^(BOOLsuccess) {
}];
(3)在主項目appdelegate的代理方法中,截取URL,做處理:
- (BOOL)application:(UIApplication *)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation
{
if([url.description hasPrefix:@"HMWidget://"]) {
if(![HMUser isLogin]) {
returnYES;
}
return[HMSchemeUrlRouter handleScheme:url.description withAppDelegate:self];
}
}