美文网首页iOS 开发学习成长之路UI进价iOS 艾欧艾斯
通过UIActivityViewController实现更多分享

通过UIActivityViewController实现更多分享

作者: SeraZheng | 来源:发表于2016-01-04 18:24 被阅读24262次

前言

我在通过UIDocumentInteractionController预览和分享"史蒂夫•乔布斯传"这篇文章中,详细讲了UIDocumentInteractionController的用途和使用方法。而在iOS 6 SDK中,苹果提供了UIActivityViewController来让我们可以使用更多地服务。这篇文章,我就来介绍一下怎么通过UIActivityViewController实现更多地服务。

简介

打开UIActivityViewController的API文档,我们可以看到UIActivityViewController的声明。

NS_CLASS_AVAILABLE_IOS(6_0) __TVOS_PROHIBITED @interface UIActivityViewController : UIViewController

我们可以看出UIActivityViewController是在iOS 6开始支持的,同样是不能在Apple TV的开发中使用。而且UIActivityViewController是直接继承UIViewController的,这意味着我们需要自己来展示和解散视图。

准备

我使用在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;

官方文档对这俩个参数有详细的解释:

参数 描述
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。代码如下:

- (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啦,然而事与愿违。

官方文档中是这么说的: “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,代码如下:

- (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中的服务分为了俩种,UIActivityCategoryActionUIActivityCategoryShare,``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如何连接设备,如何传送,可以到百度经验找完美得教程。

相关文章

网友评论

  • 9002f474c15e:airdrop 在ios 上的使用,有相关更多的介绍嘛?
  • A_rcher34:博主你好,我在UIActivityCategoryAction里(最后一行),增加了一个,但是只有在我运行的应用里分享可以看到,我在其他的应用里分享同样的内容,就看不到,请问我应该怎么做呢?
  • BohrIsLay:怎么去掉Share里的reminders和add to notes?
  • 尘世书童:请问UIDocumentInteractionController和UIActivityViewController有什么区别呢?
    SeraZheng:使用方法不一样,具体可查官方文档
  • Yl_T:作者你好! 怎么能不弹出分享列表页,而直接弹 第二步的小窗口呢? 类似于我自己的界面有微信,QQ等,点击微信,直接分享内容窗口!
    Yl_T:@SeraZheng 谢谢,已经解决了
    SeraZheng:@Yl_T ShareExtension,详情可以看过另外一篇文章,http://www.jianshu.com/p/5b9dd21438d8
    Yl_T:这个需求我在其他app上有看到的
  • Eddiegooo:你好 我就使用最基本的方法 传了title image url 三个参数过去 调起微信 分享完
    在次点击 调起分享 就不可以了呢, 页面卡死, 是什么问题呢? 大神有遇见到吗?
    貌似报错:plugin com.tencent.xin.sharetimeline interrupted
    Eddiegooo:@SeraZheng 调用系统分享没有问题。 是我写的一个弹窗出的问题 导致界面假死。:sweat:
    SeraZheng:没有遇到过,如果解决了可以发到这里分享给大家
  • SDBridge:老师好,各位同学好,大家好,早上好,上午好,晚上好,请问如何同时分享多份PDF文件到微信好友,我已经实现分享一份PDF文件到微信好友。

    谢谢:lollipop:
    SeraZheng:这种可以专一看微信开放平台的SDK是否支持
  • 丶小杨_:咋直接调出分享面板啊,不要模态弹出
    丶小杨_:@SeraZheng 不知道怎么实现,你可以去看一下 淘宝联盟这个APP 的分享,他实现了。
    SeraZheng:应该不能吧!如果找到了技巧可以分享到这里,大家共同学习。
  • 云淡风轻的成长:大神,我在iPad上调用UIActivityViewController宽度只显示了一半,不知道这是什么问题?
  • 7e33bcf02f7f:大神请教下,我的需求是通过airDrop 分享 图片,视频出去。 但是airDrop界面弹出,上面会多一个 图标,这个怎么去掉呢。
  • 秘制鸭腿:想在3D Touch添加分享功能,但为什么点了是进了app才弹出,而不是像其他应用一样在外面弹出呢?
  • cee20cbc3927:你好,现在弹出的界面都是英文的,怎么转换为中文?
    cee20cbc3927:@SeraZheng 谢谢回复,已解决,本地语音环境就是中文的,在info中将Localization native development region设置为China就可以了
    SeraZheng:@inremembert 本地语言环境
  • b98b6fee64cc:你好,备忘录和提醒事项是怎么控制他不显示的?有没有方法可以指定显示固定的app?没能找到方法,请大神指导一下呗 ,谢谢!
    SeraZheng:@b98b6fee64cc 分享内容类型决定
  • 广锅锅:谢谢,加油加油。
  • 203b4353d989:您好,想请教下,我在做分享文件的时候,分享png图片时,显示可以分享到qq了;但是在分享excel文件的时候就没有显示qq的选项了,不知道这是为什么呢;但是我看阿里邮箱的分享文件就可以分享到qq;看了你这篇文章就觉得阿里邮箱是不是就采用的自定义服务呢?
    SeraZheng:@freedomsure 找到了就好
  • 李枫林:作者,你好,UIActivityViewController上的QQ分享出去的为什么是一张图片,怎样分享正常呢,而我传的是标题 、url、image。微信分享是正常的。(这两个都是UIActivityViewController自带的,我没重新集成
    SeraZheng:@李枫林 Document Types 内的一些属性设置,可以到官方文档查看,也可以帮助熟知info.plist的属性
    李枫林:@SeraZheng 大神,不知道哪个键是代表允许传输的数据类型,没有查到:disappointed_relieved:
    SeraZheng:@李枫林 看一下项目info.plist配置,允许的传输数据类型
  • ljqjbb:那两个more的按钮能不能去除掉呢?
    SeraZheng:@ljqjbb 暂时没有私有API处理
  • 123_4567_8910:你好,当自定义UIActivity的时候 类型选择UIActivityCategoryShare时 图标能正常显示 但是选择UIActivityCategoryAction的时候 图标图片就显示不了 会显示灰色 好像也不是图片大小的问题 不知道是什么原因 望大神指点下 谢谢。
    SeraZheng:@123_4567_8910 Action代表的是动作列表,与打印,拷贝相似,图标为灰色素。
  • fe3c322fafc6:作者 你好 我有一个需求就是 当选择发邮件分享时 能够把自己APP上的收件人信息直接传到Mail上收件人的textFried上,望大神指点下,谢谢你
    SeraZheng:@骚一骚吧 可以通过MFMail那个接口
  • 程序员不务正业:作者,你好,我有一个需求是如何能把自己的app显示在UIActivityViewController中,就是相册里如果想分享的话可以看到自己的app
    SeraZheng:@_interface Apple Extension
  • 3ddf9ec13d22:作者您好,UIActivityViewController在不同的iPhone设备上,点击more,有的有微信选项,有的没有,就是在两个6Plus设备上,一个有微信选项,而一个没有,不知道是什么原因,望大神指点下,谢谢。
    3ddf9ec13d22:@SeraZheng 嗯嗯,谢谢哈,最后发现是一个手机微信版本的问题,他的微信没有更新。
    SeraZheng:@Horcrux91 1.确定安装了微信 2.确定分享的内容类型和数量一致
  • oriyum:自定义没有效果跟传nil一样
  • DeCori:作者您好,冒昧问下关于App Extension部分您还准备继续写吗?
    SeraZheng: @回忆说晚安 这几天暂时没有时间,这个feature在写作计划中!
  • 772ce963e13d:作者你好,第二行图标颜色被系统渲染成黑色了 怎么弄成和第一行一样彩色的呢
    SeraZheng: @背着吉他去流浪 你可以自定义UIActivity试试,如果效果达不到,那就考虑一下私有API,或者通过KVC实验一下,并没有很多时间做demo试验,项目中用不到
    背着吉他去流浪:@SeraZheng 我也想知道你是怎么自定义第二排,然后又让颜色数据有效,来个Demo吧
    SeraZheng:@772ce963e13d UIActivity可以自定义
  • 11bfc71c3fe3:@SeraZheng :UIDocumentInteractionController 有copy to 微信、copy to qq等,而UIActivityViewController只有微信 没有copy to 微信?
    SeraZheng:@2499971551 一个是Apple Extension ,一个是普通的分享
  • 11bfc71c3fe3:我用UIActivityViewController 为什么第二行不显示copy to app呢?
    SeraZheng:@2499971551 第二行是Action,第一行是App
  • 陈晓寒:作者你好,我比较好奇,如果像微信QQ那样,分享按钮会出现在Safari等系统应用的UIActivityViewController中,而不只是自己的App。这是如何实现的?
    CoderMrGuo:你好,我想请教下,比如我在今日头条分享一条新闻,在系统的分享的更多选项里面,分享到自己的app怎么做
    陈晓寒:@SeraZheng 明白了,感谢回复。
    SeraZheng: @陈晓寒 Apple Extension里面的share Extension,是从iOS8以后开始的
  • 花开半夏已成殇:想問下,能不能微信不出现呢?查了好多都没有方法。
  • KevinTing:问一下大神,为什么在iPad上面使用UIActivityViewController,在更多列表里面找不到新浪微博和qq的选项,但是微信可以找到?iPhone上面就不存在这个问题。
    KevinTing:@SeraZheng 还有一个问题,iPhone上安装了qq之后可以显示在分享列表,但是iPad上安装了无法显示。。。qq并不是iOS内置的
    SeraZheng: @KevinTing 不客气
    SeraZheng: @KevinTing 需要看分享所选的文件类型,是否被QQ和新浪微博所支持!iPad上都有内置的新浪微博,账号登陆后,就可以显示!
  • 向晚forever:想请教下作者,更多按钮怎么隐藏
    向晚forever:@SeraZheng 知道了,谢谢作者这么忙还抽空帮忙解决
    SeraZheng:@SeraZheng 专门查了一些私有API,暂时还没有找到可以实现这个效果的方法
    SeraZheng:@向晚forever 很抱歉这么晚才回复,据我了解,这是无法隐藏的!More button是iOS8以后才加进去的,目的就是为了让用户可以管理配置iOS8新加的extension,因此即使一个activity都不存在,这个 More button也会显示!官方并没有提供这样的接口
  • scyworld:受教了,感谢分享~
    SeraZheng: @Sauchye 不客气,共勉
  • 曾樑:Airdrop用的机会蛮少的,不过有时候挺有用
    SeraZheng: @曾樑 也的确是

本文标题:通过UIActivityViewController实现更多分享

本文链接:https://www.haomeiwen.com/subject/yioihttx.html