在IOS8 beta中的分享扩展功能

13

我正在尝试使用新的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?

7个回答

13
以下是获取URL的方法。请注意,类型标识符为kUTTypeURL,块参数为NSURL。此外,plist文件也需要像我的一样正确。文档不够完善,我从苹果开发者论坛上的number4获得了帮助。(您需要注册并登录才能查看)。
代码:
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的解决方法 - 请参阅我的 答案 - xZenon

9

我已经解决了。原因是我尝试使用共享图像。

- (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];移动到其他任务结束的块内部。最终可行版本看起来像这样(在Mavericks OS X 10.9.4上使用Xcode 6 beta 5):

- (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共享。


1
是的,它有效了,谢谢!我也曾经苦于默认的ShareViewController,并注意到在didSelectPost中的任何loadItemForTypeIdentifier都被简单地忽略了。但是,在loadItemForTypeIdentifier的所有块之后调用[self.extensionContext completeRequestReturningItems:@ [] completionHandler:nil]; 就解决了问题。 - IPv6
很酷。听到这个消息很好。 :) - Adnan
1
此外,请确保在处理项目提供者完成之前不要调用 [super didSelectPost]。其实现会调用 completeRequestReturningItems.... - Dan Loughney

3
您的扩展视图控制器需要采用 NSExtensionRequestHandling 协议。该协议的一个方法为:
- (void)beginRequestWithExtensionContext:(NSExtensionContext *)context

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

这在这个文档中有详细说明。


仍然无法工作。即使提供了NSExtensionContext对象,也没有URL。 - Masalis
这应该是不必要的,因为对 beginRequestWithExtensionContext 的调用发生在 loadView 之前。https://developer.apple.com/library/prerelease/ios/documentation/Foundation/Reference/NSExtensionRequestHandling_Protocol/index.html#//apple_ref/occ/intfm/NSExtensionRequestHandling/beginRequestWithExtensionContext: - Oren

3

之前的回答都非常好,但我在Swift中遇到了这个问题,感觉从给定的NSExtensionContext中提取URL有点繁琐,特别是在CFString转换为String的过程中,以及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扩展可以。任何想法为什么? - cdf1982
另外需要注意的是:仅检索 kUTTypeURL 对于某些应用程序有效,但对于其他应用程序,我必须添加另一个 where provider.hasItemConformingToTypeIdentifier() 以获取“public.url”。然而,在 Pocket 应用程序或 Ebay 应用程序中(我尝试了大约 30 个应用程序),仍然无法正常工作。 - cdf1982
1
我将那个代码段放置在了 viewDidAppear 中,但你说得对将它放在 isContentValid() 中会在每次更新时触发该代码。我不确定哪种选择最好,但现在我会坚持使用 viewDidAppear。至于其他应用程序,我还没有尝试过很多。如果它们不使用 kUTTypeURL,我猜你必须倾倒整个上下文以查看它们的返回值。 - apouche
Tinkl提供的Swift代码似乎可以用更少的代码解决这个问题。这种方法有什么问题吗? - Esqarrouth
这个程序似乎无法从Google Chrome中获取URL,你有什么想法吗? - Esqarrouth

3
其他答案都比较复杂且不完整,它们只在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
                        }
                    }
                })
            }
        }
    }
}

1

这个程序不能从Google Chrome中获取链接,有什么想法吗? - Esqarrouth

1
你需要查找类型为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,可以看看我的回答https://dev59.com/zGAg5IYBdhLWcg3wBXAR#24225678 - abc123
看起来JavaScript是没有用户界面的共享扩展的唯一有效方法。请参阅我的回答 - xZenon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接