导出PDF时应用冻结问题求助(子视图超出屏幕时触发)
我参考视频教程给应用添加导出PDF功能,遇到了难以调试的冻结问题:当屏幕显示的子视图超过4个Card时,选择导出PDF选项会导致应用完全冻结。
我尝试用调试器排查,但没找到冻结原因:在exportPDF代码的do{}catch{}处设置断点,能看到有效的pdfView:
(lldb) po pdfView <UIScrollView: 0x105823600; frame = (0 59; 430 942); clipsToBounds = YES; tag = 1009; gestureRecognizers = <NSArray: 0x2819abea0>; layer = <CALayer: 0x281700200>; contentOffset: {0, 0}; contentSize: {430, 942}; adjustedContentInset: {0, 0, 0, 0}>
但离开闭包后应用就会冻结,永远无法到达getRootController().view.subviews.forEach的断点。
以下是简化后的调用代码(已移除CoreData查询和属性定义):
struct ViewEventsView: View { // MARK: PDF Properties @State var PDFUrl: URL? @State var showShareSheet: Bool = false var body: some View { ScrollView { VStack { HStack { AddressView(recipient: recipient) .scaledToFit() .frame(width: 250, height: 150) } } ScrollView { LazyVGrid(columns: gridLayout, alignment: .center, spacing: 5) { ForEach(events, id: \.self) { event in HStack { VStack { Image(uiImage: (event.cardFrontImage ?? blankCardFront)!) .resizable() .aspectRatio(contentMode: .fit) .scaledToFit() .frame(width: iPhone ? 120 : 200, height: iPhone ? 120 : 200) .padding(.top, iPhone ? 2: 5) HStack { VStack { Text("\(event.event ?? \"")") .foregroundColor(.green) Spacer() HStack { Text("\(event.eventDate ?? NSDate(), formatter: ViewEventsView.eventDateFormatter)") .fixedSize() .foregroundColor(.green) MenuOverlayView(recipient: recipient, event: event) } } .padding(iPhone ? 1 : 5) .font(iPhone ? .caption : .title3) .foregroundColor(.primary) } } } .padding() .frame(minWidth: iPhone ? 160 : 320, maxWidth: .infinity, minHeight: iPhone ? 160 : 320, maxHeight: .infinity) .background(Color(UIColor.systemGroupedBackground)) .mask(RoundedRectangle(cornerRadius: 20)) .shadow(radius: 5) .padding(iPhone ? 5: 10) } .padding() } } .navigationBarTitleDisplayMode(.inline) .navigationBarItems(trailing: HStack { Button(action: { // MARK: self is Current View // You can give whatever View to Conver // print(convertToScrollView(content: { // self // }).contentSize) exportPDF { self } completion: { status, url in if let url = url, status { PDFUrl = url showShareSheet.toggle() } else { print("Failed to Produce PDF") } } }, label: { Image(systemName: "square.and.arrow.up.fill") .font(.title2) .foregroundColor(.green) }) }) } .sheet(isPresented: $showShareSheet) { PDFUrl = nil } content: { if let PDFUrl = PDFUrl { ShareSheet(urls: [PDFUrl]) } } } } }
求调试建议。
检查PDF渲染的线程归属:PDF导出的视图渲染、文件写入属于耗时操作,若在主线程执行,大量Card的内容会直接阻塞主线程导致冻结。查看
exportPDF实现,确保核心逻辑放在后台队列(如DispatchQueue.global(qos: .userInitiated))执行,完成后切回主线程更新UI。排查LazyVGrid的懒加载冲突:
LazyVGrid仅渲染可见区域视图,但PDF导出需要全部Card的完整渲染,未预加载的视图可能导致渲染过程中出现阻塞或死锁。临时替换为普通VGrid测试,若问题消失,说明是懒加载机制导致的,需在导出前强制触发所有视图预渲染,或改用非懒加载布局。优化图片加载逻辑:每个Card包含的图片可能在主线程解码,大量图片会占用内存并阻塞主线程。改用后台队列加载并解码图片,同时避免强制解包
event.cardFrontImage ?? blankCardFront!,改用可选绑定或安全默认值,减少潜在的内存异常风险。检测线程死锁:应用冻结时,打开Xcode的Debug Navigator查看线程调用栈,重点看主线程是否存在循环等待、锁竞争情况,比如
exportPDF内部是否误用了主线程同步锁,导致主线程自我阻塞。简化内容逐步排查:
- 先导出仅含文本的Card,去掉图片和复杂布局,验证是否还会冻结
- 逐步增加Card数量,定位触发冻结的临界点
- 移除代码中嵌套的
ScrollView,测试布局简化后的导出是否正常
检查视图转换逻辑:视频教程中的
exportPDF可能通过SwiftUI转UIKit视图实现渲染,复杂的视图层级(嵌套ScrollView+LazyVGrid)可能导致转换过程中出现布局计算死锁。尝试打印视图转换过程的日志,或简化导出的视图结构。
内容的提问来源于stack exchange,提问作者Michael Rowe

