最近自己做个小项目,需要把数据绘制成PDF打印出来。

在研究PDF绘制的过程中,发现国内在这方面很难搜到优质有深度的内容,尤其是超长字符串分页绘制,基本没找到解决方案(我使用的原生方法绘制,所以不考虑使用HTML的情况)。无奈去google,在stackover上找到一篇帖子,根据它的思路,终于实现了基础功能。

使用PDFKit,绘制部分基本都是CoreGraphics,多行文字分页绘制使用了CoreText。

具体代码如下:

这是数据模型,主要记录每天早中晚饭,几点,吃的什么。

/// 组数据模型
class HomeSectionModel {// 日期var day: String = ""// 单行数据模型 数组var rowModels: [HomeRowModel] = []
}/// 单行数据模型
class HomeRowModel {// 数据IDvar objectID: String = ""// 饮食类型var type: String = ""// 日期var date: String = ""// 食物var food: String = ""
}

下面为绘制PDF的代码,我是一页PDF左右两列绘制的。

// 需要import的库
import UIKit
import PDFKit
import CoreGraphics// PrintViewController中关于PDF的方法/// PDF预览视图private lazy var pdfView: PDFView = {let pdfView = PDFView()return pdfView}()/// 生成PDF,预览/// - Parameters:///   - models: 数据模型数组///   - fromDate: 开始日期///   - toDate: 结束日期private func createPDFAndShow(models: [HomeSectionModel], fromDate: String, toDate: String) {// PDF单页宽度let pageWidth: CGFloat = 8.5 * 72.0// PDF单页一半的宽度let halfPageWidth: CGFloat = pageWidth / 2.0// PDF单页高度let pageHeight: CGFloat = 11 * 72.0// PDF Rectlet pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)// 开始绘制PDFlet renderer = UIGraphicsPDFRenderer(bounds: pageRect)let data = renderer.pdfData { (context) in// 开启新的单页context.beginPage()// 绘制属性let dateAttr = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 17),NSAttributedString.Key.foregroundColor: UIColor.systemRed]let typeAttr = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 15),NSAttributedString.Key.foregroundColor: greenColor]// 支持多行let paragraphStyle = NSMutableParagraphStyle()paragraphStyle.alignment = .naturalparagraphStyle.lineBreakMode = .byWordWrappinglet foodAttr = [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 13),NSAttributedString.Key.foregroundColor: UIColor.darkGray,NSAttributedString.Key.paragraphStyle: paragraphStyle]// 记录当前X轴坐标var tempX: CGFloat = 20// 记录当前Y轴坐标var tempY: CGFloat = 30// 日期间距let dayMargin: CGFloat = 30// 通用间距let margin: CGFloat = 20// 顶部间距let topMargin: CGFloat = 40// 在日期范围内var isInRange: Bool = false// 大循环,绘制包含全部内容for model in models.reversed() { // 当前数据是根据日期排序的// 判断日期是否在范围内if model.day == fromDateStr { // 到开始日期,开始进入日期范围内isInRange = true}if !isInRange { // 没在范围,跳过本次循环continue}if model.day == toDateStr { // 到结束日期,离开日期范围isInRange = false}// 刚进入绘制时,tempY初始化为30,+10 = topMargin。循环绘制时,此为跟之前绘制内容的间距tempY += 10// 判断当前是否到页面底了,如果到底了,再判断是在右边继续绘制还是需要重新创建新的单页if isPageFull(targetY: tempY + dayMargin, pageHeight: pageHeight) {if tempX < halfPageWidth {// 在右面继续绘制tempX = halfPageWidth + margintempY = topMargin} else {// 创建新的页面context.beginPage()tempX = margintempY = topMargin}}// 开始绘制日期model.day.draw(at: CGPoint(x: tempX, y: tempY), withAttributes: dateAttr)// 加间距tempY += dayMargin// 小循环,绘制饮食类型、时间和食物for rowModel in model.rowModels.reversed() {// 判断当前是否到页面底了,如果到底了,再判断是在右边继续绘制还是需要重新创建新的单页if isPageFull(targetY: tempY + margin, pageHeight: pageHeight) {if tempX < halfPageWidth {// 在右面继续绘制tempX = halfPageWidth + margintempY = topMargin} else {// 创建新的页面context.beginPage()tempX = margintempY = topMargin}}// 开始绘制饮食类型rowModel.type.draw(at: CGPoint(x: tempX, y: tempY), withAttributes: typeAttr)// 绘制时间String(rowModel.date.suffix(5)).draw(at: CGPoint(x: tempX + 40, y: tempY), withAttributes: typeAttr)// 加间距tempY += margin// 绘制食物 CoreText 支持超长字符串换行分页绘制let foodText = CFAttributedStringCreate(nil, rowModel.food as CFString, foodAttr as CFDictionary)let framesetter = CTFramesetterCreateWithAttributedString(foodText!)// 记录当前字符串绘制到哪个locationvar currentRange = CFRangeMake(0, 0)// 记录食物字符串是否绘制完成,true时,repeat循环结束var isFoodFinish = false// 循环绘制食物字符串。因为涉及到单页左右两列切换,和开启新的页面继续绘制,每次切换都要走一次循环repeat {// 获取上下文let currentContext = UIGraphicsGetCurrentContext()// 保存上下文。当前为UIKit的坐标系。// 原因:// 1.CoreText坐标系和UIKit不一样,下面代码转换坐标系后,结束时恢复上下文,不会影响到上面上下文、坐标系的状态。// 2.同样是CoreText坐标系问题,保存、恢复上下文,使每一次循环绘制食物字符串时,互相不影响。currentContext?.saveGState()// 计算单行文字高度,确保当前页剩余绘制高度至少可以绘制一行,如果不能,判断是去右边绘制还是新开一页let foodSingleLineHeight: CGFloat = String(rowModel.food.prefix(1)).height(font: UIFont.boldSystemFont(ofSize: 13), width: CGFloat(halfPageWidth - margin * 2))// 判断当前是否到页面底了,如果到底了,再判断是在右边继续绘制还是需要重新创建新的单页if isPageFull(targetY: tempY + foodSingleLineHeight + 10.0, pageHeight: pageHeight) {if tempX < halfPageWidth {// 在右面继续绘制tempX = halfPageWidth + margintempY = topMargin} else {// 创建新的页面context.beginPage()tempX = margintempY = topMargin}}// 由于CoreText和UIKit坐标系不一样,一个圆点在左下角,向上、右延伸,一个圆点在左上角,向下、右延伸。因此需要翻转、平移坐标// 坐标系平移。y值平移越大,翻转后它在视图上的位置越往下。由于圆点一个在上,一个在下,因此最少需要平移pageHeight的距离。再考虑当前绘制到的点位,所以要加上当前的tempY。而tempY和pageHeight中都包含了一个topMargin,因此需要减去重复的一个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 textlet frameRect = CGRect(x: tempX, y: tempY, width: halfPageWidth - 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)// 获取当前绘制文字的可见范围,通过它获取到当前绘制的文字高度,用以计算tempYlet currentRect = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, currentRange, nil, .init(width: halfPageWidth - margin * 2, height: pageHeight - topMargin - tempY), nil)currentRange.location += currentRange.lengthcurrentRange.length = CFIndex(0)tempY += currentRect.height + 10.0// 位置已到字符串结尾,结束绘制if currentRange.location == CFAttributedStringGetLength(foodText) {isFoodFinish = true}// 恢复上下文currentContext?.restoreGState()} while !isFoodFinish}}}// 赋值给预览视图pdfView.document = PDFDocument(data: data)pdfView.autoScales = truepdfData = data}/// 判断当前页面是否已写满/// - Parameters:///   - targetY: 将要写入的内容的最终Y值///   - pageHeight: 单页高度/// - Returns: 是否写满private func isPageFull(targetY: CGFloat, pageHeight: CGFloat) -> Bool {return targetY >= pageHeight - 40}// MARK: - String Extension 计算文字高度
extension String {public func height(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 NSStringlet rect = text.boundingRect(with: size, options:.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font : font], context:nil)return rect.size.height}
}

效果如下:

最后用了系统的分享,把PDF保存或者通过微信、QQ等方式发出。

    /// 分享按钮点击事件/// - Parameter sender: 按钮@IBAction func shareButtonDidClick(_ sender: UIButton) {guard let data = pdfData else {HUD.shared.show(text: "PDF数据异常", on: self.view, hideAfter: 2.0, completion: nil)return}// PDF本地保存地址let localCachePath = NSHomeDirectory() + "/Library/Caches/food.pdf"let localCacheURL = URL(fileURLWithPath: localCachePath)do {// 保存到本地try data.write(to: localCacheURL)// 系统分享控件 同时分享data和本地文件地址,才能出来微信和qq的分享let shareVC = UIActivityViewController(activityItems: [data, localCacheURL], applicationActivities: nil)self.present(shareVC, animated: true, completion: nil)} catch {print(error)}}

iOS Swift 绘制PDF,超长字符串分页绘制相关推荐

  1. iOS Swift JSON转JSONString字符串

    本人亲测有效!更多交流可以家魏鑫:lixiaowu1129,公重好:iOS过审汇总,一起探讨iOS技术! do{ let data = try? JSONEncoder().encode(jsonPr ...

  2. matlab yticklable,Matlab绘制XTickLabel有效字符串(Matlab Plot XTickLabel valid strings)

    Matlab绘制XTickLabel有效字符串(Matlab Plot XTickLabel valid strings) 我有一个情节: x = [0 1 2 3]; y = [0 0 1 1]; ...

  3. java 绘制pdf_Java 在PDF文档中绘制图形

    本篇文档将介绍通过Java编程在PDF文档中绘制图形的方法.包括绘制矩形.椭圆形.不规则多边形.线条.弧线.曲线.扇形等等.针对方法中提供的思路,也可以自行变换图形设计思路,如菱形.梯形或者组合图形等 ...

  4. iOS开发UI篇—Quartz2D使用(绘制基本图形)

    一.简单说明 图形上下文(Graphics Context):是一个CGContextRef类型的数据 图形上下文的作用:保存绘图信息.绘图状态 决定绘制的输出目标(绘制到什么地方去?)(输出目标可以 ...

  5. Python绘制PDF文件~超简单的小程序

    Python绘制PDF文件 项目简介 这次项目很简单,本次项目课,代码不超过40行,主要是使用 urllib和reportlab模块,来生成一个pdf文件. reportlab官方文档 http:// ...

  6. 基于matlab的能级_波函数及几率密度图形的绘制,基于MATLAB的能级波函数及几率密度图形的绘制.pdf...

    基于MATLAB的能级波函数及几率密度图形的绘制.pdf 第 卷 第 期 大 学 物 理 实 验 年 月 出版 壬 刃协 卫 印 文 章编 号 一 ' 一 一 基 于 的能级 . 波 函数 及 几 率 ...

  7. C# 绘制PDF图形——基本图形、自定义图形、色彩透明度

    引言 在PDF中我们可以通过C#程序代码来添加非常丰富的元素来呈现我们想要表达的内容,如绘制表格.文字,添加图形.图像等等.在本篇文章中,我将介绍如何在PDF中绘制图形,并设置图形属性的操作. 文章中 ...

  8. 使用IText组件在PDF文档上绘制椭圆形印章的算法分析及代码分享

    1. 引言 PDF是一种和操作系统及平台无关的.可移植的电子文件格式,其以PostScript语言图像模型为基础,无论在哪种打印机上,都可保证精确的颜色和准确的打印效果.PDF将真实地再现原稿的每一个 ...

  9. iOS:quartz2D绘图(给图形绘制阴影)

    quartz2D既可以绘制原始图形,也可以给原始图形绘制阴影. 绘制阴影时,需要的一些参数:上下文.阴影偏移量.阴影模糊系数 注意:在drawRect:方法中同时调用绘制同一个图形时,在对绘制的图形做 ...

  10. iOS 中饼状图的自定义绘制

    前几天有一个需求是制作一个统计工资的饼状图,但是和一般的饼状图不同的是要求该饼状图中心需要有两条文字,功能需求就是这样,先上一张效果图: 因为咱们的饼状图本身只是一个View ,在调用的时候一定是在一 ...

最新文章

  1. 小坑记录:get_cmap参数区分大小写
  2. python零基础电子书免费下载-零基础学Python
  3. python的GUI库PyQt5的使用
  4. 影院平台搭建 - (2)FLV发布系统的简单搭建
  5. vs2010 将.mc编译为.rc文件
  6. 012_SpringBoot视图层技术thymeleaf-条件判断
  7. 何时查询2021高考成绩长春市,2020年吉林长春成人高考成绩查询入口(已开通)...
  8. K8S的 Custom Resource Definition(CRD)之初体验
  9. javascript 西瓜一期 11 二进制的数数进位解析
  10. 【云快讯】之五十五《IBM推出Data Warehouse数据仓库云服务》
  11. CANFD MCP2517FD 滤波ID设置例子
  12. 汽车汽配行业供应链协同管理系统一体化管理,SCM供应链提升企业竞争力
  13. uniapp 小程序生成二维码 (兼容H5、微信小程序)
  14. redis通过key模糊搜索_Redis中关于Key的模糊查找
  15. gimp:图层的混合模式
  16. 锐捷路由器如何配置虚拟服务器,锐捷路由器配置命令完美宝典
  17. 08_Linux系统之link(),symlink(),readlink()函数
  18. CommandLineRunner
  19. Springboot企业出纳系统的设计与实现7k9je计算机毕业设计-课程设计-期末作业-毕设程序代做
  20. 进程冻结(freezing of task)

热门文章

  1. Tapestry5.3使用总结
  2. Android MVVM开发框架
  3. 全国计算机信息处理技术员报名官网入口,信息处理技术员考试,就是这么简单!...
  4. 2022年计算机软件水平考试信息处理技术员练习题及答案
  5. Java集合类源码详解
  6. Allegro cadence下载安装
  7. 学习python的语法规则
  8. 安卓系统抓包工具大全
  9. 2022最新软件库iApp源码+简约唯美/对接hybbs
  10. modelsim 下载链接