iOS8中添加的extensions总结(四)——Action扩展

  • 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改成你的

 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,所以下载源码后,需要修改下面几个地方

Bundle Identifier和Team

Group ID和源码中

1 //RWTBitlyHistoryService.m
2 - (NSURL *)applicationDocumentsDirectory {
3 #warning SET TO YOUR APP GROUP ID
4     NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.qq100858433.JMQuickBit"];
5     return containerURL;
6 }

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

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<NSSecureCoding>  _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 App
31     //顺序与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提供的服务模型源码的分析

在Action扩展实现中的itemLoadCompletedWithPreprocessingResults:函数,我们先调用了

1 - (void)shortenUrl:(NSURL *)longUrl
2             domain:(NSString *)domain
3         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 ID
7     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

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

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

首先明确一点,RWTBitlyHistoryService这个类采用的是单例模式,在任何文件中调用该类都是使用提供的类方法获取这个单例

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共享数据的实现流程

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

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

时间: 2024-12-04 18:37:08

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

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

通知栏中的今日扩展 分享扩展 Action扩展 图片编辑扩展 文件管理扩展 第三方键盘扩展 注:此教程来源于http://www.raywenderlich.com的<iOS8 by Tutorials> 关于App extensions 的原理,即How extensions work 首先App扩展是一个App功能性上的扩展,它并不独立与你原来的App,也就是说在给App Store提交的时候是打包到原有App中一起提交,它们并不是独立的App.其次,App的每一种扩展都有自己单独的API

iOS8中添加的extensions总结(二)

分享扩展 注:此教程来源于http://www.raywenderlich.com的<iOS8 by Tutorials> 1.准备 这次例子来源于国外的图片分享网站Imgur.com 首先现在Imgur上注册你的帐号,之后按下面的步骤来,点击settings 选择applications中新建app,即下面的create your own,这里上面的App是我得Demo 之后点击register注册你的应用 输入你App的名字后选择第二项,后面输入你的邮箱与面述,点击submit 这里请记好

asp.net core 3.0在Microsoft.Extensions.Logging中添加log4net支持

1. 引入log4net.Microsoft.Extensions.Logging.Log4Net.AspNetCore包 2. 添加log4net配置文件 3. 在Program中添加如下代码: public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureLogging((context, logger) => { logger.ClearPr

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

向视图中添加服务 现在,ASP.NET MVC 6 支持注入类到视图中,和VC类不同的是,对类是公开的.非嵌套或非抽象并没有限制.在这个例子中,我们创建了一个简单的类,用于统计代办事件.已完成事件和平均优先级的服务. 1. 添加命名为Services 的文件夹,在该文件夹下添加名称为 StatisticsService.cs 的类: StatisticsService 类代码设计如下: using System.Linq; using System.Threading.Tasks; using

Django 程序中添加js插件文本编辑器

第一步:在首页中添加写博客的按钮     <li>         <a href="{% url 'create_article' %}">写博客</a>     </li> 第二步:写相应的创建博客视图,编辑views.py文件 def create_article(request):     if request.method == "GET" :         return  render(request,'

初探 iOS8 中的 Size Class

以前和安卓的同学聊天的时候,谈到适配一直是一个非常开心的话题,看到他们被各种屏幕适配折磨的欲仙欲死,心里真替他们高兴.不过在做到 iPhone 和 iPad 的适配的时候,一个页面需要配置多个 xib 进行开发还是个很头疼的事情.再加上 iPhone6 和 iPhone6 plus 的发布,适配似乎也变得麻烦起来.今天了解了 iOS8 中的 Size Class 之后,真的笑,笑出声. 简介 先来看一下我们的新伙伴:Size Classes.在 iOS8 中,我们不用再像以前那样,一个页面新建多

[iOS] 初探 iOS8 中的 Size Class

转自:http://www.itnose.net/detail/6112176.html ? ? ? 以前和安卓的同学聊天的时候,谈到适配一直是一个非常开心的话题,看到他们被各种屏幕适配折磨的欲仙欲死,心里真替他们高兴.不过在做到 iPhone 和 iPad 的适配的时候,一个页面需要配置多个 xib 进行开发还是个很头疼的事情.再加上 iPhone6 和 iPhone6 plus 的发布,适配似乎也变得麻烦起来.今天了解了 iOS8 中的 Size Class 之后,真的笑,笑出声. 简介 先

iOS8中提示框的使用UIAlertController(UIAlertView和UIActionSheet二合一)

iOS8推出了几个新的“controller”,主要是把类似之前的UIAlertView变成了UIAlertController,这不经意的改变,貌似把我之前理解的“controller”一下子推翻了-但是也无所谓,有新东西不怕,学会使用了就行.接下来会探讨一下这些个新的Controller. - (void)showOkayCancelAlert { NSString *title = NSLocalizedString(@"A Short Title Is Best", nil);

iOS8中UIAlertController的使用

iOS8中的UIAlertController包括了之前的UIAlertView和UIActionSheet,将它们集合成了自己的style: 1------------UIAlertControllerStyleAlert-----原有的UIAlertView UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"alertC" message:@"message" pr