简介:面向iOS开发者的PDF电子签章场景,此资源提供了一款原生且占用体积较小的PDF签章库,用来解决在iPhone/iPad应用中快速加载PDF并完成电子盖章的实际需求,适合中高级移动开发者直接集成到商业项目中。库将PDF展示、页面交互与签章操作封装为统一控制器,使用方只需传入文件路径与文件名,再push目标控制器即可呈现完整签章界面,接入成本较低。压缩包含7个文件,其中4个静态库.a文件承载核心编译产物,2个.h头文件暴露对外接口,1个.mm实现文件展示控制器内部逻辑,便于开发者了解调用链并按需调整。整个资源包约55.69MB,便于下载与归档。已有639人学习浏览,这套轻量级方案比集成大型PDF框架更快捷,适合作为iOS电子签章模块的核心依赖或功能参考。 做了几年的 iOS 开发,第一次接到"PDF 电子签章"这个需求时,我其实是有点兴奋又有点慌的。兴奋在于这功能听起来很"企业级",慌在于——苹果官方框架里根本没有一个叫"电子签章"的模块,你得把 PDF 解析、渲染、坐标转换、图形绘制、文件导出这一整套链路自己串起来。后来做完整套功能再回头看,核心难点其实就三块:一是搞清楚 PDF 的坐标系到底怎么映射到手机屏幕上,二是签章内容怎么"写"进 PDF 里还能保证原文件结构不被破坏,三是和合规数字签名服务怎么对接。这篇就把我从零到上线整个过程中的方案选型、具体实现、踩坑记录都整理出来,给后面要做 iOS PDF 签章、签名、批注这类功能的朋友一个参考。
1. 需求梳理与方案选型
1.1 电子签章到底分几种
开始动手之前,先把"电子签章"这四个字拆开。实际业务里常见的形态有三种,很多人一开始容易混:
第一种是图形签章,也就是把签名图片、印章图片贴到 PDF 的指定位置,本质上是一次图片合成,改动的是 PDF 的内容流(Content Stream)。这种方案实现成本低,普通合同预览、内部审批场景够用,但法律效力有限。
第二种是数字签名(Digital Signature),它基于非对称加密,对 PDF 的字节内容做摘要、签名、封装,任何人改动文件后签名都会失效。这是 PDF 标准里真正的"防篡改"机制,用的是 PKCS#7 / CMS 格式,证书链校验、时间戳都在这套体系里。
第三种是平台型电子签章服务,比如对接金格、法大大、e签宝这类第三方。这种通常是服务端完成证书签发、签章、存证全流程,iOS 端往往只负责展示和确认。
我这次做的项目需求是:用户能在 iOS App 里打开一份 PDF 合同,手写签名或选择公章图片,拖放到指定位置,生成带防篡改能力的新 PDF,再通过系统原生分享发出去。所以实际落地是第一种和第二种的结合体:图形层面我们自己做,数字签名能力走服务端合规通道。
1.2 技术方案对比与选型逻辑
iOS 生态里做 PDF 签章,可选的路子看着多,其实绕来绕去就这么几条:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 纯 PDFKit + Core Graphics | 系统原生,无第三方依赖,包体积不受影响 | 数字签名要自己封装,复杂操作学习成本高 | 图形签章、批注、预览为主 |
| PDF 第三方库(PDFTron、PDFium 等) | 功能全,页面编辑/表单/签章都有现成API | 要钱或者要折腾编译,包体积暴涨 | 复杂 PDF 编辑类产品 |
| 原生 PDFKit + 服务端签名 | 移动端轻量,合规性强 | 需要做网络层和调签名服务的逻辑 | 企业合同、金融级场景 |
我最终选了"PDFKit + Core Graphics 绘制图片签章 + 服务端做数字签名"的组合。理由很直白:PDFKit 在 iOS 11 之后已经足够稳定,PDFView 和 PDFPage 把渲染和页面管理做得很完整,我们不需要为"打开 PDF"这个动作引入额外 SDK;而数字签名涉及证书私钥和算法,放在端上既容易泄露密钥又过不了合规审查,交给服务端是对的。
2. 核心细节拆解:PDF解析与坐标转换
2.1 PDF坐标系与UIKit坐标系到底差在哪
这个坑几乎每个做 PDF 功能的 iOS 开发者都会踩一次。PDF 的坐标原点在左下角,x 轴向右,y 轴向上,单位是点(Point);而 UIKit 的默认坐标系原点在左上角,y 轴向下。你直接把 UIKit 里的触摸点画到 PDF 上,位置一定是上下颠倒的。
所以我在实现里写了一个统一的坐标转换工具,核心思路是:
- 从 PDFPage 拿到
boundsForBox(.mediaBox),得到这一页原始尺寸; - 拿到 PDFView 的可见区域和缩放比例;
- 把用户点击的 view 坐标通过 PDFView 的
convert(_:to:)方法转成 PDF 页面坐标; - 绘制时在 CGContext 里做一次
concatenating变换,把 UIKit 坐标系翻转为 PDF 坐标系。
这里要特别提醒一句:PDFView 的转换方法比你自己拿 contentOffset 算要可靠得多。我一开始图省事,用 scrollView 的偏移量手工换算,结果在双指缩放后位置就偏了。后来换成pdfView.convert(point, from: pdfView)之后,缩放、翻页都没再出过偏差。
// 把屏幕上的点转换成 PDF 页面内的点 let pointInView = gesture.location(in: pdfView) let pointInPage = pdfView.convert(pointInView, to: currentPage)这一步是整个签章功能的地基,坐标转换做不对,后面所有定位都是空中楼阁。
2.2 页面渲染和签名位置的定位策略
PDF 页面渲染这块,PDFKit 做得已经很省心,核心就是 PDFView + PDFPage。你要做的是搞清楚这些问题:
- 多页 PDF 怎么管理:PDFDocument 负责页面集合,增删页、获取页码、锁定文档都走它;
- 每一页怎么画:拿到 PDFPage 后调用
page.draw(with:to:)就能渲染到指定 context; - 缩放后签章位置怎么不变:签章坐标一律以 PDF 页面坐标存储,展示时再转回 UIView 坐标,而不是存屏幕坐标。
实际业务里我还发现,签章位置的"锚点"不能只记一个点。用户把印章拖到某个位置,我们需要保存的是:页码、中心点坐标、缩放比例、旋转角度这四元组。因为同一份 PDF 可能在手机上看、在电脑上看、再打印出来,只有以页面大小为基准的相对位置才能保证跨设备一致。
2.3 手写签名采集的几个关键参数
签章里最常被忽略的是手写签名的采集质量。我用的是 UIBezierPath 记录触摸轨迹,再用 UIGraphicsImageRenderer 把它画成透明底图片。这里有几个参数直接影响最终效果:
- 笔画宽度:我取 2.5pt 到 3pt,太细打印出来看不清,太粗又像记号笔;
- 抗锯齿开关:绘制时把
isAntialiased设为 true,否则放大后边缘全是锯齿; - 图片分辨率:按 @3x 生成,也就是物理像素是逻辑点的三倍,保证在 PDF 高分打印时不会糊。
另外,签名板的背景一定要用纯白或纯透明,不要带任何阴影渐变。你永远不知道这份合同最后会被打印成什么规格,带花哨背景的签名图在打印预览里会非常突兀。
3. 签章写入与合成实现
3.1 用 Core Graphics 把签章画进 PDF
这一节是实操味道最浓的部分。把签章写进 PDF 有两条路:一条是用 PDFKit 的PDFPage添加 annotation,另一条是用 Core Graphics 重新生成一份 PDF。
Annotation 方式适合可交互的签章,用户点一下能选中、能拖动,你可以给它设置边框、颜色、弹出框。代码大致是这样:
let annotation = PDFAnnotation(bounds: stampRect, forType: .stamp, withProperties: nil) annotation.contents = "公司公章" annotation.fieldName = "stamp_001" annotation.mediaBox = currentPage.bounds(for: .mediaBox) currentPage.addAnnotation(annotation)Core Graphics 重绘方式则是把原来页面内容画进一个新的 context,然后在对应位置再绘制签章图片,最后输出新 PDF。这个方式的优势是完全自己控制渲染管线,适合需要把图片"烤"进页面里、不希望用户能轻易挪动的场景。
我实际项目里用的是后者:因为签章一旦落下,应该是合同内容的组成部分,而不该是一个可随意拖动的浮动层。重绘的伪代码逻辑是这样的:
UIGraphicsPDFRenderer(bounds: pageMediaBox) { ctx in // 第一页原内容 page.draw(with: .mediaBox, to: ctx.cgContext) // 在签章坐标绘制图片 ctx.cgContext.draw(stampImage, in: stampRectInPDF, byTiling: false) }注意绘制完一定要检查输出 PDF 的字节大小,我有一次发现某个大图签章把整个文件撑大了 10 倍,后来才意识到是没做压缩,直接嵌入了原图。
3.2 合规数字签名的接入方式
图形签章只是"看起来像盖章",真正的法律效力来自数字签名。这一步我的建议很明确:不要自己在端上实现签名算法,直接对接合规服务。现在国内做电子签章的服务商都有成熟的开放接口,流程一般是:
- App 端把待签文件的摘要(SHA-256)传给服务端;
- 服务端用企业证书私钥对摘要做签名;
- 服务端返回签名值和时间戳;
- 端上把签名封装进 PDF 的签名域,或者由服务端直接返回完整签名后的 PDF。
对接过程中最容易出问题的是证书提供方。很多人会遇到类似org.kg.bouncycastle.jce.provider.BouncyCastleProvider这样的依赖冲突,就是因为 PDF 签名库和服务端返回的 PKCS#7 数据用的底层加密库版本不一致。这种问题在 iOS 端的表现往往还特别隐蔽——服务端验签明明成功了,Adobe Reader 打开却提示"签名有效性未知"。
我的排查方法就一句话:先验字节流,再看证书链。把签名后的 PDF 用十六进制转储,确认签名对象是嵌在文件里的ByteRange区域,而不是加了外壳;然后用 OpenSSL 命令行单独验签一次,排除 App 端解析的问题。绝大多数情况都是证书链表不完整,缺了中间证书,把完整链一起嵌进去就解决了。
3.3 用系统原生分享把签名 PDF 发出去
签章完成之后,用户最自然的操作就是把文件发送给别人。很多新手会自己跳转第三方分享 SDK,其实 iOS 原生就把这条路铺好了——UIActivityViewController,也就是系统分享面板。这也是我在标题热词里看到"ios系统原生分享实现"才特意展开说的:原生分享不仅能分享到微信、QQ、邮件,还能存到 Files,兼容性比接一堆 SDK 好太多。
let activityVC = UIActivityViewController(activityItems: [signedFileURL], applicationActivities: nil) present(activityVC, animated: true)这里有个细节:分享的 PDF 如果不想让系统直接全屏预览,而是走"存储/发送",可以把文件先存到临时目录,再把fileURL传进去。如果传Data,大部分分享目标会把文件存成通用二进制,名字和格式都不好控制。
4. 常见问题与排查技巧实录
4.1 高频问题速查表
开发过程中我把团队踩过的坑整理成了一张表,后面接手的人照着查能省一半时间:
| 现象 | 根因 | 解决办法 |
|---|---|---|
| 签章位置上下颠倒 | 坐标系没做翻转 | 用 PDFPage 坐标而非 view 坐标,绘制时翻转 CGContext |
| 缩放后印章漂移 | 存储了屏幕坐标 | 统一存 PDF 坐标,展示时再转换 |
| 手写签名锯齿明显 | 图片分辨率过低 | 按 @3x 生成,关闭抗锯齿,放大 2-3 倍绘制 |
| 打开大 PDF 内存暴涨 | 直接把整个文档加载进内存 | 用 PDFDocument 的init(data:)配合流式读取,或分页渲染 |
| 分享出去的文件是 .bin | 传了 Data 而不是 fileURL | 先写临时文件再分享 URL |
| 服务端返回的 PDF 打不开 | 签名封装破坏了结构 | 核对 ByteRange 位置,确保未修改原文件字节 |
4.2 实战踩坑记录
下面这几个问题是我印象最深的,每个都花了大半天才定位到原因。
第一个是"看得到但存不了"的问题。我用 Core Graphics 重绘方式生成新 PDF 后,在预览里一切正常,但把文件发到电脑上,Adobe Acrobat 直接报"文件已损坏"。后来逐字节对比才发现,我在重绘时用了pageMediaBox,而原 PDF 某些页用的是cropBox,导致某些页的尺寸信息对不上。解决办法是统一用page.bounds(for: .mediaBox)和page.bounds(for: .cropBox)两个值同时取,绘制时用 cropBox 作为画布,坐标换算用 mediaBox。
第二个是字体缺失问题。原 PDF 里如果嵌入了特殊字体,用 Core Graphics 重绘时,如果 context 不支持该字体,渲染出来就是方块。这个问题没法逐一解决字体,我的方案是从服务端拿原生 PDF 做叠加层——也就是把签章作为 annotation 加在原 PDF 上,不让重绘逻辑碰原页面内容。最终生产环境也改成了这种方式。
4.3 性能与内存优化建议
PDF 签章功能最容易翻车的就是大文件。我实测过一份 200 页、500MB 的标书,直接用PDFKit加载加签章,App 内存峰值能到 1.5GB,iPhone 直接被杀。后来做了三层优化,效果非常明显:
- 分层渲染:预览时只渲染当前可见页,用
PDFPage的缩略图接口替代整页高分辨率绘制; - 异步生成:签章写入和 PDF 生成全部丢到后台队列,绝不阻塞主线程,进度条显示"正在生成签署文件…";
- 内存池复用:重绘时关闭
UIGraphicsPDFRenderer的自动缓存,每一页画完立即释放 context,用autoreleasepool包住循环体。
优化之后,同样 500MB 的文件,峰值内存降到了 300MB 左右,生成耗时从 20 多秒降到 8 秒,基本达到可用状态。
5. 最后一点个人经验
做这个功能前前后后花了三周,回头看我最大的体会是:iOS 端做 PDF 签章,难点从来不在"画图"上,而在对整个 PDF 结构、坐标体系、文件完整性约束的理解上。如果你只是给 PDF 贴个图章,一两百行代码就能搞定;但要做成一个能上生产环境、能通过财务法务验收的签章模块,坐标系转换、并发内存控制、数字证书对接,这些"看不见"的功夫才是最花时间的。
另外一个小技巧分享给做同类功能的朋友:签章图片尽量用 PNG 透明底,尺寸控制在 512×512 以内,超过这个尺寸在 PDF 里缩小时没问题,放大到打印尺寸时会暴露马赛克。还有,上线前一定把生成的文件用 Adobe Acrobat、苹果预览、WPS 三个软件分别打开验证一遍,很多 iOS 端看不出来的问题,在别的阅读器里会现出原形。
本文还有配套的精品资源,点击获取