iOS_分享_UIActivityController
来源:互联网 发布:淘宝ifashion入驻入口 编辑:程序博客网 时间:2024/06/07 21:47
一行代码实现UIImage的分享
- (void)shareToOtherAPP{ NSArray *arr1 = @[_captureImg]; UIActivityViewController *ctrl = [[UIActivityViewController alloc]initWithActivityItems:arr1 applicationActivities:nil]; [self presentViewController:ctrl animated:YES completion:nil];}
iOS实现App之间的内容分享
前言
我们在iOS平台上想要实现不同App之间的内容分享一般有几种常用方式:
- 第一种是通过
AirDrop
实现不同设备的App之间文档和数据的分享; - 第二种是给每个App定义一个URL Scheme,通过访问指定了URL Scheme的一个URL,实现直接访问一个APP;
- 第三种是通过
UIDocumentInteractionController
或者是UIActivityViewController
这俩个iOS SDK中封装好的类在App之间发送数据、分享数据和操作数据; - 第四种是通过
App Extension
,在iOS 8的SDK中提供的扩展新特性实现跨App的数据操作和分享; - 还有一种集成第三方SDK实现的有限个App的数据分享,比如社交平台(QQ,微信,新浪微博等)给我们提供的官方SDK,或者是集成了多个社交平台的ShareSDK组件和友盟分享组件等。
关于集成第三方SDK的使用,各大平台官网上都有详细的文档说明,因此我们这系列文章主要是来谈谈苹果原生提供的基于iOS SDK的分享技术,同时推荐俩篇苹果开发者中心的文档:Inter-App Communication和Document Interaction Programming Topics for iOS。我们的第一篇文章就谈一下如何通过UTI让我们的App支持分享。
原理
我在“详解苹果提供的UTI(统一类型标识符)“这篇文章中,详细地讲解了一下UTI(Uniform Type Identifier)
,一套苹果给我们提供用来在基于Cocoa和Cocoa Touch应用程序中识别实体内容类型的规范,而关于实现内容关联的技术也正是基于这套规范。在iOS和Mac OS开发中,苹果给我们提供了注册文档类型的接口,而这种注册的文档类型是全局的,系统中所有的应用程序和服务都可以侦测到。因此我们通过这个底层侦测,可以使用其他可选的第三方App
来预览我们的App
中不支持的文档,而且我们还可以通过这个接口在我们的App
中打开并处理第三方App
的文档。
如果我们的App可以处理某些类型的实体内容,那么我们就可以在我们项目中的Info.plist
文件中进行注册。关于使用哪种类型和UTI,就要参考我在“详解苹果提供的UTI(统一类型标识符)“这篇文章中的讲解。当一个第三方App通过苹果的底层侦测技术检查有哪些App可以处理它所指定的内容类型时,如果我们的App已经注册了这种类型,那么我们的App图标就会显示在其中,并且作为我们自己的App的一个入口。
主要技术
主要应用到这种底层侦测的技术有iOS SDK中给我们提供的UIDocumentInteractionController
、UIActivityViewController
和Quick Look 框架
。此外,在iOS 8中,苹果又给开发者提供了App Extension
,一种更高大上的方式在App之间的实现分享内容。
关于UIDocumentInteractionController
、UIActivityViewController
、Quick Look 框架
以及App Extension
的细节,我计划在后面的文章中详细讲解。这篇文章,我们主要是来谈谈如何注册我们App可用的文档类型
以及简单使用我们的App来处理第三方App分享的内容
。
注册可用类型
我们需要在info.plist
文件中,添加一个新的属性CFBundleDocumentTypes
(实际上输入的是"Document types"
),这是一个数组类型的属性,意思就是我们可以同时注册多个类型。而针对数组中的每一个元素,都有许多属性可以指定,详细的属性列表我们可以从官方文档上找到: Core Foundation Keys ---- CFBundleDocumentTypes。这里列举我们在做iOS开发时常用的属性:
- CFBundleTypeName(
"Icon File Name"
)
字符串类型,指定某种类型的别名,也就是用来指代我们规定的类型的别称,一般为了保持唯一性,我们使用UTI来标识。 - CFBundleTypeIconFiles
数组类型,包含指定的png图标的文件名,指定代表某种类型的图标,而图标有具体的尺寸标识:
Device Sizes iPad64 x 64 pixels, 320 x 320 pixelsiPhone and iPod touch22 x 29 pixels, 44 x 58 pixels (high resolution)
- LSItemContentTypes(
"Document Content Type UTIs"
)
数组类型,包含UTI字符串,指定我们的应用程序所有可以识别的类型集合 - LSHandlerRank(
"Handler rank"
)
字符串类型,包含Owner
,Default
,Alternate
,None
四个可选值,指定对于某种类型的优先权级别,而Launcher Service
会根据这个优先级别来排列显示的App的顺序。优先级别从高到低依次是Owner
,Alternate
,Default
。None
表示不接受这种类型。
了解了这些基本属性,我们就需要在注册App可用类型时,指定这些属性,根据每个项目的需求不同,属性值也不同。具体的注册请参照我的GitHub上的项目:SeraZheng---ZSUTITest。下图示例作为一个参照:
而当我们添加完所有属性后,开始运行我们的程序,然后再回到我们的Info界面,就会看到Document types
这个列表已经发生了变化,这就证明我们成功的注册好了App可用的类型。
打开第三方应用
我们在上面的步骤中注册好了我们的App可以识别的类型,现在我们可以打开一个使用UIDocumentInteractionController
或者是Quick Look
框架来展示内容的第三方App,这里以iPhone 上的QQ程序为例。
我们在上面的注册步骤中,注册的LSItemContentTypes
仅包含了public.image
这个UTI。所以我们先从QQ应用程序的我的文件
中,打开不同类型的文件进行对比,大家可以看下图我的文件
列表中包含俩种类型的文件,一种是.jpg
扩展名的图片文件,一种是.pdf
扩展名的文档文件。
当我打开一个图片文件进行预览时,点击其他应用打开
,就可以在App列表中看到我们的App图标。简单介绍一下这个页面,第一行是苹果在iOS 7之后给我们提供的使用AirDrop
在iPhone
、iPad
或iPod Touch
设备之间通过iCloud
共享内容的一种方式。第二行是通过文档类型关联技术识别的App的列表。第三行是通过文档关联技术识别的Action
的列表,Action
表示对文档可进行的操作,如复制,打印等。
而如果我打开PDF文件的话,就看不到我们的App图标。
程序回调
当我们通过上面步骤,成功地显示了ZSUTITestDemo
的图标之后,点击图标,我们就可以跳转到ZSUTITestDemo
应用中,而苹果在iOS SDK中给我们提供的接收回调的方法在iOS 9之后做出了改变,因此我们需要针对不同的设备版本做出改变:
#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(nullable NSString *)sourceApplication annotation:(id)annotation{ UINavigationController *navigation = (UINavigationController *)application.keyWindow.rootViewController; ViewController *displayController = (ViewController *)navigation.topViewController; [displayController.imageView setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; [displayController.label setText:sourceApplication]; return YES;}#else- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(nonnull NSDictionary<NSString *,id> *)options{ UINavigationController *navigation = (UINavigationController *)application.keyWindow.rootViewController; ViewController *displayController = (ViewController *)navigation.topViewController; [displayController.imageView setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; [displayController.label setText:[options objectForKey:UIApplicationOpenURLOptionsSourceApplicationKey]]; return YES;}#endif
Demo示例可以从GitHub项目上参照代码:SeraZheng---ZSUTITest。当点击ZSUTITestDemo
程序图标回到调用代码中,我们可以在这里做各种我们想做的事,如上传图片、预览图片、操作图片等等。我只对图片做了简单的预览显示,然后显示文件的源程序的Bundle Identifier
,示例如下图:
通过UIDocumentInteractionController预览和分享"史蒂夫•乔布斯传"
朋友分享推荐给我一本PDF格式的史蒂夫•乔布斯传
,阅读了几篇,很受感触,于是想把他分享给大家欣赏阅读。早起闲来无事,正好就接着写篇文章来分享一下!我在“iOS实现App之间的内容分享”这篇文章中详细讲解了通过注册UTI的方式让我们的App支持分享,也简单地说了一下App内部怎么处理分享。同时,我也指出了在iOS系统跨App分享内容的几种常用技术,比如URL Scheme
,AirDrop
, UIDocumentInteractionController
,UIActivityViewController
这几种。这一篇文章,我们来谈一下最基础的原始方法,怎么通过使用UIDocumentInteractionController
来预览、操作和分享史蒂夫•乔布斯传
。
简介
从iOS SDK的API文档中,我们可以找到UIDocumentInteractionController
的声明:
NS_CLASS_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED @interface UIDocumentInteractionController : NSObject <UIActionSheetDelegate>
由此声明我们可以得知,UIDocumentInteractionController
是从iOS 3.2的SDK开始支持的,它是直接继承的NSObject
,而不是我们想象的UIViewController
,因此我们需要使用UIDocumentInteractionController
提供的方法来展示它,而且我们还可以看出它是不能在Apple TV 的开发中使用的。遍观UIDocumentInteractionController
的属性和方法可以看出,UIDocumentInteractionController
主要给我们提供了三种用途,我会在下面的内容中逐条的讲解UIDocumentInteraction
的每一种用途的具体使用:
- 展示一个可以操作我们分享的文档类型的第三方App列表
- 在第一条展示列表的基础上添加额外的操作,比如
复制
,打印
,预览
,保存
等。- 结合
Quick Look
框架直接展示文档内容
准备阶段
首先我创建了一个新的应用方便演示和截图,我把它命名为ZSDocumentInteractionTest
,然后拖入PDF格式的史蒂夫•乔布斯传
到ZSDocumentInteractionTest
项目的bundle中。然后在Storyboard
的ViewController
中添加了一个Button作为UIDocumentInteractionController
的触发操作(这些操作都比较简单,就不在这里用图展示啦)。运行程序,我们就可以看到Button啦,截图如下。然后我们就可以在Button的触发方法中,操作UIDocumentInteractionController
来显示或者分享我们的史蒂夫•乔布斯传
啦,具体的应用详情可以参考GitHub上的Demo:ZSDocumentInteractionTest。
初始化
不管我们使用哪种UIDocumentInteractionController
的展示方式和用途,都需要给UIDocumentInteractionController
指定文档的URL,所以我们通常使用下面的初始化方式,给UIDocumentInteractionController
指定文件的URL
。
- (IBAction)presentPDFDocumentInteraction:(id)sender { UIDocumentInteractionController *documentController = [UIDocumentInteractionController interactionControllerWithURL:[[NSBundle mainBundle] URLForResource:@"Steve" withExtension:@"pdf"]];}
展示第三方App列表
我们先实现UIDocumentInteractionController
的第一个用途,展示可以操作PDF文件的第三方App列表。我们需要使用UIDocumentInteractionController
提供的方法:
- (BOOL)presentOpenInMenuFromRect:(CGRect)rect inView:(UIView *)view animated:(BOOL)animated;
我在Button的触发方法中添加下面的代码,意思就是让UIDocumentInteractionController
的View在当前控制器视图上显示:
[documentController presentOpenInMenuFromRect:self.view.bounds inView:self.view animated:YES];
运行程序,点击Button,我们可以开始第一次展示测试啦。
第一次展示测试
一切准备就绪之后,我开始进行UIDocumentInteractionController
的测试,点击Button,就可以看到下面的界面啦。这说明我们的第一步成功了!!(真棒)
简单介绍一下这个界面,这个视图中的第一行列表显示AirDrop
,是苹果在iOS 7
提供的一种跨设备分享的技术,我会在后边的文章中讲解。视图中的第二行列表就是整个iOS系统中,可以操作PDF文档的应用程序列表,还包括了苹果在iOS 8
提供的Share Extension
图标,关于Share Extension
,我会在后边的文章中讲解。视图中的第三行列表,就是现实设备可选的操作,如Copy
,Print
中,这里什么操作都没有,并不是说没有可执行的操作,而是我们没有让他显示出来。
接着我试着点击QQ图标,打算把史蒂夫•乔布斯传
分享给我的好友,然而意外发生了,ZSDocumentInteractionTest
崩溃掉啦,而且还给出我们一段错误提示:
2015-12-30 19:00:40.078 ZSDocumentInteractionTest[1254:344240] *** Assertion failure in -[_UIOpenWithAppActivity performActivity], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.29.5/UIDocumentInteractionController.m:4082015-12-30 19:00:40.079 ZSDocumentInteractionTest[1254:344240] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UIDocumentInteractionController has gone away prematurely!'*** First throw call stack:(0x248e185b 0x35fa2dff 0x248e1731 0x25672ddb 0x290638c9 0x292695bb 0x28d5aefd 0x28d5e1a1 0x28b42107 0x28a50a55 0x28a50531 0x28a5042b 0x282e05cf 0x1acd03 0x1b17c9 0x248a4535 0x248a2a2f 0x247f50d9 0x247f4ecd 0x2db6aaf9 0x28a7e2dd 0x780ad 0x366f0873)libc++abi.dylib: terminating with uncaught exception of type NSException
我看到错误提示竟然指向了UIDocumentInteractionController.m
文件,而且错误提示是NSInternalInconsistencyException
(内部不一致)和"UIDocumentInteractionController has gone away prematurely!"(UIDocumentInteractionController过早地被释放掉啦)。由此我想出这个应该是内存过早释放的一个错误,然后我查阅了一下Apple Developer上的文档,原来,在ARC环境下展示UIDocumentInteractionController
时,当我的函数方法调用完毕,退栈之后,UIDocumentInteractionController
的实例就被释放掉了,展示出来的这个View由Quick Look
框架来操作,并不会对UIDocumentInteractionController
产生引用。当点击View上面的Button时,内部操作仍然会继续访问这个UIDocumentInteractionController
实例,就会报出上述错误。
错误原因找到了,那么解决原理也就清楚了,只要不让UIDocumentInteractionController
实例过早释放就可以啦。我们可以将UIDocumentInteractionController
声明为一个strong
类型的实例属性,然后修改一下Button触发方法就可以啦。(仍然不理解的朋友可以去GitHub上下载Demo测试)
@interface ViewController ()@property (nonatomic, strong) UIDocumentInteractionController *documentController;@end
我在Button的触发方法中添加下面方法的调用,为了方便区分和理解,我把代码封装成了私有实例方法:
- (void)presentOpenInMenu{ // display third-party apps [self.documentController presentOpenInMenuFromRect:self.view.bounds inView:self.view animated:YES];}
- (IBAction)presentPGNDocumentInteraction:(id)sender { _documentController = [UIDocumentInteractionController interactionControllerWithURL:[[NSBundle mainBundle] URLForResource:@"Steve" withExtension:@"pdf"]]; [self presentOpenInMenu];}
修改完之后,运行程序,然后点击Button,看到第一次测试时展示出来的图片啦。然后再点QQ图标,就可以正确地跳转到QQ程序中,选择好友就可以分享史蒂夫•乔布斯传
啦。(QQ接收分享页面就不展示了,想试验的可以手动测试下)
展示可选操作
我们可以看到第一步图示里面只有App图标,第二行操作列表中只有一个More
。所以我们来展示UIDocumentInteractionController
的第二种用途,在第一步的基础之上,显示附加的操作选项,。这需要我们使用UIDocumentInteractionController
提供的另外一种展示方法:
- (BOOL)presentOptionsMenuFromRect:(CGRect)rect inView:(UIView *)view animated:(BOOL)animated;
我们在Button的触发方法中添加下面方法的调用:
- (void)presentOptionsMenu{ // display third-party apps as well as actions, such as Copy, Print, Save Image, Quick Look [_documentController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES];}
运行程序,点击Button,我们可以看到下面的界面,多了Copy
和Print
的操作。Copy
操作可以将文件拷贝到系统粘贴板中,而Print
操作则是关联打印机进行打印操作的。(在这里我就不展示这俩种操作的具体界面啦!)
如果UIDocumentInteractionController
关联的是一个图片文件,这个界面还会提供一个Save Image
的操作,用来直接保存图片到系统的Photos
中,此外这个界面还提供了一个Quick Look
操作,可以让我们直接预览乔布斯自传
PDF文档,只不过需要我们再多写点代码,为了文章的合理性和结构性,我决定在下面的标题内容中讲解。(先卖个小关子!!)
直接预览
UIDocumentInteractionController
第三种预览文档内容的用途非常重要,而且也是常见的。我会详细地说一下如何通过UIDocumentInteractionController
实现预览史蒂夫•乔布斯传
。首先你需要为UIDocumentInteractionController
指定一个delegate,并且实现下面的代理方法:
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller;
这个代理方法主要是用来指定UIDocumentInteractionController
要显示的视图所在的父视图容器。这样UIDocumentInteractionController
才清楚在哪里展示Quick Look
预览内容, 我在这里就指定Button所在的UIViewController来做UIDocumentInteractionController
的代理对象,并且实现上面的代理方法。在Button的触发方法中添加下面的代码
_documentController.delegate = self;
然后实现代理方法:
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller{ return self;}
UIDocumentInteractionController
是继承自NSObject
的,因而为了能够实现直接预览,我们需要用到UIDocumentInteractionController
提供的展示预览的方法,
- (BOOL)presentPreviewAnimated:(BOOL)animated;
这个方法是以模态窗口通过Quick Look框架全屏显示PDF的内容,所以我们在Button的触发方法中添加下面方法的调用:
- (void)presentPreview{ // display PDF contents by Quick Look framework [self.documentController presentPreviewAnimated:YES];}
然后运行程序,点击Button,弹出了一个新视图,可以看到史蒂夫•乔布斯传
的内容,如下图
展示预览操作
通过上面的操作我们就可以欣赏阅读我们想看的史蒂夫•乔布斯传
啦,不过别忘记我们上面还卖了一个小关子,就是在展示可选操的时候,除了Copy
,Print
,其实我们还可以展示Quick Look
这个预览操作。为什么我要卖关子呢,因为我是一个相信因果循环的人,我组织文章的逻辑是由浅入深,我设想通过一步步铺垫来展开UIDocumentInteractionController
所有特性。
好啦,回归正题!我们想要实现显示Quick Look
预览操作,其大部分的工作在直接预览
这一小节中都做完了,比如指定代理对象,然后实现这个代理方法来指定UIDocumentInteractionController
的父视图容器:
- (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *)controller;
由于我们已经做完了所有准备,在这一步,我们只需要将直接展示史蒂夫•乔布斯传
内容的方法替换为下面这段,展示可选操作列表
的方法,就可以啦!
- (void)presentOptionsMenu{ // display third-party apps as well as actions, such as Copy, Print, Save Image, Quick Look [_documentController presentOptionsMenuFromRect:self.view.bounds inView:self.view animated:YES];}
然后我们运行程序,点击Button,就可以看到Quick Look
操作已经显示出来啦!如下图:
如果我们点击这个Quick Look
操作,就可以看到直接预览内容时所展示的界面啦。好啦,通过UIDocumentInteractionController
实现史蒂夫•乔布斯传
的预览和分享就到此结束啦。我会在下面的章节中,讲解通过其他技术实现乔布斯自传
的分享和操作。
通过UIActivityViewController实现更多分享服务
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
的触发事件,运行程序,就可以看到下面的界面啦(过程自行想象,哈哈)
初始化
接着我们在Button的触发方法里面开始操作UIActivityViewController
来提供服务。首先,我们需要初始化一个UIActivityViewController
的实例,UIActivityViewController
提供了一个初始化方法:
- (instancetype)initWithActivityItems:(NSArray *)activityItems applicationActivities:(nullable NSArray<__kindof UIActivity *> *)applicationActivities NS_DESIGNATED_INITIALIZER;
官方文档对这俩个参数有详细的解释:
大概意思是这个方法接收俩个数组类型的参数,第一个数组内的对象代表的是我们想要操作的数据的一些表征,而且这个数组至少需要一个值,比如我们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,就可以看到下面的界面啦(完美展示)
然后我们就可以选择服务来操作和分享史蒂夫•乔布斯传
啦。
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
是一个字符串数组,所包含的内容必须是系统提供的UIActivity
的activityType
字符串,而系统提供的字符串如下:
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,我们可以看到下面的界面发生的变化。
自定义UIActivity服务
UIActivityViewController
相比于UIDocumentInteractionController
的最大优势就是UIActivityViewController
所提供的自定义服务,我们可以通过UIActivity
在UIActivityViewController
上添加我们自定义的服务。
官方文档上对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(蒙版图层),不透明的图片会显示成渐进色填充。
其次是图标的尺寸,在不同的设备需要不同的尺寸,因此需要准备一套图标。
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
表示在最下面一栏的操作型服务,比如Copy
、Print
;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
中。
补充之AirDrop
前面一直提到AirDrop
,我们在这里额外补充一下AirDrop
的相关知识点。AirDrop
是在iOS 7
中提供的,实现跨设备传输文档的功能。AirDrop
的实现基于蓝牙创建一种类似WIFI的”点对点网络“,然后实现跨设备传输功能。
只是AirDrop
的传输是有限制的,我们可以在我们的App中通过AirDrop
传送内容,却不能实现通过AirDrop
接收内容,因为,苹果把设备上通过AirDrop
接收到的内容都放到了自家App上,比如仅仅传送文字时,在接收设备上就会通过Notes
打开;如果传送图片,在接收设备上就会保存到Photos
应用中;通过URL传送文件,在接收设备上就会通过Safari
打开。
只要是有UIDocumentInteractionController
和UIActivityViewController
展示的地方,都可以展示AirDrop
功能。关于AirDrop
如何连接设备,如何传送,可以到百度经验找完美得教程。
- iOS_分享_UIActivityController
- iOS_系统原生分享
- IOS_技巧分享之另类随机数组
- IOS_分享新语法&&视图生命周期相关的图片
- IOS_分享本地化语言封装好的类
- IOS_利用反射获得类中的属性列表和Demo分享
- IOS_基础知识
- IOS_多线程
- iOS_适配
- iOS_视图
- iOS_知识点
- iOS_笔记
- IOS_ CAKeyframeAnimation
- IOS_函数
- iOS_二维码
- iOS_传感器
- iOS_ UISearchBarDelegate
- iOS_多线程
- 分布式架构高可用架构篇_04_Keepalived+Nginx实现高可用Web负载均衡
- 事务相关、不可重复读与幻读的区别
- html之特殊字符表
- 【C/C++】【VS开发】结构体存储空间数据对齐说明
- STM32学习13
- iOS_分享_UIActivityController
- z-index在css中怎么用
- 关于视频直播云服务,这几点你不得不知
- Caffe下将mnist手写图片数据转化成lmdb格式
- Linux下http协议实现
- HttpClient 命名空间
- 【python学习笔记一】python的5种方法参数
- 《Redis官方文档》用Redis构建分布式锁
- C++两个项目之间相互引用的方法