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

SwiftUI ScrollView中嵌入UITextView实现局部文本选择的布局问题

解决SwiftUI中ScrollView内UITextView的布局与局部文本选择问题

问题背景与需求

我们正在开发基于SwiftUI的项目,需要实现局部文本选择复制(而非SwiftUI .textSelection() 提供的全选功能),目前采用UIViewRepresentable封装UITextView的方案,核心需求如下:

  • 支持用户选择部分文本进行复制
  • 文本视图不可滚动,且宽高布局适配正确
  • 支持富文本显示

遇到的问题

将该UITextView嵌入包含其他视图的ScrollView时,出现两个矛盾的布局问题:

  • 禁用UITextView滚动后,文本会超出可用宽度,破坏整体布局
  • 启用滚动时,UITextView渲染高度极小,内容无法完整显示

原实现代码

struct TextView: UIViewRepresentable {
     var text: AttributedString
     var textStyle: UIFont
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        
        // set font property
        textView.font = textStyle
        textView.isSelectable = true
        
        // enable user interaction
        textView.isUserInteractionEnabled = true
        
        // set is editable to false
        // to prevent user editing the text view
        // or avoid showing any cursor
        textView.isEditable = false
        
        // disable scroll to avoid the text view to be shown
        // inside a scrollable view
        textView.isScrollEnabled = false
        
        // tracks the height of the view based on content inside it
        textView.textContainer.heightTracksTextView = true
        return textView
    }
    func updateUIView(_ uiView: UITextView, context: Context) {
        // set attributed text to ui text view
        uiView.attributedText = NSAttributedString(text)
        // set font
        uiView.font = textStyle
    }
}

解决方案

要在ScrollView内让UITextView既禁用滚动、支持局部选中文本,又能正确适配布局,需要从布局约束、内容自适应两个方面调整:

1. 核心调整点

  • 给UITextView添加明确的宽度约束,绑定父容器的可用宽度,避免文本溢出
  • 关闭heightTracksTextView,改用sizeToFit()配合布局优先级来让高度自适应内容
  • 调整文本容器的内边距,避免文本紧贴边缘

2. 修改后的完整代码

struct TextView: UIViewRepresentable {
    var text: AttributedString
    var textStyle: UIFont
    // 可选:自定义内边距,默认和SwiftUI Text对齐
    var insets: UIEdgeInsets = UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)
    
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        
        textView.font = textStyle
        textView.isSelectable = true
        textView.isUserInteractionEnabled = true
        textView.isEditable = false
        textView.isScrollEnabled = false
        
        // 关键:关闭高度追踪,让视图根据内容计算高度
        textView.textContainer.heightTracksTextView = false
        // 设置文本容器内边距,适配SwiftUI布局
        textView.textContainerInset = insets
        // 禁止文本容器宽度超过视图宽度,强制换行
        textView.textContainer.widthTracksTextView = true
        
        // 关闭自动调整大小,避免和SwiftUI布局冲突
        textView.translatesAutoresizingMaskIntoConstraints = false
        
        return textView
    }
    
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.attributedText = NSAttributedString(text)
        uiView.font = textStyle
        uiView.textContainerInset = insets
        
        // 更新内容后强制计算正确尺寸
        uiView.layoutIfNeeded()
        uiView.sizeToFit()
        // 确保高度足够容纳所有内容
        uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
        uiView.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
    }
}

// 使用示例
struct ContentView: View {
    let sampleAttributedText: AttributedString = {
        var str = AttributedString("这是一段测试富文本,包含多行内容,用来验证UITextView在ScrollView中的布局是否正常。我们需要确保文本可以自动换行,高度自适应,同时支持局部选择复制。")
        str.font = .systemFont(ofSize: 16)
        str.foregroundColor = .black
        return str
    }()
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                Text("标题")
                    .font(.title)
                TextView(text: sampleAttributedText, textStyle: .systemFont(ofSize: 16))
                    // 关键:给TextView绑定父容器的宽度约束
                    .frame(maxWidth: .infinity)
                Text("其他内容视图")
                    .font(.headline)
                // 更多视图...
            }
            .padding()
        }
    }
}

3. 关键说明

  • 宽度约束:在SwiftUI侧给TextView添加.frame(maxWidth: .infinity),让它继承父容器的可用宽度,配合textContainer.widthTracksTextView = true强制文本换行
  • 高度自适应:sizeToFit()会让UITextView根据内容计算准确高度,再通过contentHuggingPriority确保它不会被压缩或拉伸
  • 布局冲突避免:关闭translatesAutoresizingMaskIntoConstraints,让SwiftUI主导布局,同时禁用UITextView的滚动,避免嵌套滚动冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 16:12:42