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
相关产品推荐
相关产品推荐

