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

SwiftUI自定义富文本视图无法自动换行的问题求助

解决UITextView封装后文本不自动换行的问题

问题根源

  1. 硬设宽度+padding叠加冲突:调用时直接给RichTextView设置width: UIScreen.screenWidth,再叠加leading/trailing padding,导致UITextView的实际可布局宽度超出预期,无法触发换行逻辑。
  2. 高度计算传入无效宽度:calculateHeight方法传width: .infinity,sizeThatFits无法计算正确的换行宽度,返回的高度不准确,进而导致视图高度被错误设置。
  3. UIKit视图配置干扰布局:textContainer.widthTracksTextView = true会让文本容器宽度无限跟随TextView,加上updateUIView里调用sizeToFit,打乱了Auto Layout的正常布局流程。
  4. 误用SwiftUI修饰符:multilineTextAlignment、lineLimit、lineSpacing是SwiftUI原生Text的专属修饰符,对UIKit封装的UITextView完全无效。

分步解决方案

1. 修正RichTextView的UIKit配置

移除干扰布局的属性,让UITextView完全响应SwiftUI的布局约束:

struct RichTextView: UIViewRepresentable {
    var text: String
    var lineSpacing: CGFloat = 5
    var paragraphSpacing: CGFloat = 10
    var textAlignment: NSTextAlignment = .left

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.backgroundColor = .clear
        textView.isEditable = false
        textView.isScrollEnabled = false
        textView.font = UIFont.systemFont(ofSize: 18)
        textView.dataDetectorTypes = .all
        textView.linkTextAttributes = [NSAttributedString.Key.foregroundColor: Colors.colorPrimary()]
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.textContainer.lineBreakMode = .byWordWrapping
        textView.textAlignment = textAlignment
        return textView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        let paragraphStyle = NSMutableParagraphStyle()
        paragraphStyle.lineSpacing = lineSpacing
        paragraphStyle.paragraphSpacing = paragraphSpacing
        paragraphStyle.lineBreakMode = .byWordWrapping
        paragraphStyle.lineBreakStrategy = .standard

        let attributedString = NSAttributedString(
            string: text,
            attributes: [
                .font: UIFont.systemFont(ofSize: 18),
                .paragraphStyle: paragraphStyle,
                .foregroundColor: UIColor.label
            ]
        )

        uiView.attributedText = attributedString
        uiView.textAlignment = textAlignment
        uiView.isScrollEnabled = false
    }
}

2. 修正调用代码的布局逻辑

去掉无效修饰符,用正确方式约束视图宽度:

private let horizontalPadding: CGFloat = 16 // 和padding值保持一致

RichTextView(text: description)
    .padding([.leading, .trailing], horizontalPadding)
    .padding(.bottom, 16)
    .frame(maxWidth: .infinity) // 让视图占满父布局可用宽度,触发换行
    .onAppear {
        // 传入实际可用宽度:屏幕宽度 - 左右padding总和
        self.viewHeight = calculateHeight(for: description, width: UIScreen.screenWidth - 2 * horizontalPadding)
    }
    .onChange(of: description) { _ in
        self.viewHeight = calculateHeight(for: description, width: UIScreen.screenWidth - 2 * horizontalPadding)
    }

3. 修正高度计算方法

传入实际可用宽度,保持和RichTextView一致的配置:

private func calculateHeight(for text: String, width: CGFloat) -> CGFloat {
    let textView = UITextView()
    textView.font = UIFont.systemFont(ofSize: 18)
    textView.dataDetectorTypes = .all // 和RichTextView保持一致,避免链接识别影响高度计算
    textView.isScrollEnabled = false

    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineSpacing = 5
    paragraphStyle.paragraphSpacing = 10
    paragraphStyle.lineBreakMode = .byWordWrapping
    paragraphStyle.lineBreakStrategy = .standard

    let attributedString = NSAttributedString(
        string: text,
        attributes: [
            .font: UIFont.systemFont(ofSize: 18),
            .paragraphStyle: paragraphStyle
        ]
    )
    textView.attributedText = attributedString
    textView.textContainer.lineBreakMode = .byWordWrapping
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.textContainer.maximumNumberOfLines = 0

    // 使用传入的具体可用宽度计算高度
    let size = textView.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
    return size.height
}

关键说明

  • 移除textContainer.widthTracksTextView = true:该属性会让文本容器宽度无限扩展,导致文本永远不换行,必须去掉才能让UITextView根据布局宽度自动换行。
  • 放弃SwiftUI原生文本修饰符:UIKit视图的配置必须在UITextView内部完成,SwiftUI的lineLimit、lineSpacing等修饰符对它无效。
  • 高度计算必须用实际可用宽度:如果传.infinity,sizeThatFits会认为文本可以无限宽,自然不会计算换行后的高度。

内容的提问来源于stack exchange,提问作者Lilia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 06:05:02