iOS14下SwiftUI实现限高5行的类消息式可滚动TextView问题
实现自适应高度且带最大行数限制的TextView问题
我想要实现一个类消息输入框的UITextView,要求:
- 初始高度为1行
- 最大高度限制为5行
- 内容达到5行高度时,停止高度扩展并启用滚动功能
目前编写的代码可以随换行自动增高,但达到5行高度后不会停止增高,也不会启用滚动,运行环境为iOS14:
struct AutoAdjustingTextView: UIViewRepresentable { @Binding var text: String func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.isScrollEnabled = false textView.font = UIFont.systemFont(ofSize: 17) textView.delegate = context.coordinator textView.textContainerInset = .zero textView.textContainer.lineFragmentPadding = 0 textView.setContentHuggingPriority(.defaultHigh, for: .vertical) return textView } func updateUIView(_ uiView: UITextView, context: Context) { uiView.text = text updateTextViewHeight(uiView) } private func updateTextViewHeight(_ textView: UITextView) { let size = textView.sizeThatFits(CGSize(width: textView.frame.width, height: .greatestFiniteMagnitude)) if textView.frame.size.height != size.height { textView.frame.size.height = size.height } } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITextViewDelegate { var parent: AutoAdjustingTextView init(_ parent: AutoAdjustingTextView) { self.parent = parent } func textViewDidChange(_ textView: UITextView) { parent.text = textView.text parent.updateTextViewHeight(textView) } } }
解决方案
问题核心是缺少最大行数对应的高度计算,以及未在达到阈值时切换滚动状态。以下是修改后的完整代码:
struct AutoAdjustingTextView: UIViewRepresentable { @Binding var text: String // 定义最大行数常量 private let maxLines: Int = 5 private let font = UIFont.systemFont(ofSize: 17) func makeUIView(context: Context) -> UITextView { let textView = UITextView() textView.isScrollEnabled = false textView.font = font textView.delegate = context.coordinator textView.textContainerInset = .zero textView.textContainer.lineFragmentPadding = 0 textView.setContentHuggingPriority(.defaultHigh, for: .vertical) // 直接设置初始高度为单行文本高度 textView.frame.size.height = calculateSingleLineHeight() return textView } func updateUIView(_ uiView: UITextView, context: Context) { // 避免外部更新文本时重复计算高度 if uiView.text != text { uiView.text = text updateTextViewHeight(uiView) } } private func updateTextViewHeight(_ textView: UITextView) { let singleLineHeight = calculateSingleLineHeight() let maxHeight = singleLineHeight * CGFloat(maxLines) // 计算内容所需的理想高度 let fittingSize = textView.sizeThatFits(CGSize(width: textView.frame.width, height: .greatestFiniteMagnitude)) // 取理想高度和最大高度的较小值作为目标高度 let targetHeight = min(fittingSize.height, maxHeight) // 仅在高度变化时更新,避免不必要的布局刷新 if textView.frame.size.height != targetHeight { textView.frame.size.height = targetHeight } // 当内容实际高度超过最大高度时,启用滚动 textView.isScrollEnabled = textView.contentSize.height > maxHeight } // 计算单行文本的高度(通过UILabel间接获取,确保和TextView行高一致) private func calculateSingleLineHeight() -> CGFloat { let testLabel = UILabel() testLabel.font = font testLabel.text = "测试文本" testLabel.sizeToFit() return testLabel.frame.height } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITextViewDelegate { var parent: AutoAdjustingTextView init(_ parent: AutoAdjustingTextView) { self.parent = parent } func textViewDidChange(_ textView: UITextView) { parent.text = textView.text parent.updateTextViewHeight(textView) } } }
修改要点说明:
- 新增高度阈值计算:通过单行高度乘以最大行数,得到允许的最大高度值
- 限制高度扩展:用
min函数确保TextView高度不会超过最大行数对应的高度 - 动态切换滚动状态:对比内容实际高度和最大高度,自动开启/关闭滚动功能
- 设置初始高度:在初始化时直接将TextView高度设为单行高度,符合需求
- 优化更新逻辑:仅在文本实际变化时才更新高度,减少无效布局操作
内容的提问来源于stack exchange,提问作者Haumikj Haumer
相关产品推荐
相关产品推荐

