自iOS8之后因悲,蘋果支持了擴(kuò)展(Extension)的開發(fā),開發(fā)者可以通過系統(tǒng)提供給我們的擴(kuò)展接入點(diǎn) (Extension point) 來為系統(tǒng)特定的服務(wù)提供某些附加的功能讯检。今年iOS10的推出卫旱,讓W(xué)idget擴(kuò)展應(yīng)用漸漸的火了起來,地位得到重大的提升顾翼,從這也可以看出蘋果對(duì)他的重視,今天我們就來一起學(xué)習(xí)下Widget灸芳,來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的擴(kuò)展程序。
程序效果
創(chuàng)建Widget程序
- 創(chuàng)建工程拜姿,在工程中添加擴(kuò)展程序
- 創(chuàng)建成功后的目錄
順便說一句砾隅,擴(kuò)展程序雖然是程序的擴(kuò)展,但是這兩個(gè)應(yīng)用其實(shí)是“獨(dú)立”的究反。準(zhǔn)確的來說,它們是兩個(gè)獨(dú)立的進(jìn)程精耐,默認(rèn)情況下互相不應(yīng)該知道對(duì)方的存在琅锻。擴(kuò)展需要對(duì)宿主 app (host app,即調(diào)用該擴(kuò)展的 app) 的請(qǐng)求做出響應(yīng)恼蓬,當(dāng)然,通過進(jìn)行配置和一些手段小槐,我們可以在擴(kuò)展中訪問和共享一些容器 app 的資源,這個(gè)我們稍后再說凿跳。
Widget布局方式
- 使用Interface Builder
工程默認(rèn)的方式就是使用Interface Builder,如果實(shí)現(xiàn)簡(jiǎn)單的布局的話可以考慮這種方式控嗜。 - 使用代碼進(jìn)行布局
當(dāng)涉及到比較復(fù)雜的UI布局的時(shí)候,可以考慮使用這種布局方式曾掂,按大家平時(shí)的習(xí)慣來壁顶。這里需要注意一下,如果需要使用代碼布局的話需要修改一下plist文件博助。
首先將原有NSExtensionMainStoryboard
字段刪除,添加字段NSExtensionPrincipalClass
蛔糯,value是你所寫的controller的名稱窖式,一般默認(rèn)的都是TodayViewController
實(shí)現(xiàn)相應(yīng)的方法
1. 設(shè)置Widget的size
iOS10之后,Widget支持展開及折疊兩種展現(xiàn)方式萝喘,通過設(shè)置widgetLargestAvailableDisplayMode
屬性可以讓W(xué)idget程序?qū)崿F(xiàn)展開布局。如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
if (isIOS10)
{
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
}
self.preferredContentSize = CGSizeMake(kWidgetWidth, 110);
}
2. 重寫切換展開及折疊布局時(shí)的方法(iOS10之后)
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize
{
NSLog(@"maxWidth %f maxHeight %f",maxSize.width,maxSize.height);
if (activeDisplayMode == NCWidgetDisplayModeCompact)
{
self.preferredContentSize = CGSizeMake(maxSize.width, 110);
}
else
{
self.preferredContentSize = CGSizeMake(maxSize.width, 200);
}
}
3. iOS10之前爬早,視圖原點(diǎn)默認(rèn)存在一個(gè)間距启妹,可以實(shí)現(xiàn)以下方法來調(diào)整視圖間距
注:
該方法在iOS10之后被遺棄,iOS10默認(rèn)不存在間距饶米。
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
return UIEdgeInsetsMake(0, 10, 0, 10);
}
應(yīng)用喚醒
本來想叫應(yīng)用間跳轉(zhuǎn)的,想想還是這個(gè)名字比較高大上些??
如下照瘾,配置url scheme丧慈,這個(gè)定義的時(shí)候盡量不要和其他用用沖突,筆者定義的為WidgetDemo
。這樣,通過訪問WidgetDemo://
就可以實(shí)現(xiàn)應(yīng)用喚醒了笑旺。代碼如下:
- (void)redButtonPressed:(UIButton *)button
{
NSLog(@"%s",__func__);
NSURL *url = [NSURL URLWithString:@"WidgetDemo://red"];
[self.extensionContext openURL:url completionHandler:^(BOOL success) {
NSLog(@"isSuccessed %d",success);
}];
}
相應(yīng)的,在AppDelegate中實(shí)現(xiàn)以下方法关噪,這里可以處理傳過來的action乌妙,對(duì)于傳過來不同的值可以進(jìn)行不同的操作,這里我們打印了請(qǐng)求url的內(nèi)容藤韵。
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
if ([[url absoluteString] hasPrefix:@"WidgetDemo"])
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:[NSString stringWithFormat:@"你點(diǎn)擊了%@按鈕",[url host]] delegate:nil cancelButtonTitle:@"好的??" otherButtonTitles:nil, nil];
[alert show];
}
return YES;
}
- 簡(jiǎn)易的應(yīng)用快速啟動(dòng)器
既然說到了應(yīng)用喚醒,這里再稍稍拓展以下欲险,想必大家都有用過類似launcher這種的應(yīng)用快速啟動(dòng)器匹涮。其實(shí)就是運(yùn)用了應(yīng)用間跳轉(zhuǎn)的原理天试,每款應(yīng)用都有自定義的url scheme然低,我們只要知道他們的url scheme就可以跳轉(zhuǎn)至改款應(yīng)用,例如進(jìn)行微信的跳轉(zhuǎn):
- (void)wechatLoginButtonPressed
{
NSLog(@"%s",__func__);
NSURL *url = [NSURL URLWithString:@"wechat://"];
[self.extensionContext openURL:url completionHandler:^(BOOL success) {
NSLog(@"isSuccessed %d",success);
}];
}
以下是我們比較常用的軟件的url scheme带兜,有興趣的同學(xué)們可以試一試:
QQ mqq:// 微信 weixin:// 淘寶taobao:// 微博 sinaweibo:// 支付寶alipay://
數(shù)據(jù)共享
擴(kuò)展程序一般都不是脫離宿主程序單獨(dú)運(yùn)行的吨灭,難免需要和宿主程序進(jìn)行數(shù)據(jù)交互。而相對(duì)于一般的APP沃于,數(shù)據(jù)可以用單例,NSUserDefault等等檩互。但由于拓展與宿主應(yīng)用是兩個(gè)完全獨(dú)立的App,并且iOS應(yīng)用基于沙盒的形式限制闸昨,所以一般的共享數(shù)據(jù)方法都是實(shí)現(xiàn)不了數(shù)據(jù)共享,這里就需要使用App Groups饵较。
- 在宿主程序和擴(kuò)展程序中分別設(shè)置打開App Group,設(shè)置一個(gè)group的名稱横辆,這里要保證宿主APP和擴(kuò)展APP的groupName要是相同的茄猫。
兩種數(shù)據(jù)存儲(chǔ)方式
-
使用NSUserDefault
這里不能使用[NSUserDefaults standardUserDefaults];
方法來初始化NSUserDefault對(duì)象,正像之前所說划纽,由于沙盒機(jī)制,拓展應(yīng)用是不允許訪問宿主應(yīng)用的沙盒路徑的靖避,因此上述用法是不對(duì)的,需要搭配app group完成實(shí)例化UserDefaults比默。正確的使用方式如下:
寫入數(shù)據(jù)
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.japho.widgetDemo"];
[userDefaults setObject:self.textField.text forKey:@"widget"];
[userDefaults synchronize];
讀取數(shù)據(jù)
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.japho.widgetDemo"];
self.contentStr = [userDefaults objectForKey:@"widget"];
-
通過NSFileManager共享數(shù)據(jù)
寫入數(shù)據(jù)
-(BOOL)saveDataByNSFileManager
{
NSError *err = nil;
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxx"];
containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/ widget"];
NSString *value = @"asdfasdfasf";
BOOL result = [value writeToURL:containerURL atomically:YES encoding:NSUTF8StringEncoding error:&err];
if (!result)
{
NSLog(@"%@",err);
}
else
{
NSLog(@"save value:%@ success.",value);
}
return result;
}
讀取數(shù)據(jù)
-(NSString *)readDataByNSFileManager
{
NSError *err = nil;
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.com.xxx"];
containerURL = [containerURL URLByAppendingPathComponent:@"Library/Caches/ widget"];
NSString *value = [NSString stringWithContentsOfURL:containerURL encoding: NSUTF8StringEncoding error:&err];
return value;
}
其他
補(bǔ)充:widget的上線也是需要單獨(dú)申請(qǐng)APP ID的 需要配置證書和Provisioning Profiles文件
沒有配置相關(guān)證書時(shí):
配置證書及描述文件:(列舉一些)
證書與描述文件配置好之后:
Demo
博主雙手奉上demo粘咖,同學(xué)們?nèi)绻矚g就給我點(diǎn)個(gè)star吧~~ ??
最后感謝一下文章的博主??
Widget的簡(jiǎn)單應(yīng)用并適配iOS10
iOS開發(fā)------Widget(Today Extension)插件化開發(fā)