Swift iOS开发:如何用PDFParser提取PDF文本并解决内存与定位问题?
解决方案:无内存崩溃的PDF文本提取与定位
一、解决内存崩溃问题:逐页处理+自动释放
PDFKit遍历全页时的内存崩溃,大多是因为未及时释放单页资源。用autoreleasepool包裹单页处理逻辑,能强制回收每页的临时对象,降低内存峰值。结合你使用的PDFParser,先优化文本提取的准确性(解决复杂PDF文本错乱),再实现内存友好的全页遍历:
1. 优化PDFParser的文本提取(修复复杂PDF错乱)
复杂PDF的文本块可能存在排版顺序混乱,需要按页面位置从上到下、从左到右排序后再拼接:
extension SimpleDocumentIndexer { public func extractWholeTextFromPage(pageNumber: Int) -> String { guard let pageIndex = pageIndexes[pageNumber] else { return "" } // 按PDF坐标系排序:minY越大位置越靠上,同Y轴按minX从左到右 let sortedBlocks = pageIndex.textBlocks.sorted { block1, block2 in if block1.frame.minY != block2.frame.minY { return block1.frame.minY > block2.frame.minY } else { return block1.frame.minX < block2.frame.minX } } return sortedBlocks.map(\.chars).joined() } }
2. 内存安全的全页遍历与文本匹配
逐页处理目标文本匹配,处理完一页就释放资源,避免全量加载所有页面文本:
/// 遍历PDF所有页面,检查目标文本是否存在,返回页面号与匹配文本的映射 func checkTargetTexts(in document: PDFDocument, indexer: SimpleDocumentIndexer, targets: [String]) -> [Int: [String]] { var pageMatches = [Int: [String]]() let totalPages = document.pageCount for pageNum in 0..<totalPages { autoreleasepool { let pageText = indexer.extractWholeTextFromPage(pageNumber: pageNum) let matchedTexts = targets.filter { pageText.contains($0) } if !matchedTexts.isEmpty { pageMatches[pageNum] = matchedTexts } } } return pageMatches }
二、获取文本坐标/CGRect及前后上下文
针对匹配到的页面,单独处理文本定位,避免全量解析所有页面的坐标数据:
1. 获取目标文本的CGRect坐标(基于PDFKit)
/// 在指定PDF页面查找目标文本的所有位置坐标 func findTextRects(on page: PDFPage, target: String) -> [CGRect] { guard let textContent = page.textContent, let pageString = page.string else { return [] } var rects = [CGRect]() var currentRange = pageString.range(of: target) while let range = currentRange { let nsRange = NSRange(range, in: pageString) // 转换为glyph范围,获取对应坐标 let glyphRange = textContent.glyphRange(for: nsRange) var unionRect = CGRect.null textContent.enumerateGlyphs(in: glyphRange) { _, _, _, rect in unionRect = unionRect.union(rect) } rects.append(unionRect) // 查找下一个匹配项 let nextStart = range.upperBound currentRange = pageString.range(of: target, range: nextStart..<pageString.endIndex) } return rects }
2. 获取目标文本的前后上下文
/// 获取目标文本在页面文本中的前后上下文(默认前后各50字符) func getSurroundingText(for target: String, in pageText: String, length: Int = 50) -> String? { guard let range = pageText.range(of: target) else { return nil } let start = pageText.index(range.lowerBound, offsetBy: -length, limitedBy: pageText.startIndex) ?? pageText.startIndex let end = pageText.index(range.upperBound, offsetBy: length, limitedBy: pageText.endIndex) ?? pageText.endIndex return String(pageText[start..<end]) }
三、整合流程示例
// 假设已初始化PDFDocument和SimpleDocumentIndexer let pdfDocument = PDFDocument(url: yourPDFURL)! let indexer = SimpleDocumentIndexer(document: pdfDocument) // 1. 检查所有页面的目标文本 let targetTexts = ["目标词1", "目标段落"] let pageMatches = checkTargetTexts(in: pdfDocument, indexer: indexer, targets: targetTexts) // 2. 对匹配的页面,获取坐标和上下文 for (pageNum, matches) in pageMatches { guard let page = pdfDocument.page(at: pageNum) else { continue } let pageText = indexer.extractWholeTextFromPage(pageNumber: pageNum) for match in matches { let rects = findTextRects(on: page, target: match) let context = getSurroundingText(for: match, in: pageText) print("页面\(pageNum+1)找到\(match),坐标:\(rects),上下文:\(context ?? "无")") } }
内容的提问来源于stack exchange,提问作者Aliya Chudhary
相关产品推荐
相关产品推荐

