2.7 秘诀:使用文档交互控制器
iOS 6高级开发手册(第4版)
UIDocumentInteractionController类允许应用程序给用户展示一个选项菜单,允许他们以各种方式使用文档文件,利用这个类,用户可以利用以下特性。
iOS应用程序之间的文档共享(即“在某个应用程序中打开这个文档”)。
使用QuickLook进行文档预览。
活动控制器选项,比如打印、共享和社交网络。
你已经在本章前面的动作中见过后两种特性。文档交互类在这些特性顶部添加了应用程序间的共享,如图2-7所示。控制器被展示为菜单,这使用户能够指定他们想怎样与给定的文档交互。
UIDocumentInteractionController显示了用于iPhone(左图)和iPad(右图)的选项风格。两种表示都包括两个页面的图标,如靠近显示屏底部的页面控制器所示
在iOS 6中,“open in”(打开在)选项的数量不再会限制它在早期的OS版本中的使用方式,这就是你为什么会在菜单底部看到页面指示器的原因。用户可以轻扫屏幕,查看“open in”(打开在)选项的完整补充。
控制器提供了两种基本的菜单风格。“open”(打开)风格只提供了“open in”(打开在)选择,使用菜单空间提供尽可能多的目的地选择。“options”(选项)风格(见图2-7)提供了所有交互选项的列表,包括“open in”(打开在)、快速查看和任何支持的动作。它实质上是你将从标准的Actions(“动作”)菜单中获得的所有良好的选项,以及“open in”(打开在)额外选项。你必须明确地添加快速查看回调,但它需要做一点工作。
2.7.1 创建文档交互控制器实例
每个文档交互控制器都特定于单个文档文件。这个文件通常存储在用户的Documents(“文档”)文件夹中:
dic = [UIDocumentInteractionController
interactionControllerWithURL:fileURL];
你提供一个本地文件URL,并且使用选项变化(基本上是Action菜单)或者打开菜单(仅仅是“open in”(打开在)项目)。两种表示风格都来自于一个栏按钮或者屏幕上的矩形:
presentOptionsMenuFromRect:inView:animated:
presentOptionsMenuFromBarButtonItem:animated:
presentOpenInMenuFromRect:inView:animated:
presentOpenInMenuFromBarButtonItem:animated:
iPad使用你传递的栏按钮或矩形来展示一个弹出窗口(popover)。在iPhone上,实现的功能将展示一个模态控制器视图。如你所期望的,更多的流水账会占据iPad上的空间,其中用户可能点按其他的栏按钮,可能会关闭弹出窗口,等等。
你将希望在展示每个iPad栏按钮项目关联的控制器之后禁用它们,以及在它们不起作用之后重新启用它们。这很重要,因为你不希望用户再次点按正在使用的栏按钮,并且需要处理需要接管不同弹出窗口的情形。基本上,如果不仔细监测哪些按钮是活动的以及哪个弹出窗口正在使用中,将有可能发生各种不愉快的情况。秘诀2-7预防了这些情况。
2.7.2 文档交互控制器属性
每个文档交互控制器都提供了许多属性,可以在你的委托回调中使用它们。
URL属性允许向控制器查询它正在服务的文件,它与你在创建控制器时传递的URL相同。
UTI属性用于确定哪些应用程序可以打开文档。它使用本章前面讨论过的系统提供的功能,基于文件名和元数据查找最佳的UTI匹配。你可以在代码中覆盖它,以手动设置属性。
name属性指定了URL的最后一个路径成分,它提供了一种快捷方式,指定一个用户可解释的名称。
使用icons属性获取正在使用的文件类型的图标。声明支持某些文件类型的应用程序将在它们的声明中提供图像链接(稍后将讨论如何声明文件支持)。这些图像对应于为kUTTypeIconFileKey键存储的值,这在本章前面讨论过。
annotation属性提供了一种方式,连同文件一起把自定义的数据传递给任何将打开该文件的应用程序。这个属性没有标准的用法;尽管如此,仍然必须把项目设置为某个顶级的属性列表对象,即字典、数组、数据、字符串、数字和日期。由于没有共同的标准,人们倾向于把这个属性的使用控制在最小范围内,除非开发人员在他们自己发布的应用程序套件中共享信息。
2.7.3 提供文档Quick Look支持
通过实现3个委托回调给控制器添加Quick Look支持。这些方法声明哪个视图控制器将用于展示预览、哪个视图将宿主它,以及用于预览尺寸的框架。你可能偶尔会有具有说服力的理由,在平板电脑上的有限屏幕空间内使用子视图控制器(比如在拆分视图中,利用仅仅一部分空间进行预览),但是对于iPhone家族,几乎没有任何理由不允许预览占据整个屏幕:
#pragma mark QuickLook
- (UIViewController *)
documentInteractionControllerViewControllerForPreview:
(UIDocumentInteractionController *)controller
{
return self;
}
- (UIView *) documentInteractionControllerViewForPreview:
(UIDocumentInteractionController *)controller
{
return self.view;
}
- (CGRect) documentInteractionControllerRectForPreview:
(UIDocumentInteractionController *)controller
{
return self.view.frame;
}
2.7.4 检查打开菜单
在使用文档交互控制器时,Options(选项)菜单几乎总会提供有效的菜单选择,尤其是当实现Quick Look回调时。不过,你可能会或者可能不会有任何“open-in”(打开在)选项可以使用。这些选项依赖于你提供给控制器的文件数据以及用户在他们的设备上安装的应用程序。
当设备上没有安装支持你正在使用的文件类型的应用程序时,将会发生没有打开选项的情况。这可能是由于鲜为人知的文件类型引起的,但是更常见的是由于用户还没有购买并安装相关的应用程序。
因此总是要检查是否提供了“Open”(打开)菜单项。秘诀2-7执行了一个相当丑陋的测试,以查看外部程序是否把它们自身提供为给定URL的展示器和编辑器。它的工作方式如下:它创建一个新的、临时的控制器并尝试展示它。如果它获得成功,符合需要的文件目的地就会存在并被安装到设备上。如果没有成功,就没有这样的应用程序,并且应该禁用任何“Open In”(打开在)按钮。
在iPad上,必须在viewDidAppear:中或者以后(也就是说在建立了窗口之后)运行这项检查。方法在展示控制器之后将立即使之消失。最终用户应该不会注意到它,并且没有任何调用使用动画。
显然,这是一个相当糟糕的实现,但它具有在布置界面或者开始处理一个新文件时进行测试的优点。我鼓励你在bugreporter.apple.com上提出一个增强请求。
进一步警告:尽管这种测试可以在主视图上工作(如同这个秘诀中一样),但它可能在iPad上的弹出窗口中的非标准表示中引发问题。
注意:
通常极少在同一个应用程序中同时使用“选项”和“打开”项目。秘诀2-7为Options(选项)菜单使用系统提供的Action(动作)项目。你可能想为只使用“打开”风格的应用程序使用它来代替“Open in...”(打开在)文本。
秘诀2-7 文档交互控制器
@implementation TestBedViewController
{
NSURL *fileURL;
UIDocumentInteractionController *dic;
BOOL canOpen;
}
#pragma mark QuickLook
- (UIViewController *)
documentInteractionControllerViewControllerForPreview:
(UIDocumentInteractionController *)controller
{
return self;
}
- (UIView *) documentInteractionControllerViewForPreview:
(UIDocumentInteractionController *)controller
{
return self.view;
}
- (CGRect) documentInteractionControllerRectForPreview:
(UIDocumentInteractionController *)controller
{
return self.view.frame;
}
#pragma mark Options / Open in Menu
// Clean up after dismissing options menu
- (void) documentInteractionControllerDidDismissOptionsMenu:
(UIDocumentInteractionController *) controller
{
self.navigationItem.leftBarButtonItem.enabled = YES;
dic = nil;
}
// Clean up after dismissing open menu
- (void) documentInteractionControllerDidDismissOpenInMenu:
(UIDocumentInteractionController *) controller
{
self.navigationItem.rightBarButtonItem.enabled = canOpen;
dic = nil;
}
// Before presenting a controller, check to see if there's an
// existing one that needs dismissing
- (void) dismissIfNeeded
{
if (dic)
{
[dic dismissMenuAnimated:YES];
self.navigationItem.rightBarButtonItem.enabled = canOpen;
self.navigationItem.leftBarButtonItem.enabled = YES;
}
}
// Present the options menu
- (void) action: (UIBarButtonItem *) bbi
{
[self dismissIfNeeded];
dic = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
dic.delegate = self;
self.navigationItem.leftBarButtonItem.enabled = NO;
[dic presentOptionsMenuFromBarButtonItem:bbi animated:YES];
}
// Present the open-in menu
- (void) open: (UIBarButtonItem *) bbi
{
[self dismissIfNeeded];
dic = [UIDocumentInteractionController interactionControllerWithURL:fileURL];
dic.delegate = self;
self.navigationItem.rightBarButtonItem.enabled = NO;
[dic presentOpenInMenuFromBarButtonItem:bbi animated:YES];
}
#pragma mark Test for Open-ability
-(BOOL)canOpen: (NSURL *) aFileURL
{
UIDocumentInteractionController *tmp =
[UIDocumentInteractionController
interactionControllerWithURL:aFileURL];
BOOL success = [tmp presentOpenInMenuFromRect:CGRectMake(0,0,1,1)
inView:self.view animated:NO];
[tmp dismissMenuAnimated:NO];
return success;
}
- (void) viewDidAppear:(BOOL)animated
{
// Only enable right button if the file can be opened
canOpen = [self canOpen:fileURL];
self.navigationItem.rightBarButtonItem.enabled = canOpen;
}
#pragma mark View management
- (void) loadView
{
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Open in...", @selector(open:));
self.navigationItem.leftBarButtonItem =
SYSBARBUTTON(UIBarButtonSystemItemAction,
@selector(action:));
NSString *filePath = [NSHomeDirectory()
stringByAppendingPathComponent:@"Documents/DICImage.jpg"];
fileURL = [NSURL fileURLWithPath:filePath];
}
@end