You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS NSPrintOperation打印时SwiftUI Text视图截断但屏幕显示正常如何解决

问题根因
  1. 外层使用了List组件:macOS下SwiftUI的List自带单元格复用、隐式行高估算逻辑,渲染到打印视图时,会按屏幕显示的逻辑计算行高,不会适配打印用的大尺寸画布,导致文字被截断。
  2. 打印时额外做了位图转换:将视图转成位图再打印的逻辑,会丢失矢量文字的排版信息,且屏幕和打印的dpi差异会导致文字换行、截断逻辑随机异常。
  3. 打印画布高度写死:固定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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 05:45:07