iOS:應用程序擴展開發之Today擴展(Today Extesnsion)

1、簡介ios

iOS應用程序擴展是蘋果在iOS8推出的一個新特性,能夠將自定義的功能和內容擴展到應用程序以外,在以後又通過不斷地優化和更新,已經成爲開發中不可或缺的功能之一。擴展也是一個Target項目,它運行在主機應用程序上,能夠與主機應用程序實現資源共享,和宿主應用程序的Target項目是彼此獨立的。系統提供的擴展有不少,Toady擴展就是其中之一,也被成爲應用程序插件,它的做用是將今日發生的簡單消息展現在系統的插件界面上。Toady擴展模板名稱爲Today Extension。圖1是建立Today擴展,圖2是擴展顯示在插件界面上(能夠經過點擊Edit來添加或者移除擴展)。 
網絡

 

2、建立app

按照上圖1的方式建立一個Today Extension的Target後,系統會默認幫咱們生成一個TodayViewController控制器類、MainInterface.storyBoard故事板、plist序列化文件,文件結構圖以下:框架

上圖中紅色圈內和箭頭指向的配置就是系統經過MainInterface.storyBoard幫咱們實現了一個基本的Toady插件UI佈局,運行後能夠直接顯示在插件界面上。但是,有的時候開發者並不想使用系統的故事板來構建UI,系統支持自定義的,咱們只須要修改plist配置便可。具體的配置是這樣的:ide

[1] 將NSExtensionMainStoryboard字段刪除;佈局

[2] 添加NSExtensionPrincipalClass字典,修改value爲主控制器的類名。 優化

[3] 在TodayViewController中的ViewDidLoad中設置preferredContentSize屬性大小,用來調整widget界面UI的尺寸。ui

配置以下圖所示:atom

 

3、分析url

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

 

4、交互

Today擴展是寄宿於主機應用程序上的, TodayViewController又是一個UIViewController類,系統支持Today擴展對UIViewController進行切換。也就是說,蘋果在考慮提供給開發者在對UIViewController中添加各類展現控件這種便利的同時,也相應的提供給開發者經過Today擴展的widget從主機應用程序激活並打開宿主應用程序的機會。不過這個操做必須經過設置並調起scheme來實現。步驟以下:

[1] 配置宿主應用程序的scheme;

[2] 使用擴展的openURL打開宿主應用程序。

交互以下:

//擴展經過scheme打開主宿主應用程序
[self.extensionContext openURL:[NSURL URLWithString:@"MainApp://"] completionHandler:nil];

 

5、數據

既然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);

 

6、適配

從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

 

7、範例

【去掉MainInterface.storyBoard,採用純代碼實現】

一、宿主應用程序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;
    
}

二、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

三、打印和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 //展開

相關文章
相關標籤/搜索