一、什么是Widget組件?
widget是一個(gè)依附于App的小組件顺少,可以視作一個(gè)獨(dú)立的小App王浴,但又與主App關(guān)聯(lián),在很多情況下可方便人們使用氓辣。例:天氣、支付寶筛婉、今日頭條等的小組件。
二、Widget的創(chuàng)建
1响蓉、相關(guān)證書配置
① App的證書配置:和普通證書配置一樣,可參考iOS開發(fā)·證書配置
② Widget的證書配置:
1枫甲、創(chuàng)建App Group扼褪,信息填寫完畢后一路continue下去粱栖;
注意:AppGroup的ID需要以group開頭
示例:group.xxx.xxx.xxx
2话浇、App的App IDs開啟App Group權(quán)限,并編輯綁定闹究;
3幔崖、創(chuàng)建Widget的App IDs,并且綁定同一個(gè)App Group渣淤;
注意點(diǎn):
① Widget的Bundle ID需要在App的Bundle ID的基礎(chǔ)上添加赏寇;
例:App的Bundle ID是com.FanXG.Basic;
Widget的Bundle ID是com.FanXG.Basic.xxx价认;
② 在創(chuàng)建Widget的App IDs時(shí)候嗅定,勾選App Group,當(dāng)然也可以創(chuàng)建后再勾選用踩;
③ 創(chuàng)建完成后渠退,和App的App IDs開啟App Group權(quán)限步驟一樣,綁定App Group脐彩;
4碎乃、分別創(chuàng)建APP、Widget的證書配置文件
5丁屎、下載開發(fā)證書荠锭、發(fā)布證書、配置證書等晨川,在此不加說明证九;
========================證書配置完成========================
2、創(chuàng)建widget工程
widget是一個(gè)依附于app的小組件共虑,若想創(chuàng)建某個(gè)項(xiàng)目的widget愧怜,需新建Target
創(chuàng)建完畢之后,多出MyTarget目錄
配置項(xiàng)目證書
分別開啟項(xiàng)目App Group權(quán)限妈拌,Widget的權(quán)限開啟也是一樣拥坛;
點(diǎn)擊MyTarget->Capabilities->App Groups
3、修改布局方式
widget默認(rèn)為xib布局尘分,需要代碼布局猜惋,則需要進(jìn)行以下修改:
① 刪除MainInterface.storyboard文件
② 修改widget的info.plist文件
· 刪除NSExtensionMainStoryboard字段
· 添加NSExtensionPrincipalClass字段著摔,value是你所寫的controller的名稱定续,默認(rèn)TodayViewController
注意:
widget雖然和項(xiàng)目是一個(gè)整體的工程,但是文件和圖片是不互通的恩掷,若有一張圖片在app路徑下面黄娘,你在widget使用是讀取不到的寸宏。所以如果有什么布局想復(fù)用氮凝,需要在widget的文件下也復(fù)制一份罩阵。
4稿壁、小部件的布局
在TodayViewController中傅是,通用的組件都可以使用蕾羊,但是tableview等一系列滾動(dòng)視圖是無法滾動(dòng)的龟再,所以使用tableview布局要注意高度利凑,是不能滾動(dòng)的。
iOS10之后哀澈,Widget支持展開及折疊兩種展現(xiàn)方式割按,通過設(shè)置widgetLargestAvailableDisplayMode屬性可以讓W(xué)idget程序?qū)崿F(xiàn)展開布局哲虾。
在左滑到widget顯示的時(shí)候,會(huì)調(diào)用viewWillAppear晒旅,這時(shí)候可以去刷新獲取最新數(shù)據(jù)废恋。
展開扒寄、縮回代碼示例:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
}
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
if (activeDisplayMode == 0) {
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 105);
}else {
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 220);
}
}
三迄本、App和Widget的傳值
1嘉赎、Widget跳轉(zhuǎn)到App
① 配置跳轉(zhuǎn)的URL
② 代碼示例
TodayViewController.m文件
- (void)createButton {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:CGRectMake(10, 10, 160, 50)];
[button setTitle:@"測(cè)試示例" forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(clickButtonAction) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)clickButtonAction {
NSString *urlString = [NSString stringWithFormat:@"WidgetMain://"];
NSURL *url = [NSURL URLWithString:urlString];
[self.extensionContext openURL:url completionHandler:^(BOOL success) {
}];
}
AppDelegate.m文件
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([url.description hasPrefix:@"WidgetMain"]) {
//處理widget的傳值
}
return YES;
}
2、NSUserDefault傳值(不推薦)
NSUserDefaults寫入代碼:
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.FanXG.Basic"];
[userDefaults setObject:@"測(cè)試舉例" forKey:@"widget"];
[userDefaults synchronize];
NSUserDefaults讀取代碼:
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.FanXG.Basic"];
NSString *contentStr = [userDefaults objectForKey:@"widget"];
NSLog(@"contentStr == %@",contentStr);
注意:
利用NSUserDefaults傳值在iOS10之后可能會(huì)報(bào)錯(cuò)靶橱,報(bào)錯(cuò)內(nèi)容:
Couldn't read values in CFPrefsPlistSource<0x2815c6d80> (Domain: group.com.FanXG.Basic, User: kCFPreferencesAnyUser, ByHost: Yes, Container: (null), Contents Need Refresh: Yes): Using kCFPreferencesAnyUser with a container is only allowed for System Containers, detaching from cfprefsd
這是因?yàn)樯澈袡C(jī)制关霸,拓展應(yīng)用不允許訪問宿主應(yīng)用的沙盒路徑谒拴,因此上述用法失效英上,所以我們需要配合appGroup完成實(shí)例化UserDefaults苍日;
3窗声、NSFileManager傳值(推薦)
App寫入示例代碼:
- (void)updateWidgetData {
//獲取到共享數(shù)據(jù)的文件地址
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.FanXG.Basic"];
NSURL *birthdayContainerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/birthday.json"];
//將需要存儲(chǔ)的數(shù)據(jù)寫入到該文件中
NSString *jsonString = @"需要寫入的數(shù)據(jù)或者json字符等";
//寫入數(shù)據(jù)
NSError *err = nil;
BOOL result = [jsonString writeToURL:birthdayContainerURL atomically:YES encoding:NSUTF8StringEncoding error:&err];
if (!result) {
NSLog(@"%@",err);
}else {
NSLog(@"save value:%@ success.",jsonString);
}
}
Widget讀取數(shù)據(jù)示例代碼:
- (NSString *)readDataByNSFileManager {
NSError *err = nil;
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.FanXG.Basic"];
containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/birthday.json"];
NSString *value = [NSString stringWithContentsOfURL:containerURL encoding: NSUTF8StringEncoding error:&err];
return value;
}
四拦耐、注意事項(xiàng)
- 主App和widget分別獨(dú)立,因此需要兩套證書扫俺,再通過App Group相互綁定狼纬;
- widget的Bundle ID需要再App的Bundle ID的基礎(chǔ)上添加疗琉;
舉例:
The App Bundle ID is ---- com.FanXG.Basic
The widget Bundle ID is ---- com.FanXG.Basic.OurTarget
- 建議將widget的版本號(hào)和構(gòu)建版本號(hào)設(shè)置的和App一致盈简,不然提交到App Store的時(shí)候會(huì)出現(xiàn)警告送火,雖然不影響提交審核和正常使用种吸,但還是設(shè)置成一樣的吧坚俗!