經過UIActivityViewController實現更多分享服務

前言

我在經過UIDocumentInteractionController預覽和分享"史蒂夫•喬布斯傳"這篇文章中,詳細講了UIDocumentInteractionController的用途和使用方法。而在iOS 6 SDK中,蘋果提供了UIActivityViewController來讓咱們可使用更多地服務。這篇文章,我就來介紹一下怎麼經過UIActivityViewController實現更多地服務。c++

簡介

打開UIActivityViewController的API文檔,咱們能夠看到UIActivityViewController的聲明。git

NS_CLASS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED @interface UIActivityViewController : UIViewController

咱們能夠看出UIActivityViewController是在iOS 6開始支持的,一樣是不能在Apple TV的開發中使用。並且UIActivityViewController是直接繼承UIViewController的,這意味着咱們須要本身來展現和解散視圖。github

準備

我使用在UIDocumentInteractionController測試中使用的Demo,GitHub地址是:ZSDocumentInteractionTest。而後添加一個新的Button做爲UIActivityViewController的觸發事件,運行程序,就能夠看到下面的界面啦(過程自行想象,哈哈)數組

Screen Shot 2015-12-31 at 15.17.52.png

初始化

接着咱們在Button的觸發方法裏面開始操做UIActivityViewController來提供服務。首先,咱們須要初始化一個UIActivityViewController的實例,UIActivityViewController提供了一個初始化方法:網絡

- (instancetype)initWithActivityItems:(NSArray *)activityItems applicationActivities:(nullable NSArray<__kindof UIActivity *> *)applicationActivities NS_DESIGNATED_INITIALIZER;

官方文檔對這倆個參數有詳細的解釋:app

參數 描述
activityItems The array of data objects on which to perform the activity. The type of objects in the array is variable and dependent on the data your application manages. For example, the data might consist of one or more string or image objects representing the currently selected content. Instead of actual data objects, the objects in this array can be objects that adopt the UIActivityItemSource protocol, such as UIActivityItemProvider objects. Source and provider objects act as proxies for the corresponding data in situations where you do not want to provide that data until it is needed. Note that you should not resuse an activity view controller object that includes a UIActivityItemProvider object in its activityItems array.This array must not be nil and must contain at least one object.
applicationActivities An array of UIActivity objects representing the custom services that your application supports. This parameter may be nil.

大概意思是這個方法接收倆個數組類型的參數,第一個數組內的對象表明的是咱們想要操做的數據的一些表徵,並且這個數組至少須要一個值,好比咱們PDF文檔的名稱,URL;第二個數組指定了泛型,數組內的對象必須是UIActivity類型的對象,表明的是iOS系統支持的咱們自定義的服務,關於這點我在後面自定義UIActivity服務的內容中會講解,如今咱們暫時置爲nil。代碼以下:ide

- (IBAction)presentPDFActivityView:(id)sender {
    UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[@"Steve Jobs by waiter lsaacson", [[NSBundle mainBundle] URLForResource:@"Steve" withExtension:@"pdf"]] applicationActivities:nil];

視圖展現

UIActivityViewController是直接繼承UIViewController的,看到這,你想象能夠經過本身的需求來使用不一樣的方式展現UIActivityViewController啦,然而事與願違。oop

官方文檔中是這麼說的: 「When presenting the view controller, you must do so using the appropriate means for the current device. On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally」。 大概意思是說,展現UIActivityViewController的時候須要根據當前的設備類型選擇合適的展現方式,在iPad設備上就必須在'popover'視圖裏面展現,在其餘設備上,必須以模態視圖展現。測試

我的認爲開發必須持懷疑和驗證的態度,因此我嘗試在一個UINavigationController中push一個UIActivityController,代碼以下:this

- (IBAction)presentPDFActivityView:(id)sender {
    
    UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[@"Steve Jobs by waiter lsaacson", [[NSBundle mainBundle] URLForResource:@"Steve" withExtension:@"pdf"]] applicationActivities:nil];
    [self.navigationController pushViewController:activity animated:YES];
}

而後運行程序,點擊Button,意料之中程序崩潰掉了,給出咱們的錯誤反饋是:

2015-12-31 15:03:03.733 ZSDocumentInteractionTest[9307:971136] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'UIActivityViewController can only be used modally or as contentViewController in popover on iPad.'
*** First throw call stack:
(
    0   CoreFoundation                      0x0000000103197e65 __exceptionPreprocess + 165
    1   libobjc.A.dylib                     0x0000000102c10deb objc_exception_throw + 48
    2   CoreFoundation                      0x0000000103197d9d +[NSException raise:format:] + 205
    3   UIKit                               0x0000000103e68e55 -[UIActivityViewController viewDidAppear:] + 533
    4   UIKit                               0x00000001036e0949 -[UIViewController _setViewAppearState:isAnimating:] + 830
    5   UIKit                               0x00000001036e12cc -[UIViewController _endAppearanceTransition:] + 262
    6   UIKit                               0x000000010371bf63 -[UINavigationController navigationTransitionView:didEndTransition:fromView:toView:] + 1290
    7   UIKit                               0x0000000103711d24 __49-[UINavigationController _startCustomTransition:]_block_invoke + 233
    8   UIKit                               0x0000000103f4ad20 -[_UIViewControllerTransitionContext completeTransition:] + 101
    9   UIKit                               0x000000010352cfff __53-[_UINavigationParallaxTransition animateTransition:]_block_invoke95 + 834
    10  UIKit                               0x00000001035f1076 -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 644
    11  UIKit                               0x00000001035ce2af -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 241
    12  UIKit                               0x00000001035ce65e -[UIViewAnimationState animationDidStop:finished:] + 80
    13  QuartzCore                          0x00000001070c2fa0 _ZN2CA5Layer23run_animation_callbacksEPv + 308
    14  libdispatch.dylib                   0x000000010589f49b _dispatch_client_callout + 8
    15  libdispatch.dylib                   0x00000001058872af _dispatch_main_queue_callback_4CF + 1738
    16  CoreFoundation                      0x00000001030f7d09 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    17  CoreFoundation                      0x00000001030b92c9 __CFRunLoopRun + 2073
    18  CoreFoundation                      0x00000001030b8828 CFRunLoopRunSpecific + 488
    19  GraphicsServices                    0x0000000106954ad2 GSEventRunModal + 161
    20  UIKit                               0x0000000103544610 UIApplicationMain + 171
    21  ZSDocumentInteractionTest           0x000000010270b6af main + 111
    22  libdyld.dylib                       0x00000001058d392d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

咱們看出錯誤說明是"UIActivityViewController can only be used modally or as contentViewController in popover on iPad."所以咱們須要更換一下展現方法,在手機上已一個模態視圖的方式展現,而在iPad上則做爲popover的內容視圖展現。代碼以下:

- (IBAction)presentPDFActivityView:(id)sender {
    
    UIActivityViewController *activity = [[UIActivityViewController alloc] initWithActivityItems:@[[[NSBundle mainBundle] URLForResource:@"Steve" withExtension:@"pdf"]] applicationActivities:@[[[ZSCustomActivity alloc] init]]];
    activity.excludedActivityTypes = @[UIActivityTypeAirDrop];
    
    // incorrect usage
    // [self.navigationController pushViewController:activity animated:YES];
    
    UIPopoverPresentationController *popover = activity.popoverPresentationController;
    if (popover) {
        popover.sourceView = self.activityButton;
        popover.permittedArrowDirections = UIPopoverArrowDirectionUp;
    }
    
    [self presentViewController:activity animated:YES completion:NULL];
}

再次運行代碼,點擊Button,就能夠看到下面的界面啦(完美展現)

展現UIActivityViewController

而後咱們就能夠選擇服務來操做和分享史蒂夫•喬布斯傳啦。

excludedActivityTypes

UIActivityViewController相比於UIDocumentInteractionController優點除了能夠添加額外的自定義服務,它還提供了很是好的原生服務的定製化功能。咱們能夠徹底根據本身的需求,控制UIActivityViewController提供的系統服務的顯示,好比我不想展現AirDrop這個功能,而這點在UIDocumentInteractionController是作不到的。想作到這一點,就須要使用到UIActivityViewController提供的一個屬性:

@property(nullable, nonatomic, copy) NSArray<NSString *> *excludedActivityTypes; // default is nil. activity types listed will not be displayed

正如註釋中提到的,excludedActivityTypes這個屬性包含了全部不想在UIActivityViewController中展現的Item服務。excludedActivityTypes是一個字符串數組,所包含的內容必須是系統提供的UIActivityactivityType字符串,而系統提供的字符串以下:

NSString *const UIActivityTypePostToFacebook;
NSString *const UIActivityTypePostToTwitter;
NSString *const UIActivityTypePostToWeibo;
NSString *const UIActivityTypeMessage;
NSString *const UIActivityTypeMail;
NSString *const UIActivityTypePrint;
NSString *const UIActivityTypeCopyToPasteboard;
NSString *const UIActivityTypeAssignToContact;
NSString *const UIActivityTypeSaveToCameraRoll;
NSString *const UIActivityTypeAddToReadingList;
NSString *const UIActivityTypePostToFlickr;
NSString *const UIActivityTypePostToVimeo;
NSString *const UIActivityTypePostToTencentWeibo;
NSString *const UIActivityTypeAirDrop;

若是咱們不想展現AirDrop功能,咱們把UIActivityTypeAirDrop添加到excludedActivityTypes裏面:

activity.excludedActivityTypes = @[UIActivityTypeAirDrop];

運行程序,點擊Button,咱們能夠看到下面的界面發生的變化。

隱藏AirDrop功能

自定義UIActivity服務

UIActivityViewController相比於UIDocumentInteractionController的最大優點就是UIActivityViewController所提供的自定義服務,咱們能夠經過UIActivityUIActivityViewController上添加咱們自定義的服務。

官方文檔上對UIActivity有一段解釋,"This class must be subclassed before it can be used. The job of an activity object is to act on the data provided to it and to provide some meta information that iOS can display to the user. For more complex services, an activity object can also display a custom user interface and use it to gather additional information from the user."。其大概意思是,UIActivity必須經過繼承來使用,它主要是操做給用戶展現的信息,並且還能夠操做展現定製化的界面來獲取更多地數據信息。

如今咱們打算自定義一個叫ZS Custom的服務,因此咱們建立一個ZSCustomActivity得類來繼承UIActivity,除此以外,咱們必須重寫下面的幾個方法:

  • activityType

    - (nullable NSString *)activityType;       // default returns nil. subclass may override to return custom activity type that is reported to completion handler

    這是用來標識自定義服務的一個字符串,而系統提供的服務的標識在上面咱們已經提到了;爲了迎合iOS SDK中的規範,我給它返回一個UIActivityTypeZSCustomMine,定義以下:

    NSString *const UIActivityTypeZSCustomMine = @"ZSCustomActivityMine";
    
    - (NSString *)activityType
    {
        return UIActivityTypeZSCustomMine;
    }
  • activityTitle

    - (nullable NSString *)activityTitle;      // default returns nil. subclass must override and must return non-nil value

    UIActivityViewController中給用戶展現的服務的名稱,好比上面圖片中的"Copy","Print",咱們自定義的服務名稱爲ZS Custom

    - (NSString *)activityTitle
    {
        //國際化
        return NSLocalizedString(@"ZS Custom", @"");
    }
  • activityImage

    - (nullable UIImage *)activityImage;       // default returns nil. subclass must override and must return non-nil value

    UIActivityViewController中給用戶展現的服務的圖標。關於這裏的圖標,有很是嚴格的限制:

    • 首先是圖標的背景色,這裏推薦最好的徹底透明的背景色。

    官方文檔中是這麼解釋的,"The alpha channel of the image is used as a mask to generate the final image that is presented to the user. Any color data in the image itself is ignored. Opaque pixels have a gradient applied to them and this gradient is then laid on top of a standard background. Thus, a completely opaque image would yield a gradient filled rectangle",意思大概是,在這裏顏色數據會被忽略,而透明圖層會被當作mask(蒙版圖層),不透明的圖片會顯示成漸進色填充。

    • 其次是圖標的尺寸,在不一樣的設備須要不一樣的尺寸,所以須要準備一套圖標。

Device iOS Version Icon Size(pt)
iPhone、iPod Touch iOS 6 < 43x43
iPhone、iPod Touch iOS 7+ 60x60
iPad iOS 6 < 60x60
iPad iOS 7+ 76x76
Retina All @2x
  • canPerformWithActivityItems:

    - (BOOL)canPerformWithActivityItems:(NSArray *)activityItems;   // override this to return availability of activity based on items. default returns NO
    指定能夠處理的數據類型,若是能夠處理則返回`YES`
  • prepareWithActivityItems:

    - (void)prepareWithActivityItems:(NSArray *)activityItems;      // override to extract items and set up your HI. default does nothing

    在用戶選擇展現在UIActivityViewController中的自定義服務的圖標以後,調用自定義服務處理方法以前的準備工做,都須要在這個方法中指定,好比能夠根據數據展現一個界面來獲取用戶指定的額外數據信息

  • activityCategory

    + (UIActivityCategory)activityCategory NS_AVAILABLE_IOS(7_0); //       default is UIActivityCategoryAction.

UIActivityViewController中的服務分爲了倆種, UIActivityCategoryAction UIActivityCategoryShare,`UIActivityCategoryAction表示在最下面一欄的操做型服務,好比CopyPrint;UIActivityCategoryShare`表示在中間一欄的分享型服務,好比一些社交軟件。

  • performActivity

    - (void)performActivity; // if no view controller, this method is called. call activityDidFinish when done. default calls [self activityDidFinish:NO]
    在用戶選擇展現在`UIActivityViewController`中的自定義服務的圖標以後,並且也調用了`prepareWithActivityItems:`,就會調用這個方法執行具體的服務操做

須要的方法都重寫好以後,運行程序,點擊Button,就能夠看到咱們自定義的服務圖標顯示在了UIActivityViewController中。

自定義服務ZS Custom

補充之AirDrop

前面一直提到AirDrop,咱們在這裏額外補充一下AirDrop的相關知識點。AirDrop是在iOS 7中提供的,實現跨設備傳輸文檔的功能。AirDrop的實現基於藍牙建立一種相似WIFI的」點對點網絡「,而後實現跨設備傳輸功能。

只是AirDrop的傳輸是有限制的,咱們能夠在咱們的App中經過AirDrop傳送內容,卻不能實現經過AirDrop接收內容,由於,蘋果把設備上經過AirDrop接收到的內容都放到了自家App上,好比僅僅傳送文字時,在接收設備上就會經過Notes打開;若是傳送圖片,在接收設備上就會保存到Photos應用中;經過URL傳送文件,在接收設備上就會經過Safari打開。

只要是有UIDocumentInteractionControllerUIActivityViewController展現的地方,均可以展現AirDrop功能。關於AirDrop如何鏈接設備,如何傳送,能夠到百度經驗找完美得教程。

相關文章
相關標籤/搜索