如何实现UITextView嵌套于UIScrollView时的响应式自动滚动
解决UITextView嵌套UIScrollView后的滚动问题
我之前做项目时也踩过UITextView套UIScrollView的滚动坑,刚好遇到和你一模一样的问题,给你分享几个实测有效的解决思路:
核心思路
我们需要手动接管滚动逻辑,模拟原生UITextView的滚动行为——在光标(包括回车后的新行光标)移动到可视区域边缘时,自动调整UIScrollView的位置,同时保留上下内边距的效果。
具体实现步骤
1. 先做好基础布局
首先确保UITextView的约束能让它自适应内容高度,这样UIScrollView的contentSize才会正确更新:
- 禁用UITextView的
isScrollEnabled属性 - 给UITextView设置和UIScrollView内容区四边对齐的约束,同时让宽度和UIScrollView可视区一致
- 保留你原来设置的
textContainerInset,比如UIEdgeInsets(top: 40, left: 0, bottom: 40, right: 0)
2. 监听光标与文本变化
我们需要在文本改变或光标移动时触发滚动调整,通过UITextViewDelegate实现:
import UIKit class ViewController: UIViewController, UITextViewDelegate { @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var bodyTextView: UITextView! override func viewDidLoad() { super.viewDidLoad() // 基础配置 bodyTextView.isScrollEnabled = false bodyTextView.textContainerInset = UIEdgeInsets(top: 40, left: 0, bottom: 40, right: 0) bodyTextView.delegate = self // 代码布局时的约束示例(Storyboard布局可忽略) // bodyTextView.translatesAutoresizingMaskIntoConstraints = false // NSLayoutConstraint.activate([ // bodyTextView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), // bodyTextView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), // bodyTextView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), // bodyTextView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), // bodyTextView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor) // ]) } // 文本变化时触发滚动调整 func textViewDidChange(_ textView: UITextView) { adjustScrollToShowCaret() } // 光标移动(比如回车换行)时触发滚动调整 func textViewDidChangeSelection(_ textView: UITextView) { adjustScrollToShowCaret() } // 核心滚动调整方法 private func adjustScrollToShowCaret() { guard let selectedRange = bodyTextView.selectedTextRange else { return } // 获取光标所在的矩形区域 var caretRect = bodyTextView.caretRect(for: selectedRange.end) // 转换到UIScrollView的坐标系下 caretRect = bodyTextView.convert(caretRect, to: scrollView) // 把底部内边距加入滚动范围,确保光标下方留够空间 let bottomPadding = bodyTextView.textContainerInset.bottom caretRect.size.height += bottomPadding // 让光标区域滚动到可视范围内,使用动画保持原生手感 scrollView.scrollRectToVisible(caretRect, animated: true) } }
解决效果验证
- 回车换行立即滚动:通过
textViewDidChangeSelection监听光标移动,回车后光标跳到新行时会立刻触发滚动 - 保留底部内边距:在计算滚动区域时加入了
textContainerInset.bottom,滚动后光标下方会保留你设置的40pt空间 - 提前触发滚动:
scrollRectToVisible会自动把光标区域调整到可视区内,只要光标接近边缘就会滚动,不再需要触底才触发
这个方案完全模拟了原生UITextView的滚动逻辑,同时兼容UIScrollView里的其他UI组件一起滚动的需求。
内容的提问来源于stack exchange,提问作者Cheok Yan Cheng
相关产品推荐
相关产品推荐

