Create PDF of dynamic size with typography using UIView template(s)

前端 未结 2 1583
花落未央
花落未央 2021-02-11 05:18

I\'m new but have managed to learn a lot and create a pretty awesome (I hope) app that\'s near completion. One of my last tasks is to create a PDF of dynamically generated user

相关标签:
2条回答
  • 2021-02-11 05:56

    So here we go. The following was made for OSX with NSView but it's easily adapatable for UIView (so I guess). You will need the following scaffold:

    A) PSPrintView will handle a single page to print

    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: will hold the single PSPrintViews in an array and when done send them to the (PDF) printer

    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) a function to perform printing (from the menu)

    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'.
    }
    
    0 讨论(0)
  • 2021-02-11 06:08

    The PDF drawing code:

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

    First, I made a xib file with a UIView that fit the dimensions of a single PDF page and for my header information. This size is 612 points wide by 790 points tall.

    Then I added UILabels for all of the page 1 header information I want to use (name, address, date, etc.)

    I took note of y position and height of the lowest UILabel for my header information and subtracted it from the amount of vertical space in a page.

    I also took note of the font and font size I wanted to use.

    Then I created a class called CreatePDF

    In that class I created several variables and constants, the font name, the font size, the size of a page, the remaining vertical space after header information.

    In that class I created a method that takes two different arguments, one is a dictionary that I used for header information, the other is an array of UIImages and Strings.

    That method calls a few other methods:

    1. Determine the vertical height required for the items in the array

    To do this I created another two methods, one to determine the height of a UILabel with any given string and one to determine the height of an image (vertical and horizontal images having different heights the way that I scale them). They each returned a CGFloat, which I added to a variable in the method that kept track of all the items in array.

    For each item that was “sized” I then added another 8 points to use as a vertical offset.

    1. Determine how many pages will be needed

    The above method returned a CGFloat that I then used to figure out if either all the items will fit on one page below the header or if another page will be needed, and if so, how many more pages.

    1. Draw a UIView

    This method accepts the above mentioned dictionary, array and an estimated number of pages. It returns an array of UIViews.

    In this method I create a UIView that matches the size of one PDF Page, I run a loop for each page and add items to it, I check to see if an item will fit by comparing it’s y position and height with the reaming vertical space on a page by subtracting the current Y position from the page height then I add an item and keep track of it’s height and y position, If the remaining height won’t work and I’m out of pages, I add another page.

    1. Send the array to draw a PDF

    I create the PDF context here

    I take the array of UIViews as an argument, for each view I create a PDF Page in the PDF Context and then iterate through it’s subviews, if it’s a UILabel I send it off to a function that draws the UILabel at it’s frame position with it’s text property as the string. I create an attributed front using the variables defined in the class earlier. If it’s an image I send it to another function that also uses it’s frame, however I have to send it to yet another function to determine the actual dimensions of the image that’s drawn inside the UIImage (it changes based on scaling) and I return that for where to draw the image (this happens above too to properly size it).

    That’s pretty much it, in my case I created the PDF context with a file, then end up returning the file to whoever calls this function. The hardest part for me to wrap my head around was keeping track of the vertical positioning.

    I’ll work on making the code more generic and post it up somewhere.

    0 讨论(0)
提交回复
热议问题