macOS NSPrintOperation打印时SwiftUI Text视图截断但屏幕显示正常如何解决
问题根因
- 外层使用了
List组件:macOS下SwiftUI的List自带单元格复用、隐式行高估算逻辑,渲染到打印视图时,会按屏幕显示的逻辑计算行高,不会适配打印用的大尺寸画布,导致文字被截断。 - 打印时额外做了位图转换:将视图转成位图再打印的逻辑,会丢失矢量文字的排版信息,且屏幕和打印的dpi差异会导致文字换行、截断逻辑随机异常。
- 打印画布高度写死:固定2800的高度没有匹配内容实际高度,会出现内容溢出或者空白过多的问题。
修复方案
第一步:修改视图代码
替换外层List为VStack,并给所有Text添加纵向自适应修饰符,确保行高完全匹配两行文本的高度:
struct ScheduleDisplayView: View { var schedule: [SchedSlot] let chanmax: CGFloat = 28.0 let fontsize: CGFloat = 7.0 let cellmax: CGFloat = 120 var sortedData : [DayBlock] { let schedTree: ScheduleTree = ScheduleTree.init() for ss in schedule { schedTree.add(schedSlot: ss) } return schedTree.dayList } var body: some View { // 替换List为VStack,打印不需要滚动容器 VStack(alignment: .leading, spacing: 8) { ForEach(sortedData, id: \.dateStamp) { day in Text("\(day.dateStamp)") .bold() ForEach(day.qList, id: \.QTag) { qblock in ForEach(qblock.chanList.sorted(by: <), id: \.chanTag) { channel in HStack(alignment: .top, spacing: 0) { VStack(spacing: 0) { Text(String(channel.chanTag)) Text(channel.callSign.prefix(4)) } .border(Color.yellow) .frame(maxWidth: chanmax, alignment: .topLeading) .padding(0) ForEach(channel.timeList, id: \.timeTag) { timecell in VStack(spacing: 0) { Text("\(timecell.timeTag)") .frame(maxWidth: .infinity, alignment: .topLeading) .fixedSize(horizontal: false, vertical: true) // 新增:纵向自适应高度 ForEach(timecell.cellList, id: \.id) { cell in if cell.startTime != timecell.timeTag { Text("\(cell.title) (\(cell.startTime))") .foregroundColor(.blue) .frame(maxWidth: .infinity, alignment: .topLeading) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) // 新增 } else { Text(cell.title) .foregroundColor(.blue) .frame(maxWidth: .infinity, alignment: .topLeading) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) // 新增 } Text(cell.subtitle) .frame(maxWidth: .infinity, alignment: .topLeading) .lineLimit(2) .fixedSize(horizontal: false, vertical: true) // 新增 } } } .border(Color.green) .frame(maxWidth: .infinity, alignment: .leading) } .font(.system(size: fontsize)) .border(Color.blue) } } } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) // 新增:让VStack适配父视图尺寸 } }
第二步:修改打印逻辑
删除位图转换逻辑,直接打印NSHostingView,并自动计算内容实际高度:
func printScheduleView(schedule: [SchedSlot] ) { let printInfo = NSPrintInfo.shared // 建议保留少量边距,避免内容贴边被裁切 printInfo.topMargin = 20.0 printInfo.bottomMargin = 20.0 printInfo.rightMargin = 20.0 printInfo.leftMargin = 20.0 printInfo.horizontalPagination = .fit printInfo.verticalPagination = .automatic printInfo.isHorizontallyCentered = false printInfo.isVerticallyCentered = false let view = ScheduleDisplayView(schedule: schedule) // 自动计算内容实际高度,不要写死 let hostingView = NSHostingView(rootView: view) let fittingSize = hostingView.sizeThatFits(NSSize(width: 900, height: CGFloat.greatestFiniteMagnitude)) let contentRect = NSRect(x: 0, y: 0, width: 900, height: fittingSize.height) hostingView.frame = contentRect // 强制触发布局,确保排版完全计算完成 hostingView.layoutSubtreeIfNeeded() // 直接打印原生视图,保留矢量渲染效果 let printOperation = NSPrintOperation(view: hostingView, printInfo: printInfo) printOperation.showsPrintPanel = true printOperation.showsProgressPanel = true printOperation.run() }
补充说明
如果后续需要实现分页功能,可以自定义NSPrintPageRenderer,按打印页高度拆分内容即可。当前修改已解决文本随机截断、行裁切的问题,打印效果和屏幕显示完全一致。
内容的提问来源于stack exchange,提问作者Joe Bell
相关产品推荐
相关产品推荐

