-
Action扩展
注:此教程来源于的《iOS8 by Tutorials》
1.准备
4、
在提供的源码中将原来所有AccessToken改成你的 1 //ViewController.m 2 - (void)viewDidLoad { 3 [super viewDidLoad]; 4 #warning Input your access token 5 //在这里修改为你的Access Token 6 self.bitlyService = [[RWTBitlyService alloc] initWithOAuthAccessToken:@"fcc98e0289365e2c4f333a9bc1f336513ebf8aff"]; 7 self.actionButtonState = RWTMainViewControllerActionButtonStateCopyUrl; 8 self.button.layer.borderColor = [UIColor whiteColor].CGColor; 9 self.button.layer.borderWidth = 1.0/[UIScreen mainScreen].scale;10 self.button.layer.cornerRadius = 15.0;11 12 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(respondToUIApplicationDidBecomeActiveNotification) name:UIApplicationDidBecomeActiveNotification object:nil];13 }
还要注意的是,Action扩展需要用到App Group,所以下载源码后,需要修改下面几个地方
1 //RWTBitlyHistoryService.m2 - (NSURL *)applicationDocumentsDirectory {3 #warning SET TO YOUR APP GROUP ID4 NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.qq100858433.JMQuickBit"];5 return containerURL;6 }
2.正文
添加Action Extension
最后点击Activate
下面是新生成的Action.js文件和OC文件,这些文件默认的功能是改变Web背景颜色,原本白色背景改成红色,有色背景改成绿色背景,在Apple默认提供的代码中有详细的注释,就不再多说
针对当前的QuickBit应用,我们的目的是将当前的网址URL获取后调用Bitly的服务,将其精简化后拷贝至剪贴板并通过Alert来提示用户
1、首先是修改Action.js文件中的run函数,来获取当前网站的URL
1 run: function(arguments) {2 arguments.completionFunction({ "currentURL" : document.URL })3 }
2、之后修改ActionRequestHandler.m中的beginRequestWithExtensionContext:函数
1 //由Host App传入context参数 2 - (void)beginRequestWithExtensionContext:(NSExtensionContext *)context { 3 //在不使用interface情况下,不要调用super 4 //获取由Host App提供的context 5 self.extensionContext = context; 6 7 //从inputItems中获取第一个对象 8 NSExtensionItem *extensionItem = context.inputItems.firstObject; 9 if (!extensionItem) {10 [self doneWithResults:nil];11 return;12 }13 14 //从extensionItem中获取第一个对象15 NSItemProvider *itemProvider = extensionItem.attachments.firstObject;16 if (!itemProvider) {17 [self doneWithResults:nil];18 return;19 }20 21 //检查标识符是否为kUTTypePropertyList,如果是进行下一步处理22 if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePropertyList]) {23 [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(id_Nullable item, NSError * _Null_unspecified error) {24 NSDictionary *dictionary = (NSDictionary *)item;25 [[NSOperationQueue mainQueue] addOperationWithBlock:^{26 //拆包,取出字典中NSExtensionJavaScriptPreprocessingResultsKey的值,Safari将用到这个JS对象27 //在这里使用主队列十分必要,因为itemLoadCompletedWithPreprocessingResults方法是异步进行的28 [self itemLoadCompletedWithPreprocessingResults:dictionary[NSExtensionJavaScriptPreprocessingResultsKey]];29 }];30 }];31 } else {32 [self doneWithResults:nil];33 }34 }
3、下面修改其他函数
1 - (void)itemLoadCompletedWithPreprocessingResults:(NSDictionary *)javaScriptPreprocessingResults { 2 //在这里接受你从Action.js中的run函数传来的参数 3 NSString *currentURLString = javaScriptPreprocessingResults[@"currentURL"]; 4 5 #warning SET TO YOUR ACCESS TOKEN 6 RWTBitlyService *bitlyService = [[RWTBitlyService alloc] initWithOAuthAccessToken:@"fcc98e0289365e2c4f333a9bc1f336513ebf8aff"]; 7 8 //调用shortenUrl:方法对链接进行处理 9 NSURL *longURL = [NSURL URLWithString:currentURLString];10 [bitlyService shortenUrl:longURL domain:@"bit.ly" completion:^(RWTBitlyShortenedUrlModel *shortUrl, NSError *error) {11 if (!error) {12 NSURL *shortURL = shortUrl.shortUrl;13 [UIPasteboard generalPasteboard].URL = shortURL;14 [[RWTBitlyHistoryService sharedService] addItem:shortUrl];15 //通过下一步将数据保存在AppGroup下,在主程序中也可以获取到这个共享的数据16 [[RWTBitlyHistoryService sharedService] persistItemsArray];17 18 NSDictionary *dic = @{ @"shortURL": shortURL.absoluteString};19 [self doneWithResults:dic];20 }21 }];22 }23 24 //此方法将所得JS数据,返回给Host App,之后调用Action.js中的finalize函数25 - (void)doneWithResults:(NSDictionary *)resultsForJavaScriptFinalize {26 if (resultsForJavaScriptFinalize) {27 //打包28 NSDictionary *resultsDictionary = @{ NSExtensionJavaScriptFinalizeArgumentKey: resultsForJavaScriptFinalize };29 30 //初始化NSExtensionItem对象,并把它传到Host App31 //顺序与beginRequestWithExtensionContext相反即可32 NSItemProvider *resultsProvider = [[NSItemProvider alloc] initWithItem:resultsDictionary typeIdentifier:(NSString *)kUTTypePropertyList];33 NSExtensionItem *resultsItem = [[NSExtensionItem alloc] init];34 resultsItem.attachments = @[resultsProvider];35 36 [self.extensionContext completeRequestReturningItems:@[resultsItem] completionHandler:nil];37 } else {38 [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];39 }40 41 self.extensionContext = nil;42 }
4、最后再修改Action.js中的finalize函数,提示用户成功与否
1 finalize: function(arguments) { 2 // This method is run after the native code completes. 3 4 // We'll see if the native code has passed us a new background style, 5 // and set it on the body. 6 var error = arguments["error"]; 7 if (error) { 8 alert('There was an error creating your bit.ly link'); 9 } else {10 var shortURL = arguments["shortURL"];11 alert('Your bit.ly link is now on your clipboard\n\n' + shortURL);12 }13 }
最后在Safari中运行,下面是最后的实际效果:
最后在主程序中历史记录中也可以找到扩展网站的记录
3.后记
关于App Group的具体实现,也是对raywenderlich.com提供的服务模型源码的分析
1 - (void)shortenUrl:(NSURL *)longUrl2 domain:(NSString *)domain3 completion:(RWTBitlyShortenUrlCompletion)completion;
方法,在返回的block中,提供处理后的链接和错误信息,为了将这一次的扩展服务保存到App Group中,在block内使用了下面两个方法
1 1 - (void)addItem:(RWTBitlyShortenedUrlModel *)item;2 2 - (void)persistItemsArray;
前一个函数即是将这一次的URLModel保存起来,那么下一个函数呢
1 - (void)persistItemsArray {2 NSData *itemsData = [NSKeyedArchiver archivedDataWithRootObject:_items];3 NSError *saveError;4 [itemsData writeToURL:[self savedItemsFileUrl] options:kNilOptions error:&saveError];5 if (saveError) {6 NSLog(@"Error persisting history items: %@", saveError);7 }8 }
在persistItemsArray中先是将原来保存RWTBitlyShortenedUrlModel的数组_items固化成NSData并写入到[self savedItemsFileUrl]这个地址内
1 - (NSURL *)savedItemsFileUrl {2 return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"RWTBitlyHistoryServiceItems.dat"];3 }4 5 - (NSURL *)applicationDocumentsDirectory {6 #warning SET TO YOUR APP GROUP ID7 NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.qq100858433.JMQuickBit"];8 return containerURL;9 }
最后在模拟器内返回的路径为:/Users/JackMa/Library/Developer/CoreSimulator/Devices/784A9D4A-A97C-4123-9A7C-426839365E3A/data/Containers/Shared/AppGroup/D6057F67-084F-422F-B97D-8AE386559793/RWTBitlyHistoryServiceItems.dat
那么在主程序运行时,是如何访问这个共享文件的呢?
1 + (RWTBitlyHistoryService *)sharedService {2 static dispatch_once_t onceToken;3 static RWTBitlyHistoryService *_sharedService;4 dispatch_once(&onceToken, ^{5 _sharedService = [[self alloc] init];6 });7 8 return _sharedService;9 }
在单例的初始化中,覆写了init方法如下,其中调用了loadItemsArray方法
1 - (instancetype)init { 2 self = [super init]; 3 if (!self) { 4 return nil; 5 } 6 7 [self loadItemsArray]; 8 if (!_items) { 9 _items = [NSMutableArray array];10 }11 12 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(persistItemsArray) name:UIApplicationWillResignActiveNotification object:nil];13 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadItemsArray) name:UIApplicationWillEnterForegroundNotification object:nil];14 15 return self;16 }17 18 - (void)loadItemsArray {19 NSMutableArray *items = nil;20 BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self savedItemsFileUrl].path];21 22 if (fileExists) {23 NSError *loadError;24 NSData *itemsData = [NSData dataWithContentsOfURL:[self savedItemsFileUrl] options:kNilOptions error:&loadError];25 if (loadError) {26 NSLog(@"Error loading history items: %@", loadError);27 } else {28 items = [[NSKeyedUnarchiver unarchiveObjectWithData:itemsData] mutableCopy];29 }30 } else {31 items = [NSMutableArray array];32 }33 34 _items = items;35 }
从上面的代码中不难看出通过判断是否存在共享文件,若存在,就加载NSData数据,后解固成数据,并保存在_items内
最后总结一下App Group共享数据的实现流程
- 在工程中打开App Group开关,获取Group ID
- 在数据或服务模型中通过Group ID获得共享文件目录路径
- 单例模式的话在实现文件中使用数组、字典等来编解码文件数据
- 主程序或扩展程序通过数据模型中的方法实现对共享文件的操作
包括未添加扩展的original版本和修改后版本