在这种情况下,我如何改变 UIActivityViewController 使用的项目?

How can I vary the items used by UIActivityViewController in this scenario?

我有一个带有分享按钮的应用程序。我想根据 activity 类型自定义共享的内容。例如,消息可能会获取图像和文本,而 AirDrop 只会获取文件。

我实际上已经完美地工作了,我使用的代码在 iOS 到 iOS 10 的每个版本中都运行良好。但我意识到我是 return在我不应该的地方使用 nil,所以我想弄清楚如何解决这个问题。

我做这样的事情来设置我的 activity 视图控制器:

JUNActivityProvider *fileProvider = [[JUNActivityProvider alloc] initWithPlaceholderItem:[NSObject new]];
fileProvider.objectID = objectID;
fileProvider.fileURL = fileURL;

JUNActivityProvider *textProvider = [[JUNActivityProvider alloc] initWithPlaceholderItem:[NSString new]];
textProvider.objectID = objectID;

...

UIActivityViewController *activityController = [[UIActivityViewController alloc]
    initWithActivityItems:@[fileProvider,imageProvider,textProvider,urlProvider,printFormatter]
    applicationActivities:nil];

然后在 JUNActivityProvider 中,我有一个 item 方法可以根据 activityType:

自定义 return 值
- (id)item {

    if (self.fileURL) {

        if ([self.activityType isEqualToString:UIActivityTypeAirDrop]) {

            // Create the file
            return url;

        }

    } else if ([self.placeholderItem isKindOfClass:[UIImage class]]) {

        if ([self.activityType isEqualToString:UIActivityTypeAirDrop] == NO &&
            [self.activityType isEqualToString:UIActivityTypeMail] == NO &&
            [self.activityType isEqualToString:UIActivityTypePrint] == NO) {

            // Create the image
            return image;

        }

    } else if ([self.placeholderItem isKindOfClass:[NSString class]]) {

        if ([self.activityType isEqualToString:UIActivityTypeMail]) {

            return @"example one";

        } else if ([self.activityType isEqualToString:UIActivityTypeMessage] ||
            [self.activityType isEqualToString:UIActivityTypeCopyToPasteboard]) {

            return @"example two";

        }

    }

    return nil;

}

最后那个returnreturn nil就是问题所在。它工作正常并且完全符合我的要求——当它为 nil 时,该项目不被共享。书面文档没有说它必须 return 一个值,但头文件确实如此:

- (nonnull id)item; // called on secondary thread when user selects an activity. you must subclass and return a non-nil value.

我不想在需要非空值时 returning nil 冒崩溃的风险,所以我需要解决这个问题。据我所知,我唯一的选择是停止使用 UIActivityItemProvider,而是自己实现 UIActivityItemSource 协议。该协议包括方法 activityViewController:itemForActivityType:,它清楚地表明你 can return nil there:

May be nil if multiple items were registered for a single activity type, so long as one of the items returns an actual value.

完美。但问题是:activityViewController:itemForActivityType: 在主线程上被调用,这导致我的一个项目出现问题。以下是正在发生的事情的摘要:

  1. 我需要异步调用一些 运行 的方法。为了解决这个问题,我尝试使用调度信号量。这使得方法从 returning 直到我有机会设置 return 值。
  2. 由于在主线程上调用了 activityViewController:itemForActivityType:,因此它在工作时会被锁定。
  3. 我需要将 UIView 绘制到图像中。如果我尝试在主线程上执行该工作,则在信号量超时之前什么也不会发生。但是如果我不在主线程上执行它,它就会崩溃。

我不知道如何处理这个问题。基本上我需要在我准备好之前保持方法从 returning 开始,但是我不能锁定主线程,因为我需要在那里做一些工作。这好像……不可能?有什么办法可以做到这一点吗?

提交 enhancement request 后,我正准备放弃并选择 returning nil[NSNull null]。但后来我意识到这个问题绝对有解决办法。

虽然 UIActivityItemProvider 包含了一系列自己的功能,但它仍然在很大程度上实现了 UIActivityItemSource 协议。我知道。我没有考虑的是,这意味着我可以在适当的时候覆盖 activityViewController:itemForActivityType: 和 return nil there

所以我的 item 方法的最后一行现在看起来像这样:

return self.placeholderItem;

您也可以在此处 return [NSNull null] 或任何对象。我选择了 placeholderItem 是因为它看起来更安全一些 — 至少我知道它是 returning 一个预期类型的​​对象,以防实现发生任何变化。

然后我所要做的就是添加我自己的 activityViewController:itemForActivityType: 实现(我们 允许 return 零):

- (nullable id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(UIActivityType)activityType {
    id item = [super activityViewController:activityViewController itemForActivityType:activityType];
    if ([item isEqual:self.placeholderItem]) return nil;
    return item;
}

只需调用 super 即可获取该项目,return nil 如果您不想包含该项目,或者 return 如果是该项目。请注意,如果您的 placeholderItem 可能永远等于您实际上 想要分享的东西,您将需要稍微更改此实现 — 但相同的基本概念应该有效。