• Action扩展

注:此教程来源于http://www.raywenderlich.com的《iOS8 by Tutorials》


1.准备

本次教程利用网站bitly.com进行
bitly网站进行对网络链接的精简化,比如将YouTube某个视频链接转化为bit.ly/1wOl2zf这种形式;同时,该网站提供对链接进行数据分析等服务,直接在链接后+“+”即可查看流量、点击量等信息,比如 bit.ly/1wOl2zf+ 。
 
  1、注册点击:https://bitly.com/a/sign_up
  2、进入https://bitly.com/a/settings进行邮箱验证
  3、点击https://bitly.com/a/oauth_apps注册你的App,按网站提示进行即可,最后你会看到下面的界面,点击Generate Token得到你的Access Token

  4、​在提供的源码中将原来所有AccessToken改成你的

 //ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
#warning Input your access token
//在这里修改为你的Access Token
self.bitlyService = [[RWTBitlyService alloc] initWithOAuthAccessToken:@"fcc98e0289365e2c4f333a9bc1f336513ebf8aff"];
self.actionButtonState = RWTMainViewControllerActionButtonStateCopyUrl;
self.button.layer.borderColor = [UIColor whiteColor].CGColor;
self.button.layer.borderWidth = 1.0/[UIScreen mainScreen].scale;
self.button.layer.cornerRadius = 15.0; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(respondToUIApplicationDidBecomeActiveNotification) name:UIApplicationDidBecomeActiveNotification object:nil];
}

还要注意的是,Action扩展需要用到App Group,所以下载源码后,需要修改下面几个地方

Bundle Identifier和Team

Group ID和源码中
 //RWTBitlyHistoryService.m
- (NSURL *)applicationDocumentsDirectory {
#warning SET TO YOUR APP GROUP ID
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.qq100858433.JMQuickBit"];
return containerURL;
}

2.正文

添加Action Extension

在这里我们选择的Action Type是No User Interface,也就是说没有界面可以设置,Apple为开发者提供的是JavaScript接口来获取Web中的数据和设置你的内容。
那么,如果你想做一个类似1Password在Safari中的Action插件的话,就需要在这里选择Presents User Interface,那么就不需要JavaScript,而是直接由Host App决定NSExtensionContext的input内容

最后点击Activate

添加后需要确认App Groups以及链接的.m文件

下面打开Action扩展的Info.plist文件,系统默认的如下,意思就是这个扩展只支持Web且只支持一个URL
这个Action扩展只针对Safari浏览器,因此默认即可

下面是新生成的Action.js文件和OC文件,这些文件默认的功能是改变Web背景颜色,原本白色背景改成红色,有色背景改成绿色背景,在Apple默认提供的代码中有详细的注释,就不再多说

针对当前的QuickBit应用,我们的目的是将当前的网址URL获取后调用Bitly的服务,将其精简化后拷贝至剪贴板并通过Alert来提示用户


  1、首先是修改Action.js文件中的run函数,来获取当前网站的URL

 run: function(arguments) {
arguments.completionFunction({ "currentURL" : document.URL })
}

  2、之后修改ActionRequestHandler.m中的beginRequestWithExtensionContext:函数

 //由Host App传入context参数
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context {
//在不使用interface情况下,不要调用super
//获取由Host App提供的context
self.extensionContext = context; //从inputItems中获取第一个对象
NSExtensionItem *extensionItem = context.inputItems.firstObject;
if (!extensionItem) {
[self doneWithResults:nil];
return;
} //从extensionItem中获取第一个对象
NSItemProvider *itemProvider = extensionItem.attachments.firstObject;
if (!itemProvider) {
[self doneWithResults:nil];
return;
} //检查标识符是否为kUTTypePropertyList,如果是进行下一步处理
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypePropertyList]) {
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypePropertyList options:nil completionHandler:^(id<NSSecureCoding> _Nullable item, NSError * _Null_unspecified error) {
NSDictionary *dictionary = (NSDictionary *)item;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//拆包,取出字典中NSExtensionJavaScriptPreprocessingResultsKey的值,Safari将用到这个JS对象
//在这里使用主队列十分必要,因为itemLoadCompletedWithPreprocessingResults方法是异步进行的
[self itemLoadCompletedWithPreprocessingResults:dictionary[NSExtensionJavaScriptPreprocessingResultsKey]];
}];
}];
} else {
[self doneWithResults:nil];
}
}

  3、下面修改其他函数

 - (void)itemLoadCompletedWithPreprocessingResults:(NSDictionary *)javaScriptPreprocessingResults {
//在这里接受你从Action.js中的run函数传来的参数
NSString *currentURLString = javaScriptPreprocessingResults[@"currentURL"]; #warning SET TO YOUR ACCESS TOKEN
RWTBitlyService *bitlyService = [[RWTBitlyService alloc] initWithOAuthAccessToken:@"fcc98e0289365e2c4f333a9bc1f336513ebf8aff"]; //调用shortenUrl:方法对链接进行处理
NSURL *longURL = [NSURL URLWithString:currentURLString];
[bitlyService shortenUrl:longURL domain:@"bit.ly" completion:^(RWTBitlyShortenedUrlModel *shortUrl, NSError *error) {
if (!error) {
NSURL *shortURL = shortUrl.shortUrl;
[UIPasteboard generalPasteboard].URL = shortURL;
[[RWTBitlyHistoryService sharedService] addItem:shortUrl];
//通过下一步将数据保存在AppGroup下,在主程序中也可以获取到这个共享的数据
[[RWTBitlyHistoryService sharedService] persistItemsArray]; NSDictionary *dic = @{@"shortURL": shortURL.absoluteString};
[self doneWithResults:dic];
}
}];
} //此方法将所得JS数据,返回给Host App,之后调用Action.js中的finalize函数
- (void)doneWithResults:(NSDictionary *)resultsForJavaScriptFinalize {
if (resultsForJavaScriptFinalize) {
//打包
NSDictionary *resultsDictionary = @{ NSExtensionJavaScriptFinalizeArgumentKey: resultsForJavaScriptFinalize }; //初始化NSExtensionItem对象,并把它传到Host App
//顺序与beginRequestWithExtensionContext相反即可
NSItemProvider *resultsProvider = [[NSItemProvider alloc] initWithItem:resultsDictionary typeIdentifier:(NSString *)kUTTypePropertyList];
NSExtensionItem *resultsItem = [[NSExtensionItem alloc] init];
resultsItem.attachments = @[resultsProvider]; [self.extensionContext completeRequestReturningItems:@[resultsItem] completionHandler:nil];
} else {
[self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];
} self.extensionContext = nil;
}

  4、最后再修改Action.js中的finalize函数,提示用户成功与否

 finalize: function(arguments) {
// This method is run after the native code completes. // We'll see if the native code has passed us a new background style,
// and set it on the body.
var error = arguments["error"];
if (error) {
alert('There was an error creating your bit.ly link');
} else {
var shortURL = arguments["shortURL"];
alert('Your bit.ly link is now on your clipboard\n\n' + shortURL);
}
}

最后在Safari中运行,下面是最后的实际效果:

最后在主程序中历史记录中也可以找到扩展网站的记录


3.后记

关于App Group的具体实现,也是对raywenderlich.com提供的服务模型源码的分析

在Action扩展实现中的itemLoadCompletedWithPreprocessingResults:函数,我们先调用了
 - (void)shortenUrl:(NSURL *)longUrl
domain:(NSString *)domain
completion:(RWTBitlyShortenUrlCompletion)completion;

方法,在返回的block中,提供处理后的链接和错误信息,为了将这一次的扩展服务保存到App Group中,在block内使用了下面两个方法

  - (void)addItem:(RWTBitlyShortenedUrlModel *)item;
- (void)persistItemsArray;

前一个函数即是将这一次的URLModel保存起来,那么下一个函数呢

 - (void)persistItemsArray {
NSData *itemsData = [NSKeyedArchiver archivedDataWithRootObject:_items];
NSError *saveError;
[itemsData writeToURL:[self savedItemsFileUrl] options:kNilOptions error:&saveError];
if (saveError) {
NSLog(@"Error persisting history items: %@", saveError);
}
}

在persistItemsArray中先是将原来保存RWTBitlyShortenedUrlModel的数组_items固化成NSData并写入到[self savedItemsFileUrl]这个地址内

 - (NSURL *)savedItemsFileUrl {
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"RWTBitlyHistoryServiceItems.dat"];
} - (NSURL *)applicationDocumentsDirectory {
#warning SET TO YOUR APP GROUP ID
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.qq100858433.JMQuickBit"];
return containerURL;
}

最后在模拟器内返回的路径为:/Users/JackMa/Library/Developer/CoreSimulator/Devices/784A9D4A-A97C-4123-9A7C-426839365E3A/data/Containers/Shared/AppGroup/D6057F67-084F-422F-B97D-8AE386559793/RWTBitlyHistoryServiceItems.dat

这是在AppGroup目录下针对当前App的一个共享文件,Container App即主程序就是通过这个文件进行的数据共享

那么在主程序运行时,是如何访问这个共享文件的呢?

首先明确一点,RWTBitlyHistoryService这个类采用的是单例模式,在任何文件中调用该类都是使用提供的类方法获取这个单例
 + (RWTBitlyHistoryService *)sharedService {
static dispatch_once_t onceToken;
static RWTBitlyHistoryService *_sharedService;
dispatch_once(&onceToken, ^{
_sharedService = [[self alloc] init];
}); return _sharedService;
}

在单例的初始化中,覆写了init方法如下,其中调用了loadItemsArray方法

 - (instancetype)init {
self = [super init];
if (!self) {
return nil;
} [self loadItemsArray];
if (!_items) {
_items = [NSMutableArray array];
} [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(persistItemsArray) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadItemsArray) name:UIApplicationWillEnterForegroundNotification object:nil]; return self;
} - (void)loadItemsArray {
NSMutableArray *items = nil;
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self savedItemsFileUrl].path]; if (fileExists) {
NSError *loadError;
NSData *itemsData = [NSData dataWithContentsOfURL:[self savedItemsFileUrl] options:kNilOptions error:&loadError];
if (loadError) {
NSLog(@"Error loading history items: %@", loadError);
} else {
items = [[NSKeyedUnarchiver unarchiveObjectWithData:itemsData] mutableCopy];
}
} else {
items = [NSMutableArray array];
} _items = items;
}

从上面的代码中不难看出通过判断是否存在共享文件,若存在,就加载NSData数据,后解固成数据,并保存在_items内

最后总结一下App Group共享数据的实现流程

  1. 在工程中打开App Group开关,获取Group ID
  2. 在数据或服务模型中通过Group ID获得共享文件目录路径
  3. 单例模式的话在实现文件中使用数组、字典等来编解码文件数据
  4. 主程序或扩展程序通过数据模型中的方法实现对共享文件的操作

源码点击 包括未添加扩展的original版本和修改后版本

iOS8中添加的extensions总结(四)——Action扩展的更多相关文章

  1. iOS8中添加的extensions总结(一)——今日扩展

    通知栏中的今日扩展 分享扩展 Action扩展 图片编辑扩展 文件管理扩展 第三方键盘扩展 注:此教程来源于http://www.raywenderlich.com的<iOS8 by Tutor ...

  2. iOS8中添加的extensions总结(三)——图片编辑扩展

    图片编辑扩展 注:此教程来源于http://www.raywenderlich.com的<iOS8 by Tutorials> 1.准备 与(二)类似的使用Imgur作为图片来源   2. ...

  3. iOS8中添加的extensions总结(二)——分享扩展

    分享扩展 注:此教程来源于http://www.raywenderlich.com的<iOS8 by Tutorials> 1.准备 这次例子来源于国外的图片分享网站Imgur.com 首 ...

  4. ASP.NET 5系列教程 (四):向视图中添加服务和发布应用到公有云

    向视图中添加服务 现在,ASP.NET MVC 6 支持注入类到视图中,和VC类不同的是,对类是公开的.非嵌套或非抽象并没有限制.在这个例子中,我们创建了一个简单的类,用于统计代办事件.已完成事件和平 ...

  5. 在ASP.NET Core中添加的Cookie如果含有特殊字符,会被自动转义

    我们知道在Cookie中有些字符是特殊字符,这些字符是不能出现在Cookie的键值中的. 比如"="是Cookie中用来分隔键和值的特殊字符,例如:Key01=Value01,表示 ...

  6. linux运维中的命令梳理(四)

    ----------管理命令---------- ps命令:查看进程 要对系统中进程进行监测控制,查看状态,内存,CPU的使用情况,使用命令:/bin/ps (1) ps :是显示瞬间进程的状态,并不 ...

  7. iOS8中的UIAlertController

    转:      iOS8推出了几个新的“controller”,主要是把类似之前的UIAlertView变成了UIAlertController,这不经意的改变,貌似把我之前理解的“controlle ...

  8. 初探 iOS8 中的 Size Class

    初探 iOS8 中的 Size Class 分类: Ios2014-09-16 13:11 4323人阅读 评论(1) 收藏 举报   目录(?)[+]   初探 iOS8 中的 Size Class ...

  9. 通过判断cookie过期方式向Memcached中添加,取出数据(Java)

    应用场景:在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够 ...

随机推荐

  1. hdu 4548 第六周H题(美素数)

    第六周H题 - 数论,晒素数 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u   De ...

  2. 实验四:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 如果我写的不好或者有误的地方请留言 ...

  3. 转:MySQL导入.sql文件及常用命令

    在MySQL Qurey   Brower中直接导入*.sql脚本,是不能一次执行多条sql命令的,在mysql中执行sql文件的命令: mysql> source   d:/myprogram ...

  4. Android Service之LOCATION_SERVICE

    Android提供了GPS功能 LocationManager obj = (LocationManager)getSystemService(Context.LOCATION_SERVICE) Pe ...

  5. Miller_Rabin素数判断,rho

    safe保险一点5吧.我是MR: ; int gcd(int a,int b){return !b?a:gcd(b,a%b);} int mul(int a,int b,int p){ )*p); ? ...

  6. SSE及相关技术(web sockets, long polling等)

    server-sent events--One Way Messaging 允许网页获得来自服务器的更新,并且自动更新 Server-Sent Events: allow a web page to ...

  7. cholesky分解

        接着LU分解继续往下,就会发展出很多相关但是并不完全一样的矩阵分解,最后对于对称正定矩阵,我们则可以给出非常有用的cholesky分解.这些分解的来源就在于矩阵本身存在的特殊的 结构.对于矩阵 ...

  8. SRM468 - SRM469(1-250pt, 500pt)

    SRM 468 DIV1 250pt 题意:给出字典,按照一定要求进行查找. 解法:模拟题,暴力即可. tag:water score: 0.... 这是第一次AC的代码: /* * Author: ...

  9. objc非主流代码技巧

    原文:http://blog.sunnyxx.com/2014/08/02/objc-weird-code/ [娱乐向]objc最短的方法声明 先来个娱乐向的.方法声明时有一下几个trick: 返回值 ...

  10. Matlab绘制三维图形以及提示框

    1.首先,在编辑区输入如下代码 >> [x,y] = meshgrid([-100,0.1,100]); >> z = sqrt(x.^2 + y.^2); >> ...