
简介这份资源面向 iOS 开发者在应用中集成 PDF 电子签章能力的场景提供了一套体积较小的原生加载与签章库适合需要快速在合同、协议等 PDF 文档上完成签名展示与交互的中级开发者使用。压缩包共 7 个文件以 4 个 .a 静态库、2 个 .h 头文件和 1 个 .mm 实现文件为主静态库承载签章核心逻辑头文件暴露对外接口实现文件负责控制器层封装整体约 55.69MB接入成本较低。已有 644 人学习关注说明该方案在实际项目中有一定参考价值。资源核心是 TrustSignPDFDSController 控制器通过传入文件路径与文件名即可初始化并 push 进入签章页面调用方式简洁直观读者可据此快速完成 PDF 加载、签章展示与页面跳转的集成并参考头文件接口理解签章流程与扩展点减少从零搭建 PDF 签章模块的试错成本。1. 从一张盖不上章的合同说起iOS PDF 电子签章能解决什么上周同事发来一份采购合同 PDF让我在手机上签个字回传。我打开文件翻了半天发现 iOS 自带的标记工具只能手写涂鸦签完名位置歪了、缩放糊了对方还要求“章要盖在指定位置、签名不能压住正文”。这种场景其实很常见合同、报价单、授权书、验收单业务方要的不是一张截图而是一份打开后签名和印章都规规矩矩落在页面上的 PDF。iOS PDF 电子签章这个项目做的就是这件事——在 iPhone 和 iPad 上打开 PDF选好签名或印章拖到指定坐标合成后导出一份可继续流转的 PDF 文件。它适合两类人一类是手里有现成 PDF 签署流程、想补上移动端能力的开发者另一类是想搞清楚 PDF 页面坐标、图像叠加、文件重写这套链路怎么落地的人。下面按“能干什么、怎么跑、怎么改”的顺序拆开讲。2. 技术选型与工程骨架为什么是 PDFKit 加 Core Graphics2.1 三个候选方案的实际取舍在 iOS 上处理 PDF常见路线有三条用 PDFKit 做展示和页面操作用 Core Graphics 自己画上下文或者引入第三方 PDF 渲染库。这个项目选的是 PDFKit 负责“看”和“定位”Core Graphics 负责“画”和“写”。原因很直接PDFKit 的PDFView自带缩放、翻页、页面坐标转换省掉大量手势和坐标换算代码而真正把签名图像压到页面上、再导出新文件PDFKit 的写能力偏弱交给UIGraphicsPDFRenderer或CGPDFContext更稳。第三方库不是不能用但电子签章这类需求往往要控制包体积、要过审、要长期维护多一个渲染引擎就多一份不确定性。我一般会优先用系统框架把主链路跑通等确实遇到复杂表单域填充再考虑引入依赖。2.2 目录结构长什么样项目按职责拆成四层目录大致如下PDFSignDemo/ ├── App/ │ ├── AppDelegate.swift │ └── SceneDelegate.swift ├── Features/ │ ├── DocumentList/ # 文档列表与导入 │ ├── PDFViewer/ # PDF 预览、缩放、页码控制 │ └── Signature/ # 签名采集、印章选择、位置调整 ├── Core/ │ ├── PDFComposer.swift # 页面合成与导出 │ ├── CoordinateMapper.swift # 视图坐标到 PDF 坐标转换 │ └── FileStore.swift # 本地文件读写与临时目录管理 ├── Resources/ │ ├── Stamps/ # 预置印章图片 │ └── Assets.xcassets └── Tests/ └── PDFComposerTests.swiftFeatures放界面和交互Core放不依赖 UIKit 的合成逻辑Resources放印章素材。这样拆的好处是合成逻辑可以单独写单元测试不用起模拟器点半天。CoordinateMapper单独成文件是因为坐标转换是这个项目最容易翻车的地方后面会专门讲。2.3 关键依赖与最低版本项目只依赖系统框架PDFKit、UIKit、CoreGraphics、UniformTypeIdentifiers。没有 CocoaPods 或 SPM 外部依赖克隆下来直接打开 Xcode 工程就能编译。部署目标建议 iOS 14 及以上因为UIGraphicsPDFRenderer和 PDFKit 的页面缩略图接口在这个版本段比较稳定。如果业务要覆盖更老的系统PDFDocument的写页面接口需要做兼容分支工作量会明显上升这一点在立项时就要评估。2.4 跑起来的最小步骤拿到工程后按下面顺序操作即可看到效果# 1. 克隆或解压工程 cd PDFSignDemo # 2. 用 Xcode 打开工程文件 open PDFSignDemo.xcodeproj # 3. 选择模拟器或真机直接 Run # 首次运行会请求相册权限用于导入签名图片启动后首页是文档列表点右上角“导入”从“文件”App 选一份 PDF进入预览页后点“签名”底部弹出签名面板画完签名点“放置”拖动签名框到目标位置最后点“导出”保存到“文件”App。整条链路不依赖服务端适合先本地验证再接入业务系统。提示模拟器上画签名用鼠标拖动即可但导出后的文件建议在 Mac 上用预览打开核对位置模拟器截图容易让人误判偏移。3. 核心链路拆解从点下“放置”到导出新 PDF3.1 签名采集把手指轨迹变成透明底图片签名面板用UIView子类接管touchesMoved把触点连成UIBezierPath最后渲染成UIImage。关键点是背景必须透明否则贴到 PDF 上会盖住正文。final class SignaturePadView: UIView { private var path UIBezierPath() private var strokes: [UIBezierPath] [] override func touchesBegan(_ touches: SetUITouch, with event: UIEvent?) { guard let point touches.first?.location(in: self) else { return } path UIBezierPath() path.lineWidth 2.5 path.lineCapStyle .round path.move(to: point) strokes.append(path) } override func touchesMoved(_ touches: SetUITouch, with event: UIEvent?) { guard let point touches.first?.location(in: self) else { return } path.addLine(to: point) setNeedsDisplay() } // 导出透明底签名图 func exportImage(scale: CGFloat 2.0) - UIImage { let size bounds.size let renderer UIGraphicsImageRenderer(size: size) return renderer.image { ctx in UIColor.clear.setFill() ctx.fill(CGRect(origin: .zero, size: size)) UIColor.black.setStroke() strokes.forEach { $0.stroke() } } } }lineWidth控制笔迹粗细lineCapStyle设为圆头避免折线感。exportImage里先填透明色再描边保证导出的 PNG 只有笔迹有像素。scale参数用于提高分辨率贴到大尺寸 PDF 页面上不会发虚。实际项目里还会加“撤销上一笔”和“清空”实现方式就是操作strokes数组后重绘。3.2 坐标转换视图坐标和 PDF 坐标不是一回事这是整个项目最容易被低估的部分。PDFView里看到的页面经过缩放和平移手指落点拿到的是视图坐标而 PDF 页面有自己的坐标系原点在左下角单位是点point且受页面旋转影响。直接把视图坐标当 PDF 坐标用导出的章一定偏。struct CoordinateMapper { /// 将 PDFView 中的触点转换为目标页面的 PDF 坐标 static func convert(viewPoint: CGPoint, in pdfView: PDFView, page: PDFPage) - CGPoint { // 先转到页面视图坐标 let pagePoint pdfView.convert(viewPoint, to: page) // PDFPage 的 bounds 可能带旋转用 mediaBox 做归一 let mediaBox page.bounds(for: .mediaBox) let x pagePoint.x let y mediaBox.height - pagePoint.y return CGPoint(x: x, y: y) } }pdfView.convert(_:to:)负责把屏幕点映射到页面坐标这一步已经处理了缩放。随后用mediaBox.height做 Y 轴翻转因为 UIKit 原点在左上、PDF 原点在左下。如果页面带/Rotate属性还要根据旋转角度再做一次变换项目里在PDFComposer中按page.rotation分支处理。参数上mediaBox是页面实际内容区域cropBox是裁剪区域电子签章一般以mediaBox为准除非业务明确要求按裁剪框定位。3.3 页面合成把签名画进新 PDF合成逻辑放在PDFComposer输入原 PDF、页码、签名图、目标矩形输出新文件。核心是用UIGraphicsPDFRenderer逐页重绘。final class PDFComposer { func compose(source: PDFDocument, pageIndex: Int, stamp: UIImage, rect: CGRect, outputURL: URL) throws { let page source.page(at: pageIndex)! let bounds page.bounds(for: .mediaBox) let renderer UIGraphicsPDFRenderer(bounds: bounds) try renderer.writePDF(to: outputURL) { ctx in for index in 0..source.pageCount { let current source.page(at: index)! ctx.beginPage(withBounds: current.bounds(for: .mediaBox), pageInfo: [:]) // 先画原页面内容 ctx.cgContext.translateBy(x: 0, y: current.bounds(for: .mediaBox).height) ctx.cgContext.scaleBy(x: 1, y: -1) current.draw(with: .mediaBox, to: ctx.cgContext) // 只在目标页叠加签名 if index pageIndex { stamp.draw(in: rect) } } } } }beginPage每页开一个新上下文translateBy加scaleBy把坐标系翻回 PDF 习惯再调page.draw画原内容。签名用stamp.draw(in:)叠加rect就是上一步转换后的目标矩形。注意rect的宽高要按签名图比例算否则章会被拉变形。导出路径建议放临时目录成功后由FileStore移到文档目录避免半成品文件被用户看到。3.4 文件落盘与分享导出完成后用UIActivityViewController调起系统分享或者通过UIDocumentPickerViewController存到“文件”App。FileStore里统一管理临时文件清理防止多次导出把沙盒撑大。分享前建议校验文件大小和页数确认合成没有丢页。4. 避坑与排查五个真实踩过的坑4.1 导出的章位置整体偏移现象是预览里拖到指定位置导出后章偏上或偏左。原因多半是坐标转换时用了cropBox而合成时按mediaBox绘制两个框不一致。解决方式是转换和绘制统一用同一个 box项目里统一走mediaBox并在CoordinateMapper里加断言校验。4.2 签名图边缘发黑或带白底现象是贴上去的签名周围有一圈黑边或白底。原因是签名视图背景没设透明或者导出时用了UIGraphicsBeginImageContext默认不透明上下文。解决方式是改用UIGraphicsImageRenderer并显式填UIColor.clear同时确认签名图是 PNG 而非 JPEG。4.3 大文件导出卡顿甚至内存告警现象是几十页的 PDF 导出时界面卡住Xcode 报内存警告。原因是逐页重绘时把整份文档的页面都加载进内存。解决方式是分页处理、及时释放PDFPage引用导出放到后台队列主线程只更新进度。超过百页的文档建议做分批写盘。4.4 页面旋转后章的方向不对现象是横向扫描件上盖的章是竖着的。原因是页面带/Rotate 90而合成时没处理旋转。解决方式是在PDFComposer里读取page.rotation对签名图先做对应角度的旋转再绘制或者先把页面旋转归一化再叠加。4.5 真机上导入 PDF 失败现象是模拟器正常真机点导入没反应。原因通常是权限描述缺失或UTType过滤写错。解决方式是在Info.plist补全文件访问相关描述UIDocumentPickerViewController的forOpeningContentTypes用.pdf而不是宽泛的.data。5. 二次开发把单机 Demo 改成业务可用的签署模块5.1 先定扩展点再动界面这个项目最值得改的地方不是 UI而是PDFComposer和CoordinateMapper这两个文件。我的习惯是先把它们抽成独立模块对外只暴露“输入文档、页码、图、矩形输出文件”的接口界面层不直接碰 Core Graphics。这样后面接业务系统时无论是换成服务端下发印章、还是增加多页批量盖章都只改模块内部。5.2 三个常见的改造方向改造方向改动位置注意点多页批量盖章PDFComposer.compose增加页索引数组每页矩形独立计算别复用同一坐标印章来源改为服务端Signature模块增加下载与缓存校验图片格式和尺寸防止拉伸签署记录留痕FileStore旁增加元数据写入记录页码、坐标、时间便于审计5.3 验证方法别只看预览改完合成逻辑后我一般会做三步验证第一步用固定坐标生成一份测试 PDF在 Mac 预览里量像素位置第二步用带旋转的扫描件跑一遍确认章方向正确第三步用 50 页以上的文档压测导出耗时和内存。单元测试里对CoordinateMapper做边界用例比如页面宽高为 0、触点在页面外的情况能提前拦住大部分低级错误。5.4 一个具体技巧把签名框做成可复用的覆盖层很多人会把签名直接画在PDFView上结果一缩放就错位。更稳的做法是加一个透明覆盖层UIView签名框作为子视图跟着页面视图走导出时只取覆盖层的相对位置再转换。这样缩放、翻页都不影响已放置的签名用户还能继续拖动微调。覆盖层只负责展示不参与最终合成职责清晰排查问题也快。5.5 收个尾这个项目不大但把 PDF 页面坐标、图像叠加、文件重写这条链路完整走了一遍拿来当移动端签署模块的起点是够用的。我早期做类似需求时吃过亏预览里看着对导出后章偏了两厘米返工查了一下午才发现是cropBox和mediaBox混用。从那以后我每次改坐标相关代码都强制用一份带旋转的测试 PDF 跑一遍再提交。希望这份拆解能帮你少走一段弯路把精力花在业务逻辑上。本文还有配套的精品资源点击获取