UIDocumentInteractionController

简介

官方文档:UIDocumentInteractionController

一种视图控制器,用于预览、打开或打印应用程序无法直接处理的文件格式的文件。名字叫控制器,但却是继承自 NSObject ……

iPhone

在 iOS 上简单的使用它来打开文件是类似这样的:

初始化的时候,基本就是指定文件路径、指定本应用内打开,还是直接弹出选择菜单可以其他应用内打开

  • 初始化:
1
2
let url = Bundle.main.url(forResource: "hhh", withExtension: "docx")
let documentController = UIDocumentInteractionController.init(url: url!)
  • 本应用内预览文档
1
2
3
4
5
6
7
8
9
10
11
//设置代理 --本应用内预览必须要添加代理 UIDocumentInteractionControllerDelegate
documentController.delegate = self
//当前APP打开 需实现协议方法才可以完成预览功能
documentController.presentPreview(animated: true)

//...

func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
//这里需要返回给一个控制器用于展现 documentController 在其上面,所以我们就返回当前控制器self
return self
}

当我们打开一个文档的时候就是左边这样的效果,直接预览文档。右上角的分享按钮也可以选择其他 App 打开文档:

  • 其他应用内预览文档

网上很多都是说,加上打开 Menu 菜单(见右图)的一句代码就可以用第三方应用打开,例如:

1
2
3
4
let url = Bundle.main.url(forResource: "hhh", withExtension: "docx")
let documentController = UIDocumentInteractionController.init(url: url!)
documentController.delegate = self
documentController.presentOptionsMenu(from: self.view.frame, in: self.view, animated: true)

只这样写你会发现,菜单确实可以弹出,但你点分享到任何一个 App (包括 Quick Look)都是没反应的,而且在 console 中会报如下错误:

1
Could not instantiate class NSURL. Error: Error Domain=NSCocoaErrorDomain Code=4864 "The URL archive of type “public.url” contains invalid data." UserInfo={NSDebugDescription=The URL archive of type “public.url” contains invalid data.}

……

网上的资料给的解决办法是 documentController 这个变量需要被持有一下,才可以用第三方 App 打开文档,所以上面的代码就变成了这样:

1
2
3
4
5
6
7
8
9
10
class ViewController: UIViewController, UIDocumentInteractionControllerDelegate  {
var documentController = UIDocumentInteractionController()
//...
func showDocInteractController() -> Void {
let url = Bundle.main.url(forResource: "hhh", withExtension: "docx")
documentController.url = url
documentController.delegate = self
documentController.presentOptionsMenu(from: self.view.frame, in: self.view, animated: true)
}
}

持有一下就可以,咩~~~

PS : 设置 presentOptionsMenuFromRect 的话,我们给的是 全屏,那给一半儿的大小这个选择菜单会怎么弹呢,感兴趣自己试一下🤓……

这些不是重点,重点是在 iPad 上,菜单的弹出会有些不一样的现象

iPad

上面同样其他应用内预览文档的代码,测试发现点击文档以后,没有弹出选择菜单,当然文档也没有打开。console 中报注入布局异常的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2021-02-26 14:43:02.326794+0800 UIDocumentInteraction[13039:85917] [LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x600001912da0 H:|-(0)-[_UIActivityContentTitleView:0x7fa2ccc33b10] (active, names: '|':_UINavigationBarContentView:0x7fa2ccc26a40 )>",
"<NSLayoutConstraint:0x600001912e40 _UIActivityContentTitleView:0x7fa2ccc33b10.trailing == _UINavigationBarContentView:0x7fa2ccc26a40.trailing (active)>",
"<NSLayoutConstraint:0x6000019130c0 LPLinkView:0x7fa2ccc33f30.leading == UILayoutGuide:0x600000322680'UIViewLayoutMarginsGuide'.leading (active)>",
"<NSLayoutConstraint:0x600001912f30 H:[LPLinkView:0x7fa2ccc33f30]-(27)-| (active, names: '|':_UIActivityContentTitleView:0x7fa2ccc33b10 )>",
"<NSLayoutConstraint:0x600001919d10 'UIView-Encapsulated-Layout-Width' _UINavigationBarContentView:0x7fa2ccc26a40.width == 0 (active)>",
"<NSLayoutConstraint:0x600001912fd0 'UIView-leftMargin-guide-constraint' H:|-(16)-[UILayoutGuide:0x600000322680'UIViewLayoutMarginsGuide'](LTR) (active, names: '|':_UIActivityContentTitleView:0x7fa2ccc33b10 )>"
)

Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6000019130c0 LPLinkView:0x7fa2ccc33f30.leading == UILayoutGuide:0x600000322680'UIViewLayoutMarginsGuide'.leading (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
2021-02-26 14:43:02.327098+0800 UIDocumentInteraction[13039:87855] [Fetching] LPFileMetadataProviderSpecialization failed to retrieve a thumbnail from QuickLookThumbnailing (Error Domain=QLThumbnailErrorDomain Code=0 "Could not generate a thumbnail")

我们先看下 Safari 中的 Menu 是什么样子:

咦,无论横竖屏,Menu 不再是以屏幕宽度或者高度来处理的了,而且头上多了一点 “小尖尖~”。所以猜测我们需要给定它一个弹出点。但 API 貌似又没有对应的接口。

参考了官方一个 issue 讨论,emmmmmm,其实我也没太看懂啥意思 😂,总之~~

1
2
let frame = CGRect.init(x: self.view.frame.midX, y: self.view.frame.midY, width: 0, height: self.view.frame.height * 0.5)
documentController.presentOptionsMenu(from: frame, in: self.view, animated: true)

这句代码中的 frame 不给全屏就可以正常弹出,小尖尖的位置自己有兴趣可以调一调玩一玩儿~~