随着各种不同方式的评论、标记、保存、分享都通过指尖的操作完成,纸质印刷品的价值很容易被人忽视。

UIKit 可以很容易的把用户设备里存储的定制设计直接打印出来,并且可以兼容内容和纸张大小。本文将首先概述如何格式化你的内容以便打印,然后详细介绍呈现(或不用呈现!)打印界面的不同方式。


这篇文章的“打印”图像都来自苹果的 打印机模拟器。(黄色边表示纸张的非打印边距)

在 Xcode 6 上,打印机模拟器必须下载,它是Xcode 的硬件 IO 工具的一部分。





UIKit 打印 APIs 的核心是 UIPrintInteractionController。这个类的一个共享实例管理着打印工作的细节和配置任何将要呈现给用户的 UI。它还为你的内容的格式提供了三个级别的控制。

打印一个任务

在我们看看如何格式化打印的实际内容之前,让我们先看一下配置打印任务的选项和呈现给用户的打印选项。

UIPrintInfo

UIPrintInfo

jobName

  •  

String

  • :此打印任务的名称。这个名字将被显示在设备的打印中心,对于有些打印机则显示在液晶屏上。

orientation

  •  

UIPrintInfoOrientation

.Portrait

  •  (默认值)或

.Landscape

  • ,如果你打印的内容有一个内置的方向值,如 PDF,这个属性将被忽略。

duplex

  •  

UIPrintInfoDuplex

.None

.ShortEdge

  •  或 

.LongEdge

  • 。short- 和 long- 的边界设置指示如何装订双面页面,而 

.NoneoutputType

  •  

UIPrintInfoOutputType

  • :给 UIKit 提供要打印内容的类型提示。可以是以下任意一个:

.General

  • (默认):文本和图形混合类型;允许双面打印。

.Grayscale

  • :如果你的内容只包括黑色文本,那么该类型比 

.General.Photo

  • :彩色或黑白图像;禁用双面打印,更适用于图像媒体的纸张类型。

.PhotoGrayscale

  • :对于仅灰度的图像,根据打印机的不同,该类型可能比 

.PhotoprinterID

  •  

String?

  • :一个特定的打印机的 ID,当用户通过 UI 选择过打印机并且保存它作为未来打印预设之后,你才能得到这个类型。

UIPrintInfo 还提供一个 dictionaryRepresentation 属性,它可以被保存并用来创建一个新的UIPrintInfo

UIPrintInteractionController

UIPrintInteractionController

printInfo

  •  

UIPrintInfo

  • :之前所述的打印任务的配置。

printPaper

  •  

UIPrintPaper

  • :纸张类型的物理和打印尺寸的一个简单的类型描述;除了专门的应用程序,这将由 UIKit 处理。

showsNumberOfCopies

  •  

Bool

  • :当值为 

trueshowsPageRange

  •  

Bool

  • :当值为 

trueshowsPaperSelectionForLoadedPapers

  •  

Bool

  • :当值为 

true

格式化你的内容

UIPrintInteractionController

printingItem

  1.  

AnyObject!

  1.  或 

printingItems

  1.  

[AnyObject]!

  1. :最基本的等级,控制器只需要已经可打印(图像和 PDF 文件)的内容,并将它们发送到打印机。

printFormatter

  1.  

UIPrintFormatter

  1. :更高等级,你可以在应用程序内使用一个 

UIPrintFormatter

  1.  的子类来对内容进行格式化,然后传给

UIPrintInteractionController

  1. 。你已经做了一些格式化,剩下的大部分事情打印 API 会处理。

printPageRenderer

  1.  

UIPrintPageRenderer

  1. :最高级别,你可以创建

UIPrintPageRenderer

为了说明这些特性,正好感恩节(我最喜欢的节日)将至,我们将假想一个感恩节食谱的应用程序并增加打印不同页面的功能。

printItem(s) 打印

UIPrintInteractionController 的 printItem 或 printItems 属性打印预存的可打印内容。图像和 PDF 文件可以通过图象数据(NSDataUIImage 或 ALAsset 实例),或通过任何 NSURL引用的东西被加载到一个 NSData 对象来得到。要打印,图像必须是UIImage.

让我们来看一个非常简单的例子:当用户点击一个按钮时显示打印图像的 UI。(我们下面将看到初始化打印的几个方式。)这个过程是大致相同的,不管你是怎么设置的打印信息或设置打印交互控制器和显示 UI 之前提供的内容:


SwiftObjective-C

@IBAction func print(sender: UIBarButtonItem) {
    if UIPrintInteractionController.canPrintURL(imageURL) {
        let printInfo = UIPrintInfo(dictionary: nil)
        printInfo.jobName = imageURL.lastPathComponent
        printInfo.outputType = .Photo

        let printController = UIPrintInteractionController.sharedPrintController()!
        printController.printInfo = printInfo
        printController.showsNumberOfCopies = false

        printController.printingItem = imageURL

        printController.presentAnimated(true, completionHandler: nil)
    }
}


(或者应景的说,像做煎瑞士甜菜一样简单。)


presentAnimated(:completionHandler:) 方法是在 iPhone 上呈现打印 UI。如果是从iPad 打印,使用 presentFromBarButtonItem(:animated:completionHandler:) 或presentFromRect(:inView:animated:completionHandler:)

UIPrintFormatter

UIPrintFormatter 类有两个子类可用于格式化文本(UISimpleTextPrintFormatter 和UIMarkupTextPrintFormatter) 另外还有 (UIViewPrintFormatter)可以格式化三种试图的内容:UITextViewUIWebView 和 MKMapView。打印格式化器有几个特性,让你以不同方式定义页面的打印区域;格式化器的最终打印区域将是满足以下条件的最小矩形:

contentInsets

  •  

UIEdgeInsets

  • :一个全部内容页面的边缘插图集合。左和右插图被应用在每一页上,但顶部边界则只应用在第一页上。底部插图将被忽略。

perPageContentInsets

  •  

UIEdgeInsets

  • (仅 iOS 8):一个每一页格式化内容页面的边缘插图集。

maximumContentWidth

  •  和 

maximumContentHeight

  •  

CGFloat

  • :如果指定,可以进一步约束内容区域的宽度和高度。

虽然 Apple 的文档没有明确说明,但所有这些值都基于每英寸 72 点。

UISimpleTextPrintFormatter 可以处理普通或属性文本,而 UIMarkupTextPrintFormatter 用其 markupText

SwiftObjective-C

let formatter = UIMarkupTextPrintFormatter(markupText: htmlString)
formatter.contentInsets = UIEdgeInsets(top: 72, left: 72, bottom: 72, right: 72) // 1" margins

printController.printFormatter = formatter


结果嘞?一个漂亮的 HTML 页面:


UIViewPrintFormatter,你可以从 viewPrintFormatter

1) UITextView


2) UIWebView


3) MKMapView


UIPrintPageRenderer

最好的控制,你可以实现 UIPrintPageRendererheaderHeight(除非你设置各自的高度,否则页眉和页脚的绘图方法将不会被调用),并为菜谱的文字创建一个标记文本格式。

下面例子的完整 Objective-C 和 Swift 源代码可以在 gist 下载


SwiftObjective-C

class RecipePrintPageRenderer: UIPrintPageRenderer {
    let authorName: String
    let recipe: Recipe

    init(authorName: String, recipe: Recipe) {
        self.authorName = authorName
        self.recipe = recipe
        super.init()

        self.headerHeight = 0.5 * POINTS_PER_INCH
        self.footerHeight = 0.0 // default

        let formatter = UIMarkupTextPrintFormatter(markupText: recipe.html)
        formatter.perPageContentInsets = UIEdgeInsets(top: POINTS_PER_INCH, left: POINTS_PER_INCH,
            bottom: POINTS_PER_INCH, right: POINTS_PER_INCH * 3.5)
        addPrintFormatter(formatter, startingAtPageAtIndex: 0)
    }

    // ...
}


当你使用一个或多个打印格式作为自定义渲染的一部分(正如我们在这里所做的一样),UIKit 会查询它们的打印页数。如果你正在做真正的自定义页面布局,可以实现numberOfPages()

drawHeaderForPageAtIndex(:inRect:) 来绘制我们的自定义标题。遗憾的是,打印格式的那些方便的为每个页面内容设置插图的功能在这儿都没有了,所以我们首先需要插入headerRect 参数来适应边距,然后简单地绘制到当前的图形上下文中。还有一个类似的drawFooterForPageAtIndex(:inRect:)

SwiftObjective-C

override func drawHeaderForPageAtIndex(pageIndex: Int, var inRect headerRect: CGRect) {
    var headerInsets = UIEdgeInsets(top: CGRectGetMinY(headerRect), left: POINTS_PER_INCH, bottom: CGRectGetMaxY(paperRect) - CGRectGetMaxY(headerRect), right: POINTS_PER_INCH)
    headerRect = UIEdgeInsetsInsetRect(paperRect, headerInsets)

    // author name on left
    authorName.drawAtPointInRect(headerRect, withAttributes: nameAttributes, andAlignment: .LeftCenter)

    // page number on right
    let pageNumberString: NSString = "\(pageIndex + 1)"
    pageNumberString.drawAtPointInRect(headerRect, withAttributes: pageNumberAttributes, andAlignment: .RightCenter)
}


drawContentForPageAtIndex(:inRect:)

SwiftObjective-C

override func drawContentForPageAtIndex(pageIndex: Int, inRect contentRect: CGRect) {
    if pageIndex == 0 {
        // only use rightmost two inches of contentRect
        let imagesRectWidth = POINTS_PER_INCH * 2
        let imagesRectHeight = paperRect.height - POINTS_PER_INCH - (CGRectGetMaxY(paperRect) - CGRectGetMaxY(contentRect))
        let imagesRect = CGRect(x: CGRectGetMaxX(paperRect) - imagesRectWidth - POINTS_PER_INCH, y: paperRect.origin.y + POINTS_PER_INCH, width: imagesRectWidth, height: imagesRectHeight)

        drawImages(recipe.images, inRect: imagesRect)
    }
}


pageRenderer

SwiftObjective-C

let renderer = RecipePrintPageRenderer(authorName: "Nate Cook", recipe: selectedRecipe)
printController.printPageRenderer = renderer


最后的结果比任何内置格式都要好得多。

需要注意的是菜谱的文本是由一个 UIMarkupTextPrintFormatter


通过共享表单打印

UIPrintInfo 和打印项目及格式来显示或渲染到 UIActivityViewController 来显示打印 UI,而不是使用 UIPrintInteractionController 来呈现。如果用户在共享表单里选择了打印按钮,打印界面将完好的显示我们所有的配置。

SwiftObjective-C

@IBAction func openShareSheet() {
    let printInfo = ...
    let formatter = ...

    let activityItems = [printInfo, formatter, textView.attributedText]
    let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
    presentViewController(activityController, animated: true, completion: nil)
}


虽然 UIPrintInfo 以及 UIPrintFormatter 和 UIPrintPageRenderer 的子类可以作为活动传递到 UIActivityViewController,但他们都不符合 UIActivityItemSource

跳过打印 UI

UIPrinterPickerController 在应用程序中为你的用户在某个地方选择一台打印机提供一种方法。它的构造方法接受可选的 UIPrinter

SwiftObjective-C

let printerPicker = UIPrinterPickerController(initiallySelectedPrinter: savedPrinter)
printerPicker.presentAnimated(true) {
    (printerPicker, userDidSelect, error) in

    if userDidSelect {
        self.savedPrinter = printerPicker.selectedPrinter
    }
}


UIPrintInteractionController 调用 printToPrinter(:completionHandler:) 来使用已保存的打印机而不是调用某一个 present...


在纸张上测试你的打印布局 - 另外,大小和边距都应该最好使用适中的值。