zoukankan      html  css  js  c++  java
  • 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
    
    ' + 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版本和修改后版本

  • 相关阅读:
    regex c语言
    gitlab qq邮箱的配置
    error adding symbols: DSO missing from command line
    gcc 错误 //usr/lib/x86_64-linux-gnu/libstdc++.so.6 ...
    autogen.sh 的使用
    caffe_ssd create_data.sh 遇到的问题
    Ubuntu Server 中文乱码解决方案
    error: subprocess paste was killed by signal (Broken pipe)
    AttributeError: 'module' object has no attribute 'RAND_LIMIT_swigconstant
    eclipse:No more handles [Unknown Mozilla path (MOZILLA_FIVE_HOME not set)]
  • 原文地址:https://www.cnblogs.com/jackma86/p/5023800.html
Copyright © 2011-2022 走看看