IOS8 beta 中的共享扩展

Posted

技术标签:

【中文标题】IOS8 beta 中的共享扩展【英文标题】:Sharing Extension in IOS8 beta 【发布时间】:2014-06-05 09:08:25 【问题描述】:

我正在尝试使用新的 ios 8 应用扩展来创建共享扩展。我试图获取 Safari 站点的当前 URL 以在 UILabel 中显示它。很简单。

我正在通过苹果官方扩展指南https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/Share.html#//apple_ref/doc/uid/TP40014214-CH12-SW1 工作,但有些事情没有按预期工作。我知道它只是处于测试阶段,但也许我只是做错了什么。

这是我在扩展 ViewController 中从 safari 获取 URL 的代码:

-(void)viewDidAppear:(BOOL)animated
NSExtensionContext *myExtensionContext = [self extensionContext];
NSArray *inputItems = [myExtensionContext inputItems];
NSMutableString* mutableString = [[NSMutableString alloc]init];
for(NSExtensionItem* item in inputItems)
    NSMutableString* temp = [NSMutableString stringWithFormat:@"%@, %@, %lu, 
           %lu - ",item.attributedTitle,[item.attributedContentText string],
           (unsigned long)[item.userInfo count],[item.attachments count]];

    for(NSString* key in [item.userInfo allKeys])
        NSArray* array = [item.userInfo objectForKey:@"NSExtensionItemAttachmentsKey"];
        [temp appendString:[NSString stringWithFormat:@" in array:%lu@",[array count]]];   
    
    [mutableString appendString:temp];

self.myLabel.text = mutableString;

这是我的扩展的 Info.plist 文件的内容:

<dict>
    <key>NSExtensionMainStoryboard</key>
    <string>MainInterface</string>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.share-services</string>
    <key>NSExtensionActivationRule</key>
    <dict>
        <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
        <integer>200</integer>
    </dict>
</dict>

当我在 Safari 中访问苹果 iPod 支持页面并尝试将其共享到我的扩展程序时,我得到以下值但没有 URL:

item.attributedTitle = (null)
item.attributedContentText = "iPod - Apple Support"
item.userInfo.count = 2 (two keys: NSExtensionAttributedContentTextKey and
    NSExtensionItemAttachmentsKey)
item.attachments.count = 0

字典对象内的数组总是空的。

当我与系统邮件应用程序共享苹果网站时,URL 会发布到邮件中。那么为什么我的扩展程序中没有 URL?

【问题讨论】:

【参考方案1】:

以下是获取网址的方法。请注意,类型标识符是 kUTTypeURL,块参数是 NSURL。此外,plist 也需要像我的一样正确。缺少文档并从number4 on the Apple dev forums 获得帮助。 (您需要注册并登录才能看到它)。

代码:

NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider = item.attachments.firstObject;
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]) 
    [itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(NSURL *url, NSError *error) 
        self.urlString = url.absoluteString;
    ];

Info.plist

<key>NSExtension</key>
<dict>
    <key>NSExtensionAttributes</key>
    <dict>
        <key>NSExtensionActivationRule</key>
        <dict>
            <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
            <integer>1</integer>
        </dict>
        <key>NSExtensionPointName</key>
        <string>com.apple.share-services</string>
        <key>NSExtensionPointVersion</key>
        <string>1.0</string>
    </dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.share-services</string>
    <key>NSExtensionMainStoryboard</key>
    <string>MainInterface</string>
</dict>

【讨论】:

我从来没有被管理过的completionHandler 在没有用户界面的共享扩展中正常工作。最终使用了基于 javascript 的解决方法 - 请参阅我的 answer。【参考方案2】:

我自己解决了。我正在尝试共享图像。

- (void)didSelectPost 


// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.

// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.

// Verify that we have a valid NSExtensionItem
NSExtensionItem *imageItem = [self.extensionContext.inputItems firstObject];
if(!imageItem)
    return;


// Verify that we have a valid NSItemProvider
NSItemProvider *imageItemProvider = [[imageItem attachments] firstObject];
if(!imageItemProvider)
    return;


// Look for an image inside the NSItemProvider
if([imageItemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
    [imageItemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) 
        if(image)
            NSLog(@"image %@", image);
            // do your stuff here...

        
    ];
    
// this line should not be here. Cos it's called before the block finishes.
// and this is why the console log or any other task won't work inside the block
[self.extensionContext completeRequestReturningItems:nil completionHandler:nil];


所以我所做的只是移动了 [self.extensionContext completeRequestReturningItems:nil completionHandler:nil];在其他任务结束时的块内。最终的工作版本如下所示(Xcode 6 beta 5 on Mavericks OS X 10.9.4):

- (void)didSelectPost 


// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.

// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.

// Verify that we have a valid NSExtensionItem
NSExtensionItem *imageItem = [self.extensionContext.inputItems firstObject];
if(!imageItem)
    return;


// Verify that we have a valid NSItemProvider
NSItemProvider *imageItemProvider = [[imageItem attachments] firstObject];
if(!imageItemProvider)
    return;


// Look for an image inside the NSItemProvider
if([imageItemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage])
    [imageItemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) 
        if(image)
            NSLog(@"image %@", image);
            // do your stuff here...

            // complete and return
            [self.extensionContext completeRequestReturningItems:nil completionHandler:nil];       
        
    ];
    
// this line should not be here. Cos it's called before the block finishes.
// and this is why the console log or any other task won't work inside the block
// [self.extensionContext completeRequestReturningItems:nil completionHandler:nil];


我希望它也适用于 URL 共享。

【讨论】:

是的,它有效,谢谢!我还与默认的 ShareViewController 作斗争,并注意到 didSelectPost 中的任何 loadItemForTypeIdentifier 都被忽略了。但调用 [self.extensionContext completeRequestReturningItems:@[] completionHandler:nil];毕竟 loadItemForTypeIdentifier 中的所有块都成功了 酷。很高兴听到。 :) 另外,请确保在处理完项目提供程序之前不要调用 [super didSelectPost]。它的实现调用 completeRequestReturningItems....【参考方案3】:

您的扩展视图控制器应该采用NSExtensionRequestHandling 协议。该协议的方法之一是:

- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context

在尝试获取NSExtensionContext 之前,您应该等待调用它。它甚至将方法中的上下文作为context 参数提供。

this document 对此进行了概述。

【讨论】:

还是不行。即使提供了 NSExtensionContext 对象,也没有 URL。 这不应该是必要的,因为对 beginRequestWithExtensionContext 的调用发生在 loadView developer.apple.com/library/prerelease/ios/documentation/… 之前:【参考方案4】:

所有之前的答案都非常好,但我只是在 Swift 中遇到了这个问题,觉得从给定的 NSExtensionContext 中提取 URL 有点麻烦,尤其是在 CFStringString 的转换过程中事实上loadItemForTypeIdentifier中的completionHandler并没有在主线程中执行。

import MobileCoreServices

extension NSExtensionContext 
  private var kTypeURL:String 
      get 
          return kUTTypeURL as NSString as String
      
  

  func extractURL(completion: ((url:NSURL?) -> Void)?) -> Void 
      var processed:Bool = false

      for item in self.inputItems ?? [] 
          if  let item = item as? NSExtensionItem,
              let attachments = item.attachments,
              let provider = attachments.first as? NSItemProvider
              where provider.hasItemConformingToTypeIdentifier(kTypeURL) == true 
                  provider.loadItemForTypeIdentifier(kTypeURL, options: nil, completionHandler:  (output, error) -> Void in
                      dispatch_async(dispatch_get_main_queue(),  () -> Void in
                          processed = true
                          if let url = output as? NSURL 
                              completion?(url: url)
                          
                          else 
                              completion?(url: nil)
                          
                      )

                  )
          
      

      // make sure the completion block is called even if no url could be extracted
      if (processed == false) 
          completion?(url: nil)
      
  

这样你现在可以在你的UIViewController 子类中像这样简单地使用它:

self.extensionContext?.extractURL( (url) -> Void in
    self.urlLabel.text = url?.absoluteString
    println(url?.absoluteString)
)

【讨论】:

你的解决方案对我来说看起来很优雅,我刚刚摆脱了我在最后一天自己编写的代码并采用了你的。谢谢!我有几个问题,如果可以的话: 1. 你会在 isContentValid() 或 viewDidAppear 中调用 extractURL 吗? isContentValid() 对我来说似乎是正确的地方,但是每次用户在扩展的文本字段中键入或删除字符时都会调用该方法... 2. 您是否尝试过从 Pocket 应用程序共享代码?您的扩展程序和我之前的代码都无法从 Pocket 列表中的项目中提取 url,而 Messages 或 Mail 扩展程序可以。知道为什么吗? 另一个注意事项:只为我检索 kUTTypeURL 仅适用于某些应用程序,对于其他应用程序,我必须为“public.url”添加另一个 where provider.hasItemConformingToTypeIdentifier()。不过,不适用于 Pocket 应用程序或 Ebay 应用程序(我尝试过的 30 个左右) 我已经把它放在viewDidAppear 中,但你是对的,把它放在isContentValid() 中会在每次更新时触发代码。我不确定这里是否有最佳选择,但我现在会坚持使用viewDidAppear。至于其他应用程序,我没有尝试过很多。如果他们不使用kUTTypeURL,我猜你已经转储了整个上下文以查看他们返回的内容。 tinkl 的 swift 代码看起来可以用更少的代码解决它。这种方法有什么问题吗? 这不会从谷歌浏览器中提取网址,有什么想法吗?【参考方案5】:

其他答案都是复杂且不完整的。它们仅适用于 Safari,不适用于 Google Chrome。这适用于 Google Chrome 和 Safari:

override func viewDidLoad() 
    super.viewDidLoad()

    for item in extensionContext!.inputItems 
        if let attachments = item.attachments 
            for itemProvider in attachments! 
                itemProvider.loadItemForTypeIdentifier("public.url", options: nil, completionHandler:  (object, error) -> Void in
                    if object != nil 
                        if let url = object as? NSURL 
                        print(url.absoluteString) //This is your URL
                        
                    
                )
            
        
    

【讨论】:

【参考方案6】:

您需要寻找 kUTTypePropertyList 类型的附件。对扩展中第一个扩展项的第一个附件执行类似的操作:

NSExtensionItem *extensionItem = self.extensionContext.extensionItems.firstObject;
NSItemProvider *itemProvider =  self.extensionItem.attachments.firstObject;
[itemProvider loadItemForTypeIdentifier:kUTTypePropertyList options:nil
  completionHandler:^(NSDictionary *item, NSError *error) 
   // Unpack items from "item" in here
];

您还需要设置一些 JavaScript 来从 DOM 中获取您需要的所有数据。有关详细信息,请参阅 WWDC 14 的第二次扩展会议。

【讨论】:

你不需要Javascript,看我的回答***.com/a/24225678/1672161 似乎 Javascript 方法是没有用户界面的共享扩展的唯一工作方式。见我的answer。【参考方案7】:
let itemProvider = item.attachments?.first as! NSItemProvider
itemProvider.loadItemForTypeIdentifier("public.url", options: nil)  (object, error) -> Void in
     println(object)

所以 println : http://detail.m.tmall.com/item.htm?id=38131345289&spm=a2147.7632989.mainList.5

【讨论】:

这不会从谷歌浏览器中提取网址,有什么想法吗?

以上是关于IOS8 beta 中的共享扩展的主要内容,如果未能解决你的问题,请参考以下文章

在 iOS8 中如何设置私有文档目录作为存储提供程序应用程序扩展正在使默认文档目录共享?

在 iOS8 中的 App 和 Extension 之间共享 Core Data 堆栈和数据

在IOS8中使用共享扩展时如何获取源应用程序的bundleId

确定使用了哪个共享扩展

iOS 8 beta 5 中的今日扩展的 NSUserDefaults 是不是被破坏?

带有 App Group 的 NSUserDefault 在 iOS 8 Beta3 中不起作用