一、簡介
iOS應用程序擴展是蘋果在iOS8推出的一個新特性,可以將自定義的功能和內容擴展到應用程序之外,在之後又經過不斷地優化和更新,已經成為開發中不可或缺的功能之一。擴展也是一個Target項目,它運行在主機應用程序上,可以與主機應用程序實現資源共享,和宿主應用程序的Target項目是彼此獨立的。系統提供的擴展有很多,Toady擴展就是其中之一,也被成為應用程序插件,它的作用是將今日發生的簡單消息展示在系統的插件界面上。Toady擴展模板名稱為Today Extension。圖1是創建Today擴展,圖2是擴展显示在插件界面上(可以通過點擊Edit來添加或者移除擴展)。
二、創建
按照上圖1的方式創建一個Today Extension的Target后,系統會默認幫我們生成一個TodayViewController控制器類、MainInterface.storyBoard故事板、plist序列化文件,文件結構圖如下:
上圖中紅色圈內和箭頭指向的配置就是系統通過MainInterface.storyBoard幫我們實現了一個基本的Toady插件UI布局,運行后可以直接显示在插件界面上。可是,有的時候開發者並不想使用系統的故事板來構建UI,系統支持自定義的,我們只需要修改plist配置即可。具體的配置是這樣的:
[1] 將NSExtensionMainStoryboard字段刪除;
[2] 添加NSExtensionPrincipalClass字段,修改value為控制器的類名。
[3] 在TodayViewController中的ViewDidLoad中設置preferredContentSize屬性大小,用來調整widget界面UI的尺寸。
配置如下圖所示:
//設置尺寸
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300);
三、分析
TodayViewController類比較簡單,就是一個VC類,它實現了系統提供的一個擴展協議<NCWidgetProviding>,可以在協議方法中實現對擴展的更新和狀態監控。
協議如下,都是可選的,開發者根據需要進行重寫。
//協議
@protocol NCWidgetProviding <NSObject>
@optional
//當數據更新時調用的方法,系統會定期更新擴展
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult result))completionHandler;
//監聽显示模式(寬鬆型、緊奏型)和尺寸的改變,其中寬鬆和緊湊表示的是展開和摺疊狀態, iOS10開始才能使用
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize __API_AVAILABLE(ios(10.0));
//設置擴展UI邊距,注意:使用StoryBoard時,若要所見即所得,則這個方法中需要返回UIEdgeInsetsZero; (iOS10 and later 不會再被調用,棄用了)
- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets __API_DEPRECATED("This method will not be called on widgets linked against iOS versions 10.0 and later.", ios(8.0, 10.0));
@end
//擴展,都是iOS10開始才能使用
@interface NSExtensionContext (NCWidgetAdditions)
//設置widget摺疊或展開狀態
@property (nonatomic, assign) NCWidgetDisplayMode widgetLargestAvailableDisplayMode __API_AVAILABLE(ios(10.0));
//只讀,widget狀態
@property (nonatomic, assign, readonly) NCWidgetDisplayMode widgetActiveDisplayMode __API_AVAILABLE(ios(10.0));
//獲取widget不同狀態的尺寸
- (CGSize)widgetMaximumSizeForDisplayMode:(NCWidgetDisplayMode)displayMode __API_AVAILABLE(ios(10.0));
@end
四、交互
Today擴展是寄宿於主機應用程序上的, TodayViewController又是一個UIViewController類,系統支持Today擴展對UIViewController進行切換。也就是說,蘋果在考慮提供給開發者在對UIViewController中添加各種展示控件這種便利的同時,也相應的提供給開發者通過Today擴展的widget從主機應用程序激活並打開宿主應用程序的機會。不過這個操作必須通過設置並調起scheme來實現。步驟如下:
[1] 配置宿主應用程序的scheme;
[2] 使用擴展的openURL打開宿主應用程序。
交互如下:
//擴展通過scheme打開主宿主應用程序
[self.extensionContext openURL:[NSURL URLWithString:@"MainApp://"] completionHandler:nil];
五、數據
既然Today擴展能與宿主應用程序進行交互,那麼肯定就存在數據通信的問題了。擴展與宿主目錄應用程序位於不同的目錄結構中,默認情況下,擴展與宿主應用程序的數據並不共享,代碼也不能復用。例如在宿主目錄應用程序中可能有網絡請求、數據持久化存儲等結構框架,在擴展中不可以直接使用,擴展需要提供自己的網絡請求框架、數據持久化框架等。這些問題蘋果都提供了解決方法,可以通過創建靜態庫的方式實現代碼共享,通過APP Group和Scheme跳轉實現數據共享。這裏主要講一下數據共享。注意:擴展和宿主應用程序的素材文件也是互相獨立的,必須將擴展中的素材添加到擴展Target。
方式一:通過配置scheme跳轉來實現數據共享。可以將傳遞的數據配置到URL中,然後宿主應用程序通過AppDeleagte的代理方法application:openURL:options:獲取數據,不過這個數據傳遞只能是單方向的。
//打開主應用程序
-(void)openMainApp {
//共享數據
NSString *schemeFormat = @"MainApp://action=openCarema?name=xiayuanquan";
[self.extensionContext openURL:[NSURL URLWithString:schemeFormat] completionHandler:nil];
}
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
//從URL獲取共享數據,截取數據
NSLog(@"---------url = %@---------",url);
return YES;
}
方式二:給擴展的Target和宿主應用程序的Target項目都開啟APP Group,兩者配置相同的appgroupIndentifier標識,分別生成後綴名為.entitlements文件。然後對於小數據推薦使用偏好進行雙向傳遞共享數據,如圖所示。
//共享數據
//使用偏好設置
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
[defalut setObject:@"xiayuanquan" forKey:@"name"];
//從偏好設置獲取共享數據
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
NSString *name1 = [defalut objectForKey:@"name"];
NSLog(@"1------------name1=%@",name1);
方式三:配置跟方式二一樣,不過雙向傳遞共享數據使用文件目錄來實現。
//共享數據
//方式二:使用共享目錄
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@"xiayuanquan" requiringSecureCoding:NO error:nil];
[data writeToURL:filePath atomically:YES];
//從共享目錄獲取共享數據
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSData dataWithContentsOfURL:filePath];
NSLog(@"2------------data=%@",data);
六、適配
從iOS10開始,蘋果提供了NCWidgetDisplayMode展示模式,通過設置該模式來支持對widget進行摺疊和展開。在這裏,preferredContentSize就用到了。這個是用來設置widget的尺寸的。蘋果對widget的尺寸有自己的標準,width為maxSize.width,height取值範圍[110, maxSize.height]。這個maxSize可以在擴展協議<NCWidgetProviding>的協議方法也即widgetActiveDisplayModeDidChange:withMaximumSize中獲取:,可以發現每一種機型maxSize不一樣。
// 6s模擬器下:
// NCWidgetDisplayModeCompact模式下:{359.000000, 110.000000}
// NCWidgetDisplayModeExpanded模式下:{359.000000, 528.000000}
// 8 plus模擬器下:
// NCWidgetDisplayModeCompact模式下:{304.000000, 110.000000}
// NCWidgetDisplayModeExpanded模式下:{304.000000, 616.000000}
摺疊狀態:widget的高為110,此時設置preferredContentSize無效;
展開狀態:widget的高為開發者設置的preferredContentSize.height,但是如果preferredContentSize.height>maxSize.height,此時取值為maxSize.height。
適配iOS10,默認支持展開,設置如下:
//設置widget默認為可以展開,此時處於摺疊狀態
#ifdef __IPHONE_10_0 //適配iOS10
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
#endif
七、範例
【去掉MainInterface.storyBoard,採用純代碼實現】
1、宿主應用程序AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//存儲共享數據
//方式二:使用偏好設置
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
[defalut setObject:@"xiayuanquan" forKey:@"name"];
//存儲共享數據
//方式三:使用共享目錄
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:@"xiayuanquan" requiringSecureCoding:NO error:nil];
[data writeToURL:filePath atomically:YES];
return YES;
}
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
//方式一:從URL獲取共享數據,例如參數
NSLog(@"---------url = %@---------",url);
return YES;
}
2、Widget擴展TodayViewController
//
// TodayViewController.m
// TodayExtension
// Created by 夏遠全 on 2019/11/19.
//
#import "TodayViewController.h"
#import <NotificationCenter/NotificationCenter.h>
@interface TodayViewController () <NCWidgetProviding>
@end
@implementation TodayViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self config];
[self createUI];
[self fecthData];
}
//配置
-(void)config {
self.view.backgroundColor = [UIColor lightGrayColor]; //widget背景色為灰色
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300); //widget尺寸大小, 寬度實際取maxSize,width,高度[110, maxSize.height]
//設置widget默認為可以展開,此時處於摺疊狀態
#ifdef __IPHONE_10_0 //適配iOS10
self.extensionContext.widgetLargestAvailableDisplayMode = NCWidgetDisplayModeExpanded;
#endif
}
//創建UI
-(void)createUI {
CGFloat width = self.view.frame.size.width;
CGFloat btnWidth = 100;
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake((width-btnWidth)/2, 0, btnWidth, 40)];
button.backgroundColor = [UIColor greenColor];
[button setTitle:@"OpenAPP" forState:UIControlStateNormal];
[button setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[button addTarget:self action:@selector(openMainApp) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
//打開主應用程序
-(void)openMainApp {
//傳遞共享數據
//方式一:參數傳遞
NSString *schemeFormat = @"MainApp://action=openCarema?name=xiayuanquan";
[self.extensionContext openURL:[NSURL URLWithString:schemeFormat] completionHandler:nil];
}
//獲取共享數據
-(void)fecthData {
//方式二:從偏好設置獲取共享數據
NSUserDefaults *defalut = [[NSUserDefaults alloc] initWithSuiteName:@"group.xiayuanquan"];
NSString *name1 = [defalut objectForKey:@"name"];
NSLog(@"1------------name1=%@",name1);
//方式三:從共享目錄獲取共享數據
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *baseURL = [fileManager containerURLForSecurityApplicationGroupIdentifier:@"group.xiayuanquan"];
NSURL *filePath = [baseURL URLByAppendingPathComponent:@"file"];
NSData *data = [NSData dataWithContentsOfURL:filePath];
NSLog(@"2------------data=%@",data);
}
//當數據更新時調用的方法,系統會定期更新擴展
- (void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
//獲取共享的數據,根據判斷回調對應的block
//NCUpdateResultNewData,
//NCUpdateResultNoData,
//NCUpdateResultFailed
completionHandler(NCUpdateResultNoData);
}
//監聽显示模式(寬鬆型、緊奏型)和尺寸的改變
//NCWidgetDisplayModeCompact : 摺疊
//NCWidgetDisplayModeExpanded : 展開
- (void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayMode withMaximumSize:(CGSize)maxSize {
//maxSize:
//雖說是最大的Size,但蘋果還是把Widget的高度範圍限制在了[110 ~ maxSize]之間
//如果設置高度小於110,那麼default = 110;
//如果設置高度大於開發者設置的preferredContentSize.Heiget,那麼default = maxSize;
//摺疊狀態下,蘋果將高度固定為110,這個時候設置preferredContentSize屬性無效。
NSLog(@"width = %lf-------height = %lf",maxSize.width,maxSize.height);
//可以更改狀態
if (activeDisplayMode == NCWidgetDisplayModeExpanded) {
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 300);
}
else{
self.preferredContentSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 100);
}
}
//設置擴展UI邊距,注意:使用StoryBoard時,若要所見即所得,則這個方法中需要返回UIEdgeInsetsZero; (iOS10 and later 不會再被調用)
//- (UIEdgeInsets)widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets {
// return UIEdgeInsetsZero;
//}
@end
3、打印和gif
2019-11-20 16:22:31.074596+0800 TodayExtension[29668:1132736] 1------------name1=xiayuanquan
2019-11-20 16:22:31.234435+0800 TodayExtension[29668:1132736] 2------------data={length = 149, bytes = 0x62706c69 73743030 d4010203 04050607 ... 00000000 00000068 }
2019-11-20 16:22:31.234970+0800 TodayExtension[29668:1132736] maxSize.width = 359.000000-------maxSize.height = 110.000000 //摺疊
2019-11-20 16:22:38.117764+0800 TodayExtension[29668:1132736] maxSize.width = 359.000000-------maxSize.height = 528.000000 //展開
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享
※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選
※台灣海運大陸貨務運送流程
※兩岸物流進出口一站式服務