UIPrint​Interaction​Controller

377 查看

原文链接:http://nshipster.com/uiprintinteractioncontroller/

前言

UIKit使用户设备直接打印变得方便起来,而且打印可以根据内容或者纸张大小进行自定义排版。这篇文章的意义在于让你明白在打印的时候如何格式化你的内容,详细阐述不同的方式来展示打印接口。

Xcode6中,打印机模拟器作为硬件IO工具属于其的一部分。

UIKit Printing APIs核心是UIPrintInteractionController。这个类共享实例管理着打印任务还有展示给用户任何UI的配置。同样它还提供了3个级别对格式化内容的控制。

打印任务

首先看下打印选项配置

UIPrintInfo

UIPrintInfo实例中存放着打印任务详情设置。你可以找到如下属性:

  • jobName String:打印任务名字。它会在设备打印中心上展示,对于某些打印机会LCD展示哦。

    • orientation UIPrintInfoOrientation.Portrait(默认)或是.Landscape-通常这个属性被略去如果打印是固定朝向,比如PDF。
    • duplex UIPrintInfoDuplex.None, .ShortEdge, or .LongEdge.。长短边缘设置说明双面打印可被约束,但是None表示不支持双面打印。
    • outputType UIPrintInfoOutputType:让UIKit知道你打印模式。可以为下面任意一种:
      1).General (default):可图文混排,可双面打印
      2).Grayscale:如果内容只含黑体文本的话效果比General好
      3).Photo:彩色或者黑白图片,不允许双面打印,要求胶片纸质
      4)PhotoGrayscale:仅黑白效果的话比3)好

    • printerID String?:特定打印机ID,通过这个检索到该打印机进行预设值。

另外,UIPrintInfo提供了一个dictionaryRepresentation属性,可以被保存并后续用来创建一个新的UIPrintInfo实例。

UIPrintInteractionController设置

这里有些关于UIPrintInteractionController的设置你需要配置下在开始展示打印UI之前。它们包括:

  • printInfo UIPrintInfo:上述的配置

    • printPaper UIPrintPaper:一个简单得类型来描述打印纸得物理性质和可打印的大小;除非是特别的应用,否则这将有UIKit代劳设置。
    • showsNumberOfCopies Bool:当设置为true的时候,用户可以设置拷贝的数量。
    • showsPageRange Bool:当设置为true,让用户在打印介质上选择打印范围。只有当有多页的内容时候这个选项才有意义,对于图片来说这个选项默认关闭。
    • showsPaperSelectionForLoadedPapers Bool:当设置为true并且选择的打印机有多打印纸的选项,那么UI会询问用户选择那个打印纸来打印。

将你的内容格式化

通过UIPrintInteractionController四个不同的属性,你可以选择你想要的内容控制规格(复杂度)。

  • printingItem AnyObject! or printingItems [AnyObject]!:最基础的水平,控制器只是简单得获取可打印的内容(图片或者是PDF)并将他们发送给打印器。

    • printFormatter UIPrintFormatter:下一个等级,你可以使用UIPrintFormatter子类在你的应用中格式化内容。你可以对格式进行一些控制,打印API基本完成剩下的部分。
    • printPageRenderer UIPrintPageRenderer:最高级水平,你可以创建UIPrintPageRenderer子类,混合页面格式还有你自己所涉及的头、尾还有页面内容。

下面根据假想的感恩节菜谱来介绍这些打印属性。

根据printItem(s)进行打印

你可以通过设置UIPrintInteractionController的属性printItemprintItems来打印已存在且可以打印的内容。图片跟PDF可以以图片数据或者通过NSURL地址引用加载到NSData对象等形式提交。为了能够打印,图片必须是UIImage支持的格式。

让我们来看这么一个简单的案例:展示UI当用户点击按钮的时候用来打印图片。处理流程基本相同,无论你要打印什么-配置你的打印信息,搭建打印交互控制器,并且在展示UI之前提供你的打印内容:

@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)
    }
}

是吧,很容易吧。。。

这若是iPhone则使用presentAnimated(:completionHandler:)方法弹出打印UI。如果是iPad则使用presentFromBarButtonItem(:animated:completionHandler:) or presentFromRect(:inView:animated:completionHandler:)

UIPrintFormatter

UIPrintFormatter类有两个子类(UISimpleTextPrintFormatter and UIMarkupTextPrintFormatter)可以用来格式化文本附加上另外一个UIViewPrintFormatter可以格式化受支持的三种视图内容:UITextView, UIWebView, 和 MKMapView。打印格式有一些属性允许你去用不同的方式定义页面打印区域;

  • contentInsets UIEdgeInsets:整体内容在打印纸四个边缘内偏移一套值。左右适用用每个页面,但是上部偏移只适用于首页。底部偏移忽略。

    • perPageContentInsets UIEdgeInsets(仅限iOS8):每一页格式化后的内容四个边缘内部偏移一套值。
    • maximumContentWidth and maximumContentHeight CGFloat:如果有指定,可以进一步约束内容区域的长与高。

尽管苹果没有文档化说明,所有这些值还是基于每英寸72个点。

两文本类打印格式化与文本一起初始化,文本将会被格式。UISimpleTextPrintFormatter将会处理一半或者属性文本,而UIMarkupTextPrintFormatter使用markupText属性处理和渲染HTML文本。让我们试着发送一个HTML版本的Swiss chard菜谱通过装饰格式化:


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

内置的格式化好用,但是大部分对打印纸的控制,你可以通过继承UIPrintPageRenderer来执行。在子类中你可以混合上述打印的格式化使用你自定义的绘制方法为你的应用内容创建很牛X的排版。让我们看下更多打印菜谱的方式,此时使用的是在菜谱中添加了一个头以及在一段文字旁边画了一张图片的页面渲染。

在初始化时,我们保存了我们将要打印的数据,然后设置headerHeight并为菜谱文本创建了装饰文本格式化。

戳这里点击示例查阅

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查询要进行如此渲染的也页面数。如果你确定要自定义的页面排版,实现umberOfPages()来提供一个正确值。

接下来,我们需要重写drawHeaderForPageAtIndex(:inRect:)来画出我们的自定义头。不幸的是原来每个页面打印格式化中简单内容的内偏值现在不在了,所以我们首先需要让headerRect参数来适应我的边沿,然后简单地划入当前图形上下文。这里有一个简单的drawFooterForPageAtIndex(:inRect:)方法来画足部。

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:):

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属性,这样我们就准备好打印工作了。

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

最后的结果自然比内置格式化器炫多了。

值得注意的是菜单文字是有UIMarkupTextPrintFormatter进行格式化的,而头部还有图片是通过自定义代码格式化的。

打印共享页面

使用上述描述的工具,在共享页面添加打印能力变得简单起来。与使用UIPrintInteractionController弹出打印UI不同,我们传递我们自己配置UIPrintInfo以及打印项,格式化,或者是渲染器给UIActivityViewController。如果用户在共享页面选择打印按钮,打印UI会完整展示我们的设置。

@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)
}

UIPrintInfoUIPrintFormatter以及UIPrintPageRenderer的子类可以以活动的形式传递给UIActivityViewController的时候,它们似乎都不遵循UIActivityItemSource协议,所以你会看到一个告警"Unknown activity items."

跳过打印UI

现在iOS8可以略过打印UI。你可以在应用中提供一个方式让用户选中一个打印机进行更方便的使用UIPrinterPickerController。它在构造器中接受UIPrinter实例选项来进行预选择,使用上述同样的弹出选项,并且在用户选择她得打印器的时候有一个响应句柄:

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

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

现在你可以让你的UIPrintInteractionController通过调用printToPrinter(:completionHandler:)通过保存过的打印机进行直接打印而不是通过调用present...方法。

最后一个建议。。。略。。。呵呵