翻译自NSHipster的UIActivity​View​Controller

一直非常好奇代码和数据之间的联系。

某些编程语言,例如LispIoMathematica他们是同型性(代码即数据),那就意味着它们的代码是原始数据的呈现形式,它们本身在代码中也能够被维护。然而大多数其它语言,包括Objective-C,然而,在这两者之间有严格的边界,避免eval()和其它有动态引导加载的潜在危险方法。

当数据出现太大和呈现一些东西过于笨重除了使用二进制流的问题时代码和数据之间的不安被推向到了一个全新的高度。自从第一个操作系统出现开始,怎样编码,解码,解释二进制代表的图片,文档,和媒体一直是一个问题。

在OS X上的核心服务框架和iOS上提供标识功能和通过文件拓展和MIMIE type–根据通用数据标示,给数据类型的分类核心服务框架。UTIs提供了一种可拓展,阶梯分层系统,这给开发者在处理大量不同的文件类型提供了巨大的灵活性。例如,一个Ruby的源文件(.rb)被归类为:Ruby Source Code > Source Code > Text > Content > Data;一个QuickTime电影文件(.mov)被归类为 Video > Movie > Audiovisual Content > Content > Data。

UTIs表现得非常好在桌面的抽象文件系统中。然而,在一个移动范例中这就崩溃得很快,因为文件和目录是对用户隐藏的。还有,随着云服务和社交媒体扮演着越来越重要的角色在远程实体通过本地文件。因此,UTIs和URLs之间很紧张。

我们非常清楚我们需要一些别的东西。UIActivityViewController能否成为我们正在急切寻找的解决方案呢?


UIActivityViewController,在iOS6中被引入,提供了一套分享和在应用内执行数据操作的统一服务界面。

考虑到可操作的数据集合,一个UIActivityViewController实例通过如下方式创建:

1
2
3
4
5
6
7
8
9
10
NSString *string = ...;
NSURL *URL = ...;

UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[string, URL]
                                    applicationActivities:nil];
[navigationController presentViewController:activityViewController
                                   animated:YES
                                 completion:^{
  // ...
}];

这将在屏幕底部呈现如下界面:

activity sharing img

默认情况下,UIActivityViewController将会显示所有支持的可用服务类型,但是某些活动类型也可以排除:

1
activityViewController.excludedActivityTypes = @[UIActivityTypePostToFacebook];

活动类型被划分“动作”(action)和“分享”(share)类型:

UIActivityCategoryAction

  • UIActivityTypePrint
  • UIActivityTypeCopyToPasteboard
  • UIActivityTypeAssignToContact
  • UIActivityTypeSaveToCameraRoll
  • UIActivityTypeAddToReadingList
  • UIActivityTypeAirDrop

UIActivityCategoryShare

  • UIActivityTypeMessage
  • UIActivityTypeMail
  • UIActivityTypePostToFacebook
  • UIActivityTypePostToTwitter
  • UIActivityTypePostToFlickr
  • UIActivityTypePostToVimeo
  • UIActivityTypePostToTencentWeibo
  • UIActivityTypePostToWeibo

每种活动类型,支持一系列不同的数据类型。例如,一个Tweet可能由一个NSString并附带一个image和或者URL

不同活动类型支持的数据类型

Activity Type String Attributed String URL Data Image Asset Other
Post To Facebook
Post To Twitter
Post To Weibo
Message ✓* ✓* sms:// NSURL
Mail ✓+ ✓+ ✓+
Print ✓+ ✓+ UIPrintPageRenderer, UIPrintFormatter, & UIPrintInfo
Copy To Pasteboard UIColor, NSDictionary
Assign To Contact
Save To Camera Roll
Add To Reading List
Post To Flickr
Post To Vimeo
Post To Tencent Weibo
AirDrop

UIActivityItemSource & UIActivityItemProvider

当必要时和一个粘贴板项目能用来提供的数据类似,为了避免过度的内测开销或处理时间,活动类型可以是一种自定义类型。

任何遵循<UIActivityItemSource>协议的对象,包括内构UIActivityItemProvider类,可以用来动态提供不同类型的数据根据活动的类型。

UIActivityItemSource

获取数据项目:

  • activityViewControllerPlaceholderItem:
  • activityViewController:itemForActivityType:

提供数据项目信息:

  • activityViewController:subjectForActivityType:
  • activityViewController:dataTypeIdentifierForActivityType:
  • activityViewController:thumbnailImageForActivityType:suggestedSize:

下面是一个例子,根据是否分享到FaceBook或者Twitter来展示自定义一条消息是怎样使用的:

1
2
3
4
5
6
7
8
9
10
11
- (id)activityViewController:(UIActivityViewController *)activityViewController
         itemForActivityType:(NSString *)activityType
{
    if ([activityType isEqualToString:UIActivityTypePostToFacebook]) {
        return NSLocalizedString(@"Like this!");
    } else if ([activityType isEqualToString:UIActivityTypePostToTwitter]) {
        return NSLocalizedString(@"Retweet this!");
    } else {
        return nil;
    }
}

创建一个自定义UIActivity

除了上述的系统提供的活动,你也可以创建你自己的活动。

例如,让我们创建一个自定义活动类型,它能使一个URL中的图片添加上胡须通过使用mustache.me

Before:

Before

After:

After

首先,我们定义一个反向域名解析的标识符给活动类型:

1
static NSString * const HIPMustachifyActivityType = @"com.nshipster.activity.Mustachify";

然后指定它的分类为UIActivityCategoryAction并且提供一个本地化标题和iOS版本相应的图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma mark - UIActivity

+ (UIActivityCategory)activityCategory {
    return UIActivityCategoryAction;
}

- (NSString *)activityType {
    return HIPMustachifyActivityType;
}

- (NSString *)activityTitle {
    return NSLocalizedString(@"Mustachify", nil);
}

- (UIImage *)activityImage {
    if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_6_1) {
        return [UIImage imageNamed:@"MustachifyUIActivity7"];
    } else {
        return [UIImage imageNamed:@"MustachifyUIActivity"];
    }
}

下一步,我们创建一个辅助函数,HIPMatchingURLsInActivityItems,通过它返回一个支持类型的图片URL的数组。

1
2
3
4
5
6
7
8
9
10
11
12
static NSArray * HIPMatchingURLsInActivityItems(NSArray *activityItems) {
    return [activityItems filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:
    ^BOOL(id item, __unused NSDictionary *bindings) {
        if ([item isKindOfClass:[NSURL class]] &&
            ![(NSURL *)item isFileURL]) {
            return [[(NSURL *)item pathExtension] caseInsensitiveCompare:@"jpg"] == NSOrderedSame ||
            [[(NSURL *)item pathExtension] caseInsensitiveCompare:@"png"] == NSOrderedSame;
        }

        return NO;
    }]];
}

这个函数让后被用在-canPerformWithActivityItems:prepareWithActivityItems:来获取小胡子的第一张PNG或JPEG图片的URL,假如有的话。

1
2
3
4
5
6
7
8
9
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems {
    return [HIPMatchingURLsInActivityItems(activityItems) count] > 0;
}

- (void)prepareWithActivityItems:(NSArray *)activityItems {
    static NSString * const HIPMustachifyMeURLFormatString = @"http://mustachify.me/%d?src=%@";

    self.imageURL = [NSURL URLWithString:[NSString stringWithFormat:HIPMustachifyMeURLFormatString, self.mustacheType, [HIPMatchingURLsInActivityItems(activityItems) firstObject]]];
}

我们的网站服务提供了各种胡须选项,它被定义在一个枚举中:

1
2
3
4
5
6
7
8
typedef NS_ENUM(NSInteger, HIPMustacheType) {
    HIPMustacheTypeEnglish,
    HIPMustacheTypeHorseshoe,
    HIPMustacheTypeImperial,
    HIPMustacheTypeChevron,
    HIPMustacheTypeNatural,
    HIPMustacheTypeHandlebar,
};

最终,我们提供了一个UIViewController来显示图片。在这个例子中,一个简单的UIWebView控制器就够用了。

1
2
3
4
5
6
7
8
9
10
11
12
@interface HIPMustachifyWebViewController : UIViewController <UIWebViewDelegate>
@property (readonly, nonatomic, strong) UIWebView *webView;
@end

- (UIViewController *)activityViewController {
    HIPMustachifyWebViewController *webViewController = [[HIPMustachifyWebViewController alloc] init];

    NSURLRequest *request = [NSURLRequest requestWithURL:self.imageURL];
    [webViewController.webView loadRequest:request];

    return webViewController;
}

为了使用我们加了新胡须的活动,我们简单把它传递到UIActivityViewController构造器中:

1
2
3
4
HIPMustachifyActivity *mustacheActivity = [[HIPMustachifyActivity alloc] init];
UIActivityViewController *activityViewController =
  [[UIActivityViewController alloc] initWithActivityItems:@[imageURL]
                                    applicationActivities:@[mustacheActivity]];

手动执行动作

现在是一个提醒的好时机,当UIActivityViewController允许用户执行他们选择的动作,分享也能通过手动执行,当场景出现时。

所有为了完整性,下面是怎样手动执行这些动作示例:

Open URL

1
2
NSURL *URL = [NSURL URLWithString:@"http://nshipster.com"];
[[UIApplication sharedApplication] openURL:URL];

系统支持的URL协议包括:mailto:tel:sms:maps:

添加到Safari阅读列表

1
2
3
4
5
6
7
@import SafariServices;

NSURL *URL = [NSURL URLWithString:@"http://nshipster.com/uiactivityviewcontroller"];
[[SSReadingList defaultReadingList] addReadingListItemWithURL:URL
                                                        title:@"NSHipster"
                                                  previewText:@"..."
                                                        error:nil];

保存到相册

1
2
3
4
5
UIImage *image = ...;
id completionTarget = self;
SEL completionSelector = @selector(didWriteToSavedPhotosAlbum);
void *contextInfo = NULL;
UIImageWriteToSavedPhotosAlbum(image, completionTarget, completionSelector, contextInfo);

发送 SMS

1
2
3
4
5
6
7
8
9
@import MessageUI;

MFMessageComposeViewController *messageComposeViewController = [[MFMessageComposeViewController alloc] init];
messageComposeViewController.messageComposeDelegate = self;
messageComposeViewController.recipients = @[@"mattt@nshipster•com"];
messageComposeViewController.body = @"Lorem ipsum dolor sit amet";
[navigationController presentViewController:messageComposeViewController animated:YES completion:^{
    // ...
}];

发送 Email

1
2
3
4
5
6
7
8
9
10
@import MessageUI;

MFMailComposeViewController *mailComposeViewController = [[MFMailComposeViewController alloc] init];
mailComposeViewController.mailComposeDelegate = self;
[mailComposeViewController setToRecipients:@[@"mattt@nshipster•com"]];
[mailComposeViewController setSubject:@"Hello"];
[mailComposeViewController setMessageBody:@"Lorem ipsum dolor sit amet" isHTML:NO];
[navigationController presentViewController:mailComposeViewController animated:YES completion:^{
    // ...
}];

发送 Tweet

1
2
3
4
5
6
7
8
9
@import Social;

SLComposeViewController *tweetComposeViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
[tweetComposeViewController setInitialText:@"Lorem ipsum dolor sit amet."];
[self.navigationController presentViewController:tweetComposeViewController
                                        animated:YES
                                      completion:^{
                                          // ...
                                      }];

IntentKit

当所有的这些都是印象深刻和非常有用,它在iOS中的活动范例特别缺乏,当和在Android中的丰富的Intents Model相比。

在Android中,apps能够注册不同的intents,来指示它们能够用在Maps,或者充当一个浏览器,能够被涉及到相关活动的默认app选中,比如指引方向,或者一个书签的URL。

然而iOS缺乏来支持这些的可拓展的基础架构,有一个叫IntentKit第三方库,作者@lazerwalker(因f*ingblocksyntax.com成名),这是一个有趣的例子关于我们怎样缩小这方面的差距。

IntentKit demo

通常,一个开发者可能已经做了很多第一步的工作,来决定是否某个app是否安装,然后怎样构造一个URL来支持某个特殊的活动。

IntentKit加强了连结到大多数流行的Web,Maps,Mail,Twitter,Facebook,和Google+客户的的逻辑,以和UIActivityViewController相似的UI。

任何寻找提升他们的分享体验到下一个级别的人都应该仔细看看这个。

在iOS长期生存能力作为一个平台取决于像UIActivityViewController类型的分享机制存上在很大的争论。正如俗语所言:“资讯应该免费”。任何阻碍方式统一的东西,最终都会失去一些没有的东西。

将来的景象是公共的远程视图控制器APIs带给我希望,对于将来在iOS平台上的分享功能。然而现在我们做的和UIActivityViewController相比糟糕得多。

Comments