UIPrintInteractionController
随着各种不同方式的评论、标记、保存、分享都通过指尖的操作完成,纸质印刷品的价值很容易被人忽视。
UIKit 可以很容易的把用户设备里存储的定制设计直接打印出来,并且可以兼容内容和纸张大小。本文将首先概述如何格式化你的内容以便打印,然后详细介绍呈现(或不用呈现!)打印界面的不同方式。
这篇文章的“打印”图像都来自苹果的 打印机模拟器。(黄色边表示纸张的非打印边距)
在 Xcode 6 上,打印机模拟器必须下载,它是Xcode 的硬件 IO 工具的一部分。
UIKit 打印 APIs 的核心是 UIPrint
。这个类的一个共享实例管理着打印工作的细节和配置任何将要呈现给用户的 UI。它还为你的内容的格式提供了三个级别的控制。
打印一个任务
在我们看看如何格式化打印的实际内容之前,让我们先看一下配置打印任务的选项和呈现给用户的打印选项。
UIPrintInfo
打印任务细节在 UIPrint
实例中设置。可以使用以下属性:
job
Name String
:此打印任务的名称。这个名字将被显示在设备的打印中心,对于有些打印机则显示在液晶屏上。orientation
UIPrint
:Info Orientation .Portrait
(默认值)或.Landscape
,如果你打印的内容有一个内置的方向值,如 PDF,这个属性将被忽略。duplex
UIPrint
:Info Duplex .None
、.Short
或Edge .Long
。short- 和 long- 的边界设置指示如何装订双面页面,而Edge .None
不支持双面打印(这里不是 UI 切换为双面打印,令人困惑)。output
Type UIPrint
:给 UIKit 提供要打印内容的类型提示。可以是以下任意一个:Info Output Type
.General
(默认):文本和图形混合类型;允许双面打印。.Grayscale
:如果你的内容只包括黑色文本,那么该类型比.General
更好。.Photo
:彩色或黑白图像;禁用双面打印,更适用于图像媒体的纸张类型。.Photo
:对于仅灰度的图像,根据打印机的不同,该类型可能比Grayscale .Photo
更好。printer
ID String?
:一个特定的打印机的 ID,当用户通过 UI 选择过打印机并且保存它作为未来打印预设之后,你才能得到这个类型。
此外,UIPrint
还提供一个 dictionary
属性,它可以被保存并用来创建一个新的 UIPrint
实例。
UIPrint Interaction Controller
设置
UIPrint
有一些很便捷的设置,可以在显示打印 UI 之前配置设置。包括:
Info UIPrint
:之前所述的打印任务的配置。Info Paper UIPrint
:纸张类型的物理和打印尺寸的一个简单的类型描述;除了专门的应用程序,这将由 UIKit 处理。Paper shows
Number Of Copies Bool
:当值为true
时,让用户选择拷贝的份数。shows
Page Range Bool
:当值为true
时,让用户从打印源中选择一个子范围。这只在多页内容时有用,它默认关闭了图像。shows
Paper Selection For Loaded Papers Bool
:当值为true
并且所选择的打印机有多个纸张选项时,用户界面将让用户选择用于打印的纸张。
格式化你的内容
通过 UIPrint
四个不同的属性,可以选择内容的控制(和复杂性)级别。
printing
Item Any
或Object! printing
Items [Any
:最基本的等级,控制器只需要已经可打印(图像和 PDF 文件)的内容,并将它们发送到打印机。Object]! Formatter UIPrint
:更高等级,你可以在应用程序内使用一个Formatter UIPrint
的子类来对内容进行格式化,然后传给Formatter UIPrint
。你已经做了一些格式化,剩下的大部分事情打印 API 会处理。Interaction Controller Page Renderer UIPrint
:最高级别,你可以创建Page Renderer UIPrint
的一个自定义子类,结合页面格式和自己的绘图程序来绘制页眉、页脚和页面内容。Page Renderer
为了说明这些特性,正好感恩节(我最喜欢的节日)将至,我们将假想一个感恩节食谱的应用程序并增加打印不同页面的功能。
print Item
(s
) 打印
用 你可以通过设置 UIPrint
的 print
或 print
属性打印预存的可打印内容。图像和 PDF 文件可以通过图象数据(NSData
,UIImage
或 ALAsset
实例),或通过任何 NSURL
引用的东西被加载到一个 NSData
对象来得到。要打印,图像必须是UIImage
支持的格式.
让我们来看一个非常简单的例子:当用户点击一个按钮时显示打印图像的 UI。(我们下面将看到初始化打印的几个方式。)这个过程是大致相同的,不管你是怎么设置的打印信息或设置打印交互控制器和显示 UI 之前提供的内容:
@IBAction func print(sender: UIBar Button Item) {
if UIPrint Interaction Controller.can Print URL(image URL) {
let print Info = UIPrint Info(dictionary: nil)
print Info.job Name = image URL.last Path Component
print Info.output Type = .Photo
let print Controller = UIPrint Interaction Controller.shared Print Controller()!
print Controller.print Info = print Info
print Controller.shows Number Of Copies = false
print Controller.printing Item = image URL
print Controller.present Animated(true, completion Handler: nil)
}
}
- (IBAction)print:(id)sender {
if ([UIPrint Interaction Controller can Print URL:self.image URL]) {
UIPrint Info *print Info = [UIPrint Info print Info];
print Info.job Name = self.image URL.last Path Component;
print Info.output Type = UIPrint Info Output General;
UIPrint Interaction Controller *print Controller = [UIPrint Interaction Controller shared Print Controller];
print Controller.print Info = print Info;
print Controller.printing Item = self.image URL;
[print Controller present Animated:true completion Handler: nil];
}
}
易如反掌! (或者应景的说,像做煎瑞士甜菜一样简单。)
present
方法是在 iPhone 上呈现打印 UI。如果是从 iPad 打印,使用Animated(:completion Handler:) present
或From Bar Button Item(:animated:completion Handler:) present
方法代替。From Rect(:in View:animated:completion Handler:)
UIPrintFormatter
UIPrint
类有两个子类可用于格式化文本(UISimple
和 UIMarkup
) 另外还有 (UIView
)可以格式化三种视图的内容:UIText
、UIWeb
和 MKMap
。打印格式化器有几个特性,让你以不同方式定义页面的打印区域;格式化器的最终打印区域将是满足以下条件的最小矩形:
content
Insets UIEdge
:一个全部内容页面的边缘插图集合。左和右插图被应用在每一页上,但顶部边界则只应用在第一页上。底部插图将被忽略。Insets per
Page Content Insets UIEdge
(仅 iOS 8):一个每一页格式化内容页面的边缘插图集。Insets maximum
和Content Width maximum
Content Height CGFloat
:如果指定,可以进一步约束内容区域的宽度和高度。
虽然 Apple 的文档没有明确说明,但所有这些值都基于每英寸 72 点。
这两个基于文本的打印格式将同需要格式化的文本一起被初始化。UISimple
可以处理普通或属性文本,而 UIMarkup
用其 markup
属性呈现 HTML 文本。让我们尝试通过标记格式发送我们 HTML 版本的瑞士甜菜菜谱:
let formatter = UIMarkup Text Print Formatter(markup Text: html String)
formatter.content Insets = UIEdge Insets(top: 72, left: 72, bottom: 72, right: 72) // 1" margins
print Controller.print Formatter = formatter
UIMarkup Text Print Formatter *formatter = [[UIMarkup Text Print Formatter alloc] init With Markup Text:html String];
formatter.content Insets = UIEdge Insets Make(72, 72, 72, 72); // 1" margins
print Controller.print Formatter = formatter;
结果嘞?一个漂亮的 HTML 页面:
另一方面,使用 UIView
,你可以从 view
属性得到一个你想要打印的视图。下面就来看看格式化如何格式它所支持的三个视图的:
1) UITextView
2) UIWebView
3) MKMapView
UIPrintPageRenderer
内置的格式化都很好,但为了对打印页面实现最好的控制,你可以实现 UIPrint
的一个子类。在你的子类里,你可以结合我们上面看到的打印格式和你自定义的绘图函数为你的应用程序内容创建出色的布局。让我们来看看打印食谱的另一种方式,这次使用网页渲染器来添加页眉和绘制图像旁边的配方文本。
在初始化中,我们需要把打印的数据保存下来,然后设置 header
(除非你设置各自的高度,否则页眉和页脚的绘图方法将不会被调用),并为菜谱的文字创建一个标记文本格式。
下面例子的完整 Objective-C 和 Swift 源代码可以在 gist 下载。
class Recipe Print Page Renderer: UIPrint Page Renderer {
let author Name: String
let recipe: Recipe
init(author Name: String, recipe: Recipe) {
self.author Name = author Name
self.recipe = recipe
super.init()
self.header Height = 0.5 * POINTS_PER_INCH
self.footer Height = 0.0 // default
let formatter = UIMarkup Text Print Formatter(markup Text: recipe.html)
formatter.per Page Content Insets = UIEdge Insets(top: POINTS_PER_INCH, left: POINTS_PER_INCH,
bottom: POINTS_PER_INCH, right: POINTS_PER_INCH * 3.5)
add Print Formatter(formatter, starting At Page At Index: 0)
}
// ...
}
@interface Recipe Print Page Renderer : UIPrint Page Renderer
@property (nonatomic, strong) NSString *author Name;
@property (nonatomic, strong) Recipe *recipe;
- (id)init With Author Name:(NSString *)author Name
recipe:(Recipe *)recipe;
@end
@implementation Recipe Print Page Renderer
- (id)init With Author Name:(NSString *)author Name
recipe:(Recipe *)recipe
{
if (self = [super init]) {
self.author Name = author Name;
self.recipe = recipe;
self.header Height = 0.5;
self.footer Height = 0.0; // default
UIMarkup Text Print Formatter *formatter = [[UIMarkup Text Print Formatter alloc] init With Markup Text:recipe.html];
formatter.per Page Content Insets = UIEdge Insets Make(POINTS_PER_INCH, POINTS_PER_INCH, POINTS_PER_INCH, POINTS_PER_INCH * 3.5);
[self add Print Formatter:formatter starting At Page At Index:0];
}
return self;
}
// ...
@end
当你使用一个或多个打印格式作为自定义渲染的一部分(正如我们在这里所做的一样),UIKit 会查询它们的打印页数。如果你正在做真正的自定义页面布局,可以实现
number
方法来提供正确的值。Of Pages()
接下来,我们重写 draw
来绘制我们的自定义标题。遗憾的是,打印格式的那些方便的为每个页面内容设置插图的功能在这儿都没有了,所以我们首先需要插入 header
参数来适应边距,然后简单地绘制到当前的图形上下文中。还有一个类似的 draw
方法来绘制页脚。
override func draw Header For Page At Index(page Index: Int, var in Rect header Rect: CGRect) {
var header Insets = UIEdge Insets(top: CGRect Get Min Y(header Rect), left: POINTS_PER_INCH, bottom: CGRect Get Max Y(paper Rect) - CGRect Get Max Y(header Rect), right: POINTS_PER_INCH)
header Rect = UIEdge Insets Inset Rect(paper Rect, header Insets)
// author name on left
author Name.draw At Point In Rect(header Rect, with Attributes: name Attributes, and Alignment: .Left Center)
// page number on right
let page Number String: NSString = "\(page Index + 1)"
page Number String.draw At Point In Rect(header Rect, with Attributes: page Number Attributes, and Alignment: .Right Center)
}
- (void)draw Header For Page At Index:(NSInteger)index
in Rect:(CGRect)header Rect
{
UIEdge Insets header Insets = UIEdge Insets Make(CGRect Get Min Y(header Rect), POINTS_PER_INCH, CGRect Get Max Y(self.paper Rect) - CGRect Get Max Y(header Rect), POINTS_PER_INCH);
header Rect = UIEdge Insets Inset Rect(self.paper Rect, header Insets);
// author name on left
[self.author Name draw At Point In Rect:header Rect with Attributes:self.name Attributes and Alignment:NCString Alignment Left Center];
// page number on right
NSString *page Number String = [NSString string With Format:@"%ld", index + 1];
[page Number String draw At Point In Rect:header Rect with Attributes:self.page Number Attributes and Alignment:NCString Alignment Right Center];
}
最后,让我们来实现一个 draw
:
override func draw Content For Page At Index(page Index: Int, in Rect content Rect: CGRect) {
if page Index == 0 {
// only use rightmost two inches of content Rect
let images Rect Width = POINTS_PER_INCH * 2
let images Rect Height = paper Rect.height - POINTS_PER_INCH - (CGRect Get Max Y(paper Rect) - CGRect Get Max Y(content Rect))
let images Rect = CGRect(x: CGRect Get Max X(paper Rect) - images Rect Width - POINTS_PER_INCH, y: paper Rect.origin.y + POINTS_PER_INCH, width: images Rect Width, height: images Rect Height)
draw Images(recipe.images, in Rect: images Rect)
}
}
- (void)draw Content For Page At Index:(NSInteger)page Index
in Rect:(CGRect)content Rect
{
if (page Index == 0) {
// only use rightmost two inches of content Rect
CGFloat images Rect Width = POINTS_PER_INCH * 2;
CGFloat images Rect Height = CGRect Get Height(self.paper Rect) - POINTS_PER_INCH - (CGRect Get Max Y(self.paper Rect) - CGRect Get Max Y(content Rect));
CGRect images Rect = CGRect Make(CGRect Get Max X(self.paper Rect) - images Rect Width - POINTS_PER_INCH, CGRect Get Min Y(self.paper Rect) + POINTS_PER_INCH, images Rect Width, images Rect Height);
[self draw Images:self.recipe.images in Rect:images Rect];
}
}
当我们的自定义页面的渲染实现完成后,我们可以设置一个实例作为打印交互控制器上的 page
属性,这样我们就可以准备进行打印了。
let renderer = Recipe Print Page Renderer(author Name: "Nate Cook", recipe: selected Recipe)
print Controller.print Page Renderer = renderer
Recipe Print Page Renderer *renderer = [[Recipe Print Page Renderer alloc] init With Author Name:@"Nate Cook" recipe:selected Recipe];
print Controller.print Page Renderer = renderer;
最后的结果比任何内置格式都要好得多。
需要注意的是菜谱的文本是由一个
UIMarkup
来格式化的,然而页眉和图像则通过自定义代码绘制。Text Formatter
通过共享表单打印
有了上面这些我们已经学会了的工具,在共享表单里添加打印功能也很简单。我们把配置的 UIPrint
和打印项目及格式来显示或渲染到 UIActivity
来显示打印 UI,而不是使用 UIPrint
来呈现。如果用户在共享表单里选择了打印按钮,打印界面将完好的显示我们所有的配置。
@IBAction func open Share Sheet() {
let print Info = ...
let formatter = ...
let activity Items = [print Info, formatter, text View.attributed Text]
let activity Controller = UIActivity View Controller(activity Items: activity Items, application Activities: nil)
present View Controller(activity Controller, animated: true, completion: nil)
}
- (IBAction)open Share Sheet:(id)sender {
UIPrint Info *print Info = ...
UISimple Text Print Formatter *formatter = ...
NSArray *activity Items = @[print Info, formatter, self.text View.attributed Text];
UIActivity View Controller *activity Controller = [[UIActivity View Controller alloc] init With Activity Items:activity Items application Activities:nil];
[self present View Controller:activity Controller animated:YES completion:nil];
}
虽然
UIPrint
以及Info UIPrint
和Formatter UIPrint
的子类可以作为活动传递到Page Renderer UIActivity
,但他们都不符合View Controller UIActivity
协议,所以你会在控制台看到一个 “Unknown activity items.” 的(无害)警告。Item Source
跳过打印 UI
在新的 iOS 8 里,有一种方式可以在没有任何 UI 展示的情况下打印。不必在用户每次按下打印按钮时呈现 UI,你可以用好用的 UIPrinter
在应用程序中为你的用户在某个地方选择一台打印机提供一种方法。它的构造方法接受可选的 UIPrinter
实例作为一个预选,可以使用上面解释过的相同的展示选项,并且当用户选择了打印机后还有一个完成回调:
let printer Picker = UIPrinter Picker Controller(initially Selected Printer: saved Printer)
printer Picker.present Animated(true) {
(printer Picker, user Did Select, error) in
if user Did Select {
self.saved Printer = printer Picker.selected Printer
}
}
UIPrinter Picker Controller *print Picker = [UIPrinter Picker Controller printer Picker Controller With Initially Selected Printer:self.saved Printer];
[print Picker present Animated:YES completion Handler:
^(UIPrinter Picker Controller *printer Picker, BOOL user Did Select, NSError *error) {
if (user Did Select) {
self.saved Printer = printer Picker.selected Printer;
}
}];
现在,你可以让你的 UIPrint
调用 print
来使用已保存的打印机而不是调用某一个 present...
方法来直接打印了。
最后有一个建议,考虑到你可能会在打印页面与你的内容进行某种方式的交互。在你仔细检查字体大小和重量或是屏幕上元素之间的差异的同时,同样需要确保在纸张上测试你的打印布局 - 另外,大小和边距都应该最好使用适中的值。