Skip to content

0x058-iOS使用PDF合成图片

伪代码示例(未优化):

swift
import Foundation
import PDFKit
import SnapKit
import SVProgressHUD

enum PDFEditorError: Error {
    case invalidPageNumber(reason: String)
    case invalidPDFDocument(reason: String)
    case imageDataError(reason: String)
    case createDocumentError(reason: String)
    case getTempPageError(reason: String)
}

class PDFEditor {
    // 远程url地址
    var remoteUrl: String
    init(remoteUrl: String) {
        self.remoteUrl = remoteUrl
    }

    func loadRemote(savePath: URL,
                    downloadFinished: @escaping (PDFDocument) -> Void,
                    errorCallback: @escaping (String) -> Void)
    {
        if let url = URL(string: remoteUrl) {
            let session = URLSession.shared
            let task = session.dataTask(with: url) { data, response, error in
                if let _ = error {
                    SVProgressHUD.showError(withStatus: "下载报告失败")
                    return
                }
                guard let httpResponse = response as? HTTPURLResponse,
                      (200 ... 299).contains(httpResponse.statusCode)
                else {
                    SVProgressHUD.showError(withStatus: "下载报告失败")
                    return
                }
                if let data = data {
                    do {
                        try data.write(to: savePath)
                        if let document = PDFDocument(url: savePath) {
                            DispatchQueue.main.async {
                                downloadFinished(document)
                            }
                        } else {
                            DispatchQueue.main.async {
                                errorCallback("加载pdf失败")
                            }
                        }
                    } catch {
                        DispatchQueue.main.async {
                            errorCallback("文件保存失败: \(error)")
                        }
                        SVProgressHUD.showError(withStatus: "文件保存失败: \(error)")
                    }
                } else {
                    DispatchQueue.main.async {
                        errorCallback("未获取到文档")
                    }
                }
            }
            task.resume()
        } else {
            DispatchQueue.main.async {
                errorCallback("PDF地址有误")
            }
        }
    }

    func addImageToPDF(pdfURL: URL, image: UIImage, pageNumber: Int, rect: CGRect) throws -> Data {
        guard let pdfDocument = PDFDocument(url: pdfURL)
        else {
            throw PDFEditorError.invalidPDFDocument(reason: "PDF document could not be created from URL.")
        }
        guard pageNumber >= 0 && pageNumber < pdfDocument.pageCount,
              let page = pdfDocument.page(at: pageNumber)
        else {
            throw PDFEditorError.invalidPageNumber(reason: "Page number is out of bounds.")
        }

        // 创建一个可变的 PDFDocument
        let mutablePdfDocument = PDFDocument()
        // 复制原始 PDF 的所有页面到新的文档中
        for i in 0 ..< pdfDocument.pageCount {
            if let originalPage = pdfDocument.page(at: i) {
                mutablePdfDocument.insert(originalPage, at: i)
            }
        }
        // 创建 NSMutableData 用于保存 PDF 数据
        let pdfData = NSMutableData()
        // 获取 PDFPage 的 bounds
        let pageBounds = page.bounds(for: .mediaBox)
        // 创建 PDF 上下文
        UIGraphicsBeginPDFContextToData(pdfData, pageBounds, nil)
        // 开始新的 PDF 页
        UIGraphicsBeginPDFPageWithInfo(pageBounds, nil)
        // 将背景页绘制到上下文中
        let context = UIGraphicsGetCurrentContext()!
        // 翻转坐标系
        // 将坐标系的原点移动到页面的左上角。
        context.translateBy(x: 0, y: pageBounds.size.height)
        // 沿 Y 轴翻转坐标系
        context.scaleBy(x: 1.0, y: -1.0)
        page.draw(with: .mediaBox, to: context)

        // 定义图片绘制的位置和大小,因为以上步骤会进行翻转,需要调节位置
        let imageWidth: CGFloat = 200
        let imageHeight: CGFloat = 150
        let imageX: CGFloat = 50
        let imageY: CGFloat = pageBounds.size.height - 50 - imageHeight // 调整后的 y 坐标
        let imageRect = CGRect(x: imageX, y: imageY, width: imageWidth, height: imageHeight)
        // 10. 绘制图片
        context.draw(image.cgImage!, in: imageRect)
        // 11. 结束 PDF 页
        UIGraphicsEndPDFContext()

        guard let tempPdfDocument = PDFDocument(data: pdfData as Data) else {
            throw PDFEditorError.createDocumentError(reason: "Failed to create temporary PDF document from data.")
        }

        guard let pdfPage = tempPdfDocument.page(at: 0) else {
            throw PDFEditorError.getTempPageError(reason: "Failed to retrieve page from temporary PDF document.")
        }
        mutablePdfDocument.removePage(at: pageNumber)
        mutablePdfDocument.insert(pdfPage, at: pageNumber)
        // 返回修改后的 PDF 数据
        guard let finalData = mutablePdfDocument.dataRepresentation() else {
            throw PDFEditorError.imageDataError(reason: "Failed to generate final PDF data representation.")
        }
        return finalData
    }
}
import Foundation
import PDFKit
import SnapKit
import SVProgressHUD

enum PDFEditorError: Error {
    case invalidPageNumber(reason: String)
    case invalidPDFDocument(reason: String)
    case imageDataError(reason: String)
    case createDocumentError(reason: String)
    case getTempPageError(reason: String)
}

class PDFEditor {
    // 远程url地址
    var remoteUrl: String
    init(remoteUrl: String) {
        self.remoteUrl = remoteUrl
    }

    func loadRemote(savePath: URL,
                    downloadFinished: @escaping (PDFDocument) -> Void,
                    errorCallback: @escaping (String) -> Void)
    {
        if let url = URL(string: remoteUrl) {
            let session = URLSession.shared
            let task = session.dataTask(with: url) { data, response, error in
                if let _ = error {
                    SVProgressHUD.showError(withStatus: "下载报告失败")
                    return
                }
                guard let httpResponse = response as? HTTPURLResponse,
                      (200 ... 299).contains(httpResponse.statusCode)
                else {
                    SVProgressHUD.showError(withStatus: "下载报告失败")
                    return
                }
                if let data = data {
                    do {
                        try data.write(to: savePath)
                        if let document = PDFDocument(url: savePath) {
                            DispatchQueue.main.async {
                                downloadFinished(document)
                            }
                        } else {
                            DispatchQueue.main.async {
                                errorCallback("加载pdf失败")
                            }
                        }
                    } catch {
                        DispatchQueue.main.async {
                            errorCallback("文件保存失败: \(error)")
                        }
                        SVProgressHUD.showError(withStatus: "文件保存失败: \(error)")
                    }
                } else {
                    DispatchQueue.main.async {
                        errorCallback("未获取到文档")
                    }
                }
            }
            task.resume()
        } else {
            DispatchQueue.main.async {
                errorCallback("PDF地址有误")
            }
        }
    }

    func addImageToPDF(pdfURL: URL, image: UIImage, pageNumber: Int, rect: CGRect) throws -> Data {
        guard let pdfDocument = PDFDocument(url: pdfURL)
        else {
            throw PDFEditorError.invalidPDFDocument(reason: "PDF document could not be created from URL.")
        }
        guard pageNumber >= 0 && pageNumber < pdfDocument.pageCount,
              let page = pdfDocument.page(at: pageNumber)
        else {
            throw PDFEditorError.invalidPageNumber(reason: "Page number is out of bounds.")
        }

        // 创建一个可变的 PDFDocument
        let mutablePdfDocument = PDFDocument()
        // 复制原始 PDF 的所有页面到新的文档中
        for i in 0 ..< pdfDocument.pageCount {
            if let originalPage = pdfDocument.page(at: i) {
                mutablePdfDocument.insert(originalPage, at: i)
            }
        }
        // 创建 NSMutableData 用于保存 PDF 数据
        let pdfData = NSMutableData()
        // 获取 PDFPage 的 bounds
        let pageBounds = page.bounds(for: .mediaBox)
        // 创建 PDF 上下文
        UIGraphicsBeginPDFContextToData(pdfData, pageBounds, nil)
        // 开始新的 PDF 页
        UIGraphicsBeginPDFPageWithInfo(pageBounds, nil)
        // 将背景页绘制到上下文中
        let context = UIGraphicsGetCurrentContext()!
        // 翻转坐标系
        // 将坐标系的原点移动到页面的左上角。
        context.translateBy(x: 0, y: pageBounds.size.height)
        // 沿 Y 轴翻转坐标系
        context.scaleBy(x: 1.0, y: -1.0)
        page.draw(with: .mediaBox, to: context)

        // 定义图片绘制的位置和大小,因为以上步骤会进行翻转,需要调节位置
        let imageWidth: CGFloat = 200
        let imageHeight: CGFloat = 150
        let imageX: CGFloat = 50
        let imageY: CGFloat = pageBounds.size.height - 50 - imageHeight // 调整后的 y 坐标
        let imageRect = CGRect(x: imageX, y: imageY, width: imageWidth, height: imageHeight)
        // 10. 绘制图片
        context.draw(image.cgImage!, in: imageRect)
        // 11. 结束 PDF 页
        UIGraphicsEndPDFContext()

        guard let tempPdfDocument = PDFDocument(data: pdfData as Data) else {
            throw PDFEditorError.createDocumentError(reason: "Failed to create temporary PDF document from data.")
        }

        guard let pdfPage = tempPdfDocument.page(at: 0) else {
            throw PDFEditorError.getTempPageError(reason: "Failed to retrieve page from temporary PDF document.")
        }
        mutablePdfDocument.removePage(at: pageNumber)
        mutablePdfDocument.insert(pdfPage, at: pageNumber)
        // 返回修改后的 PDF 数据
        guard let finalData = mutablePdfDocument.dataRepresentation() else {
            throw PDFEditorError.imageDataError(reason: "Failed to generate final PDF data representation.")
        }
        return finalData
    }
}

Pdf使用示例:

swift
let pdfView = PDFView()
// 展示模式: 单页、多页
pdfView.displayMode = .singlePageContinuous
pdfView.autoScales = true
// 设置 PDF 的显示方向 (例如垂直、水平)
pdfView.displayDirection = .vertical
if let document = PDFDocument(url: url) {
	pdfView.document = document
}
let pdfView = PDFView()
// 展示模式: 单页、多页
pdfView.displayMode = .singlePageContinuous
pdfView.autoScales = true
// 设置 PDF 的显示方向 (例如垂直、水平)
pdfView.displayDirection = .vertical
if let document = PDFDocument(url: url) {
	pdfView.document = document
}