数据与代码的关系一直都让人好奇。特定的编程语言,如 Lisp、lo 和 Mathematica 都是同像性的(homoiconic),意味着它们的代码可作为数据原语呈现,也就是说它们自身就可在代码中被操纵。许多其他语言,包括 Objective-C ,就不同了,在两者之间建立了严格的界限,回避 eval() 和其它潜在的危险的动态指示加载方法。
当问题中的数据过大或难以表示为除了字节流之外的任何东西时,那么代码与数据的这种紧张关系就达到了一个新的高度。关于“如何编码、解码以及解释图像、文档和媒体的二进制表示”的问题从最开始的操作系统开始就一直存在着。
OS X 的 Core Services 框架与 iOS 的移动 Core Services 框架都提供函数通过通用类型标识符(Universal Type Identifiers,即UTI)来根据文件扩展和MIME类型识别和分类数据类型。UTI提供了可扩展和可继承的分类系统,它能给予开发人员极大的灵活性,即使是处理最奇特的文件类型。例如,一个 Ruby 源代码文件(.rb)被分类为 Ruby 源代码 > 源代码 > 文本 > 内容 > 数据;一个 QuickTime 电影文件(.mov)被分类为视频 > 电影 > 试听内容 > 内容 > 数据;
在桌面文件系统抽象里,UTI工作得相当好。然而,在一个移动范式里,文件和目录对于用户来说都被隐藏了,于是这很快就失效了。而且,更重要的是,云服务和社交媒体的兴起已经让远程实体比本地文件具有更重要的地位。因此,UTI和URL之间出现了紧张关系。
很明显我们需要其它的某种东西。那 UIActivityViewController 能成为我们拼命追求的解决办法吗?
UIActivityViewController ,出现于 iOS 6,在应用里为分享和操作数据提供了一个统一的服务接口。
给出一个可操作数据的集合,那一个 UIActivityViewController 实例就可如下创建:
1 2 3 4 5 6 7 8 9 10 11 |
NSString *string = ...; NSURL *URL = ...; UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[string, URL] applicationActivities:nil]; [navigationController presentViewController:activityViewController animated:YES completion:^{ // ... }]; |
这将在屏幕的底部呈现如下所示的东西:
默认情况下,UIActivityViewController 将显示所有可用于所提供内容的服务,但我们也可以排除特定的 Activity 类型。
1 |
activityViewController.excludedActivityTypes = @[UIActivityTypePostToFacebook]; |
Activity 类型又分为“操作”和“分享”两大类:
UIActivityCategoryAction
- UIActivityTypePrint
- UIActivityTypeCopyToPasteboard
- UIActivityTypeAssignToContact
- UIActivityTypeSaveToCameraRoll
- UIActivityTypeAddToReadingList
- UIActivityTypeAirDrop
UIActivityCategoryShare
- UIActivityTypeMessage
- UIActivityTypeMail
- UIActivityTypePostToFacebook
- UIActivityTypePostToTwitter
- UIActivityTypePostToFlickr
- UIActivityTypePostToVimeo
- UIActivityTypePostToTencentWeibo
- UIActivityTypePostToWeibo
每个 Activity 类型都支持好多种不同的数据类型。例如,一条 Tweet 可能由 NSString 以及一个附加的图像 和/或 URL 所组成。
不同的 Activity 类型所支持的数据类型
<UIActivityItemSource> & UIActivityProvider
类似于一个剪贴板条目只在必要时才提供数据,为了避免过多的内存分配或处理时间, Activity 条目可以是自定义类型。
<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
除了上述系统提供的 Activity ,你也可以自己创建 Activity。
作为例子,让我们创建一个自定义 Activity 类型,它能接受一个图片 URL 并使用 mustache.me 为其安上一瞥胡子。
之后
首先,我们为 Activity 类型定义一个反向DNS标识符(reverse-DNS identifier),指定类别为 UIActivityCategoryAction ,然后提供一个本地化的标题和一个合适于iOS版本的图像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
static NSString * const HIPMustachifyActivityType = @"com.nshipster.activity.Mustachify"; #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]]]; } |
我们的网络服务提供了好几种胡子选项,它们定义在一个 NS_ENUM 中:
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]; |