0%

UIActivity​View​Controller 详解

翻译自 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:^{
// ...
}];

默认情况下, 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

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

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 相比糟糕得多。