UIActivityViewController屬性和使用?
數據與代碼的關係一直都讓人好奇。特定的編程語言,如 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 實例就可如下創建:
Objective-C
1234567891011 | NSString*string=...;NSURL*URL=...;UIActivityViewController*activityViewController=[[UIActivityViewControlleralloc] initWithActivityItems:@[string,URL] applicationActivities:nil];[navigationController presentViewController:activityViewController animated:YES completion:^{// ...}]; |
這將在屏幕的底部呈現如下所示的東西:
默認情況下,UIActivityViewController 將顯示所有可用於所提供內容的服務,但我們也可以排除特定的 Activity 類型。
Objective-C
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 分別定義。
Objective-C
1234567891011 | -(id)activityViewController:(UIActivityViewController*)activityViewController itemForActivityType:(NSString*)activityType{if([activityType isEqualToString:UIActivityTypePostToFacebook]){returnNSLocalizedString(@"Likethis!");}elseif([activityType isEqualToString:UIActivityTypePostToTwitter]){returnNSLocalizedString(@"Retweetthis!");}else{returnnil;}} |
創建一個自定義 UIActivity
除了上述系統提供的 Activity ,你也可以自己創建 Activity。
作為例子,讓我們創建一個自定義 Activity 類型,它能接受一個圖片 URL 並使用 mustache.me 為其安上一瞥鬍子。
之前
之後
首先,我們為 Activity 類型定義一個反向DNS標識符(reverse-DNS identifier),指定類別為 UIActivityCategoryAction ,然後提供一個本地化的標題和一個合適於iOS版本的圖像:
Objective-C
12345678910111213141516171819202122 | staticNSString*constHIPMustachifyActivityType=@"com.nshipster.activity.Mustachify";#pragma mark - UIActivity+(UIActivityCategory)activityCategory{returnUIActivityCategoryAction;}-(NSString*)activityType{returnHIPMustachifyActivityType;}-(NSString*)activityTitle{returnNSLocalizedString(@"Mustachify",nil);}-(UIImage*)activityImage{if(NSFoundationVersionNumber>NSFoundationVersionNumber_iOS_6_1){return[UIImage imageNamed:@"MustachifyUIActivity7"];}else{return[UIImage imageNamed:@"MustachifyUIActivity"];}} |
接下來,我們創建一個幫助函數,HIPMatchingURLsInActivityItems,它返回一個由任何所支持類型的圖像 URL 組成的數組。
Objective-C
123456789101112 | staticNSArray*HIPMatchingURLsInActivityItems(NSArray*activityItems){return[activityItems filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(iditem,__unused NSDictionary*bindings){if([item isKindOfClass:[NSURLclass]]&&![(NSURL*)item isFileURL]){return[[(NSURL*)item pathExtension] caseInsensitiveCompare:@"jpg"]==NSOrderedSame||[[(NSURL*)item pathExtension] caseInsensitiveCompare:@"png"]==NSOrderedSame;}returnNO;}]];} |
這個函數用於 -canPerformWithActivityItems: 和 prepareWithActivityItems: 以取得第一個 PNG 或 JPEG 的加了鬍子的圖像 URL,如果有的話。
Objective-C
123456789 | -(BOOL)canPerformWithActivityItems:(NSArray*)activityItems{return[HIPMatchingURLsInActivityItems(activityItems)count]>0;}-(void)prepareWithActivityItems:(NSArray*)activityItems{staticNSString*constHIPMustachifyMeURLFormatString=@"http://mustachify.me/%d?src=%@";self.imageURL=[NSURL URLWithString:[NSString stringWithFormat:HIPMustachifyMeURLFormatString,self.mustacheType,[HIPMatchingURLsInActivityItems(activityItems)firstObject]]];} |
我們的網絡服務提供了好幾種鬍子選項,它們定義在一個 NS_ENUM 中:
Objective-C
12345678 | typedefNS_ENUM(NSInteger,HIPMustacheType){HIPMustacheTypeEnglish,HIPMustacheTypeHorseshoe,HIPMustacheTypeImperial,HIPMustacheTypeChevron,HIPMustacheTypeNatural,HIPMustacheTypeHandlebar,}; |
最終,我們提供一個 UIViewController 來顯示圖像。在這個例子裡,一個簡單的 UIWebView 控制器就夠了。
Objective-C
123456789101112 | @interface HIPMustachifyWebViewController : UIViewController<UIWebViewDelegate>@property(readonly,nonatomic,strong)UIWebView*webView;@end-(UIViewController*)activityViewController{HIPMustachifyWebViewController*webViewController=[[HIPMustachifyWebViewControlleralloc] init];NSURLRequest*request=[NSURLRequest requestWithURL:self.imageURL];[webViewController.webView loadRequest:request];returnwebViewController;} |
要使用我們全新的鬍子 Activity,我們簡單地將其傳遞給一個 UIActivityViewController 的初始化函數即可:
Objective-C
1234 | HIPMustachifyActivity*mustacheActivity=[[HIPMustachifyActivityalloc] init];UIActivityViewController*activityViewController=[[UIActivityViewControlleralloc] initWithActivityItems:@[imageURL] applicationActivities:@[mustacheActivity]; |
手動調用操作
現在正是回憶起 “UIActivityViewController 允許用戶執行它們選擇的操作” 的好時機,但當情況需要時,分享依然可以手動調用。
為了完整性,下面就來介紹手動執行這些操作的步驟:
打開 URL
Objective-C
12 | NSURL*URL=[NSURL URLWithString:@"http://nshipster.com"];[[UIApplicationsharedApplication] openURL:URL]; |
系統支持的 URL scheme 包括:mailto://、tel://、sms://、and maps://。
添加到 Safari 閱讀列表
Objective-C
1234567 | @import SafariServices;NSURL*URL=[NSURL URLWithString:@"http://nshipster.com/uiactivityviewcontroller"];[[SSReadingListdefaultReadingList] addReadingListItemWithURL:URL title:@"NSHipster" previewText:@"..." error:nil]; |
保存到相冊
Objective-C
12345 | UIImage*image=...;idcompletionTarget=self;SELcompletionSelector=@selector(didWriteToSavedPhotosAlbum);void*contextInfo=NULL;UIImageWriteToSavedPhotosAlbum(image,completionTarget,completionSelector,contextInfo); |
發送短信
Objective-C
123456789 | @import MessageUI;MFMessageComposeViewController*messageComposeViewController=[[MFMessageComposeViewControlleralloc] init];messageComposeViewController.delegate=self;messageComposeViewController.recipients=@[@"mattt@nshipster•com"];messageComposeViewController.body=@"Loremipsum dolor sit amet";[navigationController presentViewController:messageComposeViewController animated:YES completion:^{// ...}]; |
發送郵件
Objective-C
12345678910 | @import MessageUI;MFMailComposeViewController*mailComposeViewController=[[MFMailComposeViewControlleralloc] init];[mailComposeViewController setToRecipients:@[@"mattt@nshipster•com"]];[mailComposeViewController setSubject:@"Hello"];[mailComposeViewController setMessageBody:@"Loremipsum dolor sit amet" isHTML:NO];[navigationController presentViewController:mailComposeViewController animated:YES completion:^{// ...}]; |
發送推文
Objective-C
12345678910 | @import Twitter;TWTweetComposeViewController*tweetComposeViewController=[[TWTweetComposeViewControlleralloc] init];[tweetComposeViewController setInitialText:@"Loremipsum dolor sit amet."];[self.navigationController presentViewController:tweetComposeViewController animated:YES completion:^{//...}]; |
IntentKit
雖然所有這些都讓人影響深刻也很有用,但在與 Android 上的豐富的 Intent 模型對比之下,iOS 的 Activity 範式中還是有一些特殊的缺失。
在 Android 上,應用可以註冊不同的 Intent,以表面它們可用於地圖或作為瀏覽器,而且能被選擇為相關 Activity 的默認應用,例如導航或將某個URL加入書籤。
雖然 iOS 缺少可擴展的基礎架構來支持這些,但一個第三方的庫,叫做 IntentKit,由 @lazerwalker (有著 f*ingblocksyntax.com 的聲譽) 編寫,它是一個有趣的關於我們如何縮小差距的例子。
正常情況下,在一開始一個開發者就要做許多工作,例如查詢某個特定的應用是否已被安裝,以及構造一個 URL 以支持某個特定的 Activity 等。
IntentKit 合併了連接到這些最流行的服務(如 Web、地圖、郵件、Twitter、Facebook以及Google+這些客戶端)的邏輯,而且它的 UI 非常類似 UIActivityViewController。
任何想將其應用的分享體驗提高一個層次的人都應該研究一下它。
還有一個要發出的有力爭論是,關於 iOS 平臺的長期生存能力取決於如 UIActivityViewController 這樣的分享機制。俗話說,“信息需要自由”。而任何阻擋人民聯盟的事物終將輸給那些不阻擋聯盟的事物。
公開的 遠程視圖控制器(remote view controller) API 的未來前景給了我關於iOS上分享的未來希望。雖然對現在來說,我們當然可以做得比 UIActivityViewController 更差。(譯者注:是這個意思嗎? For now, though, we could certainly do much worse than UIActivityViewController.)