How to render a complex UIView into a PDF Context with high resolution?

后端 未结 1 815
北海茫月
北海茫月 2021-02-02 04:03

There are several questions on SO asking how to render a UIView into a PDF context, but they all use view.layer.renderInContext(pdfContext), which results in a 72 DPI image (and

相关标签:
1条回答
  • 2021-02-02 04:31

    In the end, I was able to take hints from several prior posts and put together a solution. I'm posting this since it took me a long time to get working, and I really hope to save someone else time and effort doing the same.

    This solution uses two basic techniques:

    1. Render the UIView into a scaled bitmap context to produce a large image
    2. Draw the image into a PDF Context which has been scaled down, so that the drawn image has a high resolution

    Build your view:

    let v = UIView()
    ... // then add subviews, constraints, etc
    

    Create the PDF Context:

    UIGraphicsBeginPDFContextToData(data, docRect, stats.headerDict) // zero == (612 by 792 points)
    
    defer { UIGraphicsEndPDFContext() }
    
    UIGraphicsBeginPDFPage();
    
    guard let pdfContext = UIGraphicsGetCurrentContext() else { return nil }
    
    // I tried 300.0/72.0 but was not happy with the results
    let rescale: CGFloat = 4 // 288 DPI rendering of VIew
    
    // You need to change the scale factor on all subviews, not just the top view!
    // This is a vital step, and there may be other types of views that need to be excluded
    

    Then create a large bitmap of the image with an expanded scale:

    func scaler(v: UIView) {
       if !v.isKindOfClass(UIStackView.self) {
          v.contentScaleFactor = 8
       }
       for sv in v.subviews {
          scaler(sv)
       }
    }
    scaler(v)
    
    // Create a large Image by rendering the scaled view
    let bigSize = CGSize(width: v.frame.size.width*rescale, height: v.frame.size.height*rescale)
    UIGraphicsBeginImageContextWithOptions(bigSize, true, 1)
    let context = UIGraphicsGetCurrentContext()!
    
    CGContextSetFillColorWithColor(context, UIColor.whiteColor().CGColor)
    CGContextFillRect(context, CGRect(origin: CGPoint(x: 0, y: 0), size: bigSize))
    
    // Must increase the transform scale
    CGContextScaleCTM(context, rescale, rescale)
    
    v.layer.renderInContext(context)
    
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    

    Now we have a large image with each point representing one pixel. To get it drawn into the PDF at high resolution, we need to scale the PDF down while drawing the image at its large size:

    CGContextSaveGState(pdfContext)
    CGContextTranslateCTM(pdfContext, v.frame.origin.x, v.frame.origin.y) // where the view should be shown
    
    CGContextScaleCTM(pdfContext, 1/rescale, 1/rescale)
    
    let frame = CGRect(origin: CGPoint(x: 0, y: 0), size: bigSize)
    image.drawInRect(frame)
    
    CGContextRestoreGState(pdfContext)
    
    ... // Continue with adding other items
    

    You can see that the left "S" contained in the cream colored bitmap looks pretty nice compared to a "S" drawn but an attributed string:

    When the same PDF is viewed by a simple rendering of the PDF without all the scaling, this is what you would see:

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