通过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`的每一种用途的具体使用:
>1. 展示一个可以操作我们分享的文档类型的第三方App列表
2. 在第一条展示列表的基础上添加额外的操作,比如`复制`,`打印`,`预览`,`保存`等。
3. 结合`Quick Look`框架直接展示文档内容

#准备阶段
首先我创建了一个新的应用方便演示和截图,我把它命名为`ZSDocumentInteractionTest`,然后拖入PDF格式的`史蒂夫•乔布斯传`到`ZSDocumentInteractionTest`项目的bundle中。然后在`Storyboard`的`ViewController`中添加了一个Button作为`UIDocumentInteractionController`的触发操作(这些操作都比较简单,就不在这里用图展示啦)。运行程序,我们就可以看到Button啦,截图如下。然后我们就可以在Button的触发方法中,操作`UIDocumentInteractionController`来显示或者分享我们的`史蒂夫•乔布斯传`啦,具体的应用详情可以参考GitHub上的Demo:[ZSDocumentInteractionTest](https://github.com/SeraZheng/ZSDocumentInteractionTest)。

![在ViewController的视图 中显示Button](http://upload-images.jianshu.io/upload_images/1216322-9fac2efc2d9a6bc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

#初始化
不管我们使用哪种`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,就可以看到下面的界面啦。这说明我们的第一步成功了!!(真棒)

![可以操作PDF文件的第三方App列表](http://upload-images.jianshu.io/upload_images/1216322-f1a0a8924ea4b42d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

简单介绍一下这个界面,这个视图中的第一行列表显示`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:408
2015-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];
    }

```Objective-c
- (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,我们可以看到下面的界面,多了CopyPrint的操作。Copy操作可以将文件拷贝到系统粘贴板中,而Print操作则是关联打印机进行打印操作的。(在这里我就不展示这俩种操作的具体界面啦!)

第三方App列表和附加操作界面

如果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,弹出了一个新视图,可以看到史蒂夫•乔布斯传的内容,如下图

直接预览乔布斯自传

展示预览操作

通过上面的操作我们就可以欣赏阅读我们想看的史蒂夫•乔布斯传啦,不过别忘记我们上面还卖了一个小关子,就是在展示可选操的时候,除了CopyPrint,其实我们还可以展示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操作

如果我们点击这个Quick Look操作,就可以看到直接预览内容时所展示的界面啦。好啦,通过UIDocumentInteractionController实现史蒂夫•乔布斯传的预览和分享就到此结束啦。我会在下面的章节中,讲解通过其他技术实现乔布斯自传的分享和操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容