import UIKit
import PDFKit
import CoreGraphics
var pdfData : Data?
private func createPDF(texts: [String]) -> Data{
// PDF单页宽度
let pageWidth: CGFloat = 8.5 * 72.0
// PDF单页高度
let pageHeight: CGFloat = 11 * 72.0
// PDF Rect
let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
// 开始绘制PDF
let renderer = UIGraphicsPDFRenderer(bounds: pageRect)
let data = renderer.pdfData { (context) in
// 开启新的单页
context.beginPage()
// 支持多行
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .natural
paragraphStyle.lineBreakMode = .byWordWrapping
let dateAttr = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 17),
NSAttributedString.Key.foregroundColor: UIColor.darkGray,
NSAttributedString.Key.paragraphStyle: paragraphStyle]
// 记录当前X轴坐标
var tempX: CGFloat = 20
// 记录当前Y轴坐标
var tempY: CGFloat = 30
// 通用间距
let margin: CGFloat = 20
// 顶部间距
let topMargin: CGFloat = 40
// 大循环,绘制包含全部内容
for str in texts {
tempY += 10
// 判断当前是否到页面底了,如果到底了
if tempY > pageHeight - 40 {
// 创建新的页面
context.beginPage()
tempX = margin
tempY = topMargin
}
// 绘制CoreText 支持超长字符串换行分页绘制
let strText = CFAttributedStringCreate(nil, str as CFString, dateAttr as CFDictionary)
let framesetter = CTFramesetterCreateWithAttributedString(strText!)
// 记录当前字符串绘制到哪个location
var currentRange = CFRangeMake(0, 0)
// 记录字符串是否绘制完成,true时,repeat循环结束
var isFinish = false
repeat {
// 获取上下文
let currentContext = UIGraphicsGetCurrentContext()
// 保存上下文。当前为UIKit的坐标系。
// 原因:
// 1.CoreText坐标系和UIKit不一样,下面代码转换坐标系后,结束时恢复上下文,不会影响到上面上下文、坐标系的状态。
// 2.同样是CoreText坐标系问题,保存、恢复上下文,使每一次循环绘制食物字符串时,互相不影响。
currentContext?.saveGState()
// 计算单行文字高度
let strSingleLineHeight: CGFloat = String(str.prefix(1)).getHeight(font: UIFont.boldSystemFont(ofSize: 17), width: CGFloat(pageWidth - margin * 2))
// 判断当前是否到页面底了,如果到底了,需要重新创建新的单页
if tempY > pageHeight - 40 {
// 创建新的页面
context.beginPage()
tempX = margin
tempY = topMargin
}
currentContext?.translateBy(x: 0, y: pageHeight - topMargin + tempY)
// 坐标系翻转
currentContext?.scaleBy(x: 1.0, y: -1.0)
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
currentContext?.textMatrix = .identity
// Create a path object to enclose the text
let frameRect = CGRect(x: tempX, y: tempY, width: pageWidth - margin * 2, height: pageHeight - topMargin - tempY)
let framePath = CGMutablePath()
framePath.addRect(frameRect, transform: .identity)
// Get the frame that will do the rendering.
// The currentRange variable specifies only the starting point. The framesetter
// lays out as much text as will fit into the frame.
let frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, nil)
// Draw the frame.
CTFrameDraw(frameRef, currentContext!)
// Update the current range based on what was drawn.
currentRange = CTFrameGetVisibleStringRange(frameRef)
// 获取当前绘制文字的可见范围,通过它获取到当前绘制的文字高度,用以计算tempY
let currentRect = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, currentRange, nil, .init(width: pageWidth - margin * 2, height: pageHeight - topMargin - tempY), nil)
currentRange.location += currentRange.length
currentRange.length = CFIndex(0)
tempY += currentRect.height + 10.0
// 位置已到字符串结尾,结束绘制
if currentRange.location == CFAttributedStringGetLength(strText) {
isFinish = true
}
// 恢复上下文
currentContext?.restoreGState()
} while !isFinish
}
}
return data
}
// MARK: - String Extension 计算文字高度
extension String {
public func getHeight(font: UIFont, width: CGFloat) -> CGFloat {
guard self.count > 0 && width > 0 else {
return 0
}
let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let text = self as NSString
let rect = text.boundingRect(with: size, options:.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : font], context:nil)
return rect.size.height
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let pdfData = createPDF(texts: ["111这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行这是第一行","这是第二行"])
// PDF本地保存地址
let localCachePath = NSHomeDirectory() + "/Library/Caches/food.pdf"
let localCacheURL = URL(fileURLWithPath: localCachePath)
do {
try pdfData.write(to: localCacheURL)
let shareVC = UIActivityViewController(activityItems: [pdfData, localCacheURL], applicationActivities: nil)
self.present(shareVC, animated: true, completion: nil)
} catch {
print(error)
}
}
}
生成成功的位置