使用UIView模板使用排版创建动态大小的PDF

时间:2015-02-11 17:31:25

标签: swift pdf uiview logic

我是新手,但我已经设法学到了很多东西并创建了一个非常棒的(我希望)应用程序,它已接近完成。我最后的任务之一是创建动态生成的用户数据的PDF。它是整个过程中最令人沮丧的部分,因为没有真正的现代清晰模板或指南。 Apple的文档不是很具描述性(有些部分我不明白)和堆栈上的Q / A以及Google上的示例都看起来非常具体。我以为我几乎是通过使用UIScrollView来获得它,但最终的结果是混乱的,我不能让事情整齐排列,我也没有足够的控制。

我相信我在这项任务中的缺陷与逻辑相关,并且对可用的API不够了解,非常感谢。

我已动态创建用户内容,在UIViewController的子类中填充NSArray。该内容由字符串和图像组成。

我想使用UIView(我假设在.xib文件中)创建一个包含PDF第一页标题信息的模板(标题信息也是动态的),之后的任何页面都可以通过代码完成,因为它实际上只是一个列表。

我对UIGraphicsPDF有一点了解......并希望将文本和图像绘制到PDF中,而不只是拍摄视图的屏幕截图。

我遇到这个问题的麻烦是: (我在这里是有目的的,因为到目前为止我所做的一切都让我无处可去)

如何找出我需要多少页?

如何查看文字是否长于页面?如何拆分?

如何在PDF中绘制图像?

如何在PDF中绘制文字?

如何在PDF中绘制文本和图像,但是垂直填充,以便没有重叠并且说明具有动态行数的字符串?

如何跟踪网页?

谢谢你的阅读,我畏缩的因素并不是太高。

2 个答案:

答案 0 :(得分:4)

所以我们走了。以下是针对OSX和NSView进行的,但它很容易适应UIView(我猜)。您将需要以下脚手架:

A)PSPrintView将处理单页打印

class PSPrintView:NSView {
  var pageNo:Int = 0 // the current page
  var totalPages:Int = 0
  struct PaperDimensions {
    size:NSSize // needs to be initialized from NSPrintInfo.sharedPrintInfo
    var leftMargin, topMargin, rightMargin, bottomMargin : CGFloat
  }
  let paperDimensions = PaperDimensions(...)

  class func clone() -> PSPrintView {
    // returns a clone of self where most page parameters are copied
    // to speed up printing
  }

  override func drawRect(dirtyRect: NSRect) {
    super.drawRect(dirtyRect)

    // Drawing code here.

    // e.g. to draw a frame inside the view
    let scale = convertSize(NSMakeSize(1, 1), fromView:nil)
    var myContext = NSGraphicsContext.currentContext()!.CGContext
    CGContextSetLineWidth(myContext, scale.height)
    CGContextSetFillColorWithColor(myContext, NSColor.whiteColor().CGColor)
    CGContextFillRect (myContext, rect)
    rect.origin.x += paperDimensions.leftMargin
    rect.origin.y += paperDimensions.bottomMargin
    rect.size.width -= paperDimensions.leftMargin + paperDimensions.rightMargin
    rect.size.height -= paperDimensions.topMargin + paperDimensions.bottomMargin
    CGContextSetStrokeColorWithColor(myContext, NSColor(red: 1, green: 0.5, blue: 0, alpha: 0.5).CGColor)
    CGContextStrokeRect(myContext, rect)

    // here goes your layout with lots of String.drawInRect....
  }
}

B)PSPrint:将单个PSPrintViews保存在一个阵列中,完成后将它们发送到(PDF)打印机

class PSPrint: NSView {
  var printViews = [PSPrintView]()

  override func knowsPageRange(range:NSRangePointer) -> Bool {
    range.memory.location = 1
    range.memory.length = printViews.count
    return true
  }

  func printTheViews() {
    let sharedPrintInfo = NSPrintInfo.sharedPrintInfo()
    let numOfViews = printViews.count

    var totalHeight:CGFloat = 0;//if not initialized to 0 weird problems occur after '3' clicks to print
    var heightOfView:CGFloat = 0
    //    PSPrintView *tempView;

    for tempView in printViews {
      heightOfView = tempView.frame.size.height
      totalHeight = totalHeight + heightOfView
    }
    //Change the frame size to reflect the amount of pages.
    var newsize = NSSize()
    newsize.width = sharedPrintInfo.paperSize.width-sharedPrintInfo.leftMargin-sharedPrintInfo.rightMargin
    newsize.height = totalHeight
    setFrameSize(newsize)
    var incrementor = -1 //default the incrementor for the loop below.  This controls what page a 'view' will appear on.

    //Add the views in reverse, because the Y position is bottom not top.  So Page 3 will have y coordinate of 0.  Doing this so order views is placed in array reflects what is printed.
    for (var i = numOfViews-1; i >= 0; i--) {
      incrementor++
      let tempView = printViews[i] //starts with the last item added to the array, in this case rectangles, and then does circle and square.
      heightOfView = tempView.frame.size.height

      tempView.setFrameOrigin(NSMakePoint(0, heightOfView*CGFloat(incrementor))) //So for the rectangle it's placed at position '0', or the very last page.

      addSubview(tempView)
    }
    NSPrintOperation(view: self, printInfo: sharedPrintInfo).runOperation()
  }

C)执行打印的功能(从菜单中)

func doPrinting (sender:AnyObject) {
  //First get the shared print info object so we know page sizes.  The shared print info object acts like a global variable.
  let sharedPrintInfo = NSPrintInfo.sharedPrintInfo()

  //initialize it's base values.
  sharedPrintInfo.leftMargin = 0
  sharedPrintInfo.rightMargin = 0
  sharedPrintInfo.topMargin = 0
  sharedPrintInfo.bottomMargin = 0

  var frame = NSRect(x: 0, y: 0, width: sharedPrintInfo.paperSize.width-sharedPrintInfo.leftMargin-sharedPrintInfo.rightMargin, height: sharedPrintInfo.paperSize.height-sharedPrintInfo.topMargin-sharedPrintInfo.bottomMargin)
  //Initiate the printObject without a frame, it's frame will be decided later.
  let printObject = PSPrint ()

  //Allocate a new instance of NSView into the variable printPageView
  let basePrintPageView = PSPrintView(frame: frame)
  // do additional init stuff for the single pages if needed
  // ...

  var printPageView:PSPrintView
  for pageNo in 0..<basePrintPageView.totalPages {
    printPageView = basePrintPageView.clone()  
    //Set the option for the printView for what it should draw.
    printPageView.pageNo = pageNo
    //Finally append the view to the PSPrint Object.
    printObject.printViews.append(printPageView)
  }
  printObject.printTheViews() //print all the views, each view being a 'page'.
}

答案 1 :(得分:1)

PDF绘图代码:

import UIKit

class CreatePDF {

// Create a PDF from an array of UIViews
// Return a URL of a temp dir / pdf file

func getScaledImageSize(imageView: UIImageView) -> CGSize {

    var scaledWidth     = CGFloat(0)
    var scaledHeight    = CGFloat(0)

    let image = imageView.image!

    if image.size.height >= image.size.width {

        scaledHeight    = imageView.frame.size.height
        scaledWidth     = (image.size.width / image.size.height) * scaledHeight

        if scaledWidth > imageView.frame.size.width {

            let diff : CGFloat  = imageView.frame.size.width - scaledWidth
            scaledHeight        = scaledHeight + diff / scaledHeight * scaledHeight
            scaledWidth         = imageView.frame.size.width

        }

    } else {

        scaledWidth     = imageView.frame.size.width
        scaledHeight    = (image.size.height / image.size.width) * scaledWidth

        if scaledHeight > imageView.frame.size.height {

            let diff : CGFloat  = imageView.frame.size.height - scaledHeight
            scaledWidth         = scaledWidth + diff / scaledWidth * scaledWidth
            scaledHeight        = imageView.frame.size.height

        }

    }

    return CGSizeMake(scaledWidth, scaledHeight)

}

func drawImageFromUIImageView(imageView: UIImageView) {

    let theImage = imageView.image!

    // Get the image as it's scaled in the image view
    let scaledImageSize = getScaledImageSize(imageView)

    let imageFrame = CGRectMake(imageView.frame.origin.x, imageView.frame.origin.y, scaledImageSize.width, scaledImageSize.height)

    theImage.drawInRect(imageFrame)

}

func drawTextFromLabel(aLabel: UILabel) {

    if aLabel.text?.isEmpty == false {

        let theFont             = aLabel.font
        let theAttributedFont   = [NSFontAttributeName: theFont!]

        let theText     = aLabel.text!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) as NSString
        let theFrame    = aLabel.frame

        theText.drawInRect(theFrame, withAttributes: theAttributedFont)

    }

}

func parseSubviews(aView: UIView) {

    for aSubview in aView.subviews {

        if aSubview.isKindOfClass(UILabel) {

            // Draw label

            drawTextFromLabel(aSubview as! UILabel)

        }

        if aSubview.isKindOfClass(UIImageView) {

            // Draw image (scaled and at correct coordinates

            drawImageFromUIImageView(aSubview as! UIImageView)
        }

    }

}

func parseViewsToRender(viewsToRender: NSArray) {

    for aView in viewsToRender as! [UIView] {

        UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil)

        parseSubviews(aView)

    }

}

func createPdf(viewsToRender: NSArray, filename: String) -> NSURL {

    // Create filename

    let tempDir     = NSTemporaryDirectory()
    let pdfFilename = tempDir.stringByAppendingPathComponent(filename)

    UIGraphicsBeginPDFContextToFile(pdfFilename, CGRectZero, nil)

    // begin to render the views in this context

    parseViewsToRender(viewsToRender)

    UIGraphicsEndPDFContext()

    return NSURL(string: pdfFilename)!

    }

}

首先,我创建了一个带有UIView的xib文件,该文件适合单个PDF页面的尺寸和我的标题信息。这个尺码是612点宽,高790点。

然后我为我想要使用的所有页面1标题信息添加了UILabel(名称,地址,日期等)

我记下了标题信息中最低UILabel的y位置和高度,并从页面中的垂直空间量中减去了它。

我还注意到了我想要使用的字体和字体大小。

然后我创建了一个名为CreatePDF

的类

在该类中,我创建了几个变量和常量,字体名称,字体大小,页面大小,标题信息后的剩余垂直空间。

在那个类中我创建了一个方法,它接受两个不同的参数,一个是我用于头信息的字典,另一个是UIImages和字符串的数组。

该方法调用其他一些方法:

  1. 确定数组中项目所需的垂直高度
  2. 为此,我创建了另外两个方法,一个用于确定具有任何给定字符串的UILabel的高度,另一个用于确定图像的高度(垂直和水平图像具有与我缩放它们不同的高度)。他们每个都返回了一个CGFloat,我将其添加到方法中的变量中,该变量跟踪数组中的所有项目。

    对于每个“大小”的项目,我再添加8个点作为垂直偏移。

    1. 确定需要多少页面
    2. 上面的方法返回了一个CGFloat,然后我用它来判断所有项目是否适合标题下面的一个页面,或者是否需要另一个页面,如果需要,还有多少页面。

      1. 画一幅UIView
      2. 此方法接受上述字典,数组和估计的页数。它返回一个UIViews数组。

        在这个方法中,我创建了一个匹配一个PDF页面大小的UIView,我为每个页面运行一个循环并向其添加项目,我通过比较它的y位置和高度来检查项目是否合适。通过从页面高度减去当前Y位置来扩大页面上的垂直空间然后我添加一个项目并跟踪它的高度和y位置,如果剩余高度不起作用并且我没有页面,我添加另一个页。

        1. 发送数组以绘制PDF
        2. 我在这里创建PDF上下文

          我将UIViews数组作为参数,对于每个视图,我在PDF上下文中创建PDF页面,然后遍历它的子视图,如果它是UILabel我将其发送到在其框架上绘制UILabel的函数使用它的text属性作为字符串。我使用前面类中定义的变量创建了一个属性前端。如果它是一个图像我将它发送到另一个也使用它的帧的函数,但是我必须将它发送到另一个函数来确定在UIImage中绘制的图像的实际尺寸(它根据缩放而改变)并且我返回在哪里绘制图像(这也发生在正确的大小上)。

          就是这样,在我的情况下,我用文件创建了PDF上下文,然后最终将文件返回给调用此函数的人。对我来说最困难的部分是跟踪垂直定位。

          我将努力使代码更通用并在某处发布。