Swift 4中如何让UITextView达到指定行数时开始滚动?
实现UITextView达到指定行数后开始滚动的方案
嘿,这个需求在聊天类APP里太常见了,我来给你一步步拆解实现方法:
核心思路
我们需要先算出5行文本对应的最大高度,然后在文本内容高度变化时,对比当前高度和最大高度:
- 未超过最大高度:保持输入框高度自适应,关闭滚动
- 超过最大高度:固定输入框高度到最大值,开启滚动,并且自动滚动到最新文本位置
步骤1:计算5行文本的最大高度
首先在你的InputAccessoryView类里添加两个属性,用来定义最大行数和对应的高度:
private let maxAllowedLines: CGFloat = 5 // 你想要的最大行数 private var maxTextViewHeight: CGFloat = 0 // 5行文本对应的总高度
然后在初始化方法里计算这个最大高度(要考虑字体行高和文本框的内边距):
override init(frame: CGRect) { super.init(frame: frame) // 初始化你的messageTextView、sendButton等控件... // 计算最大高度 calculateMaxTextViewHeight() // 添加contentSize观察者 messageTextView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil) } private func calculateMaxTextViewHeight() { guard let textFont = messageTextView.font else { return } // 单行文本高度 + 文本框上下内边距 let singleLineHeight = textFont.lineHeight let verticalInsets = messageTextView.textContainerInset.top + messageTextView.textContainerInset.bottom maxTextViewHeight = singleLineHeight * maxAllowedLines + verticalInsets }
步骤2:在contentSize变化时判断是否开启滚动
修改你的KVO观察者方法,根据当前内容高度和最大高度做逻辑处理:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard keyPath == "contentSize", let textView = object as? UITextView else { super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) return } let currentContentHeight = textView.contentSize.height if currentContentHeight > maxTextViewHeight { // 超过最大高度:开启滚动,固定输入框高度 messageTextView.isScrollEnabled = true // 触发高度重计算 self.invalidateIntrinsicContentSize() // 自动滚动到文本底部,避免输入的内容被隐藏 let bottomOffset = CGPoint(x: 0, y: textView.contentSize.height - textView.bounds.height) textView.setContentOffset(bottomOffset, animated: true) } else { // 未超过:关闭滚动,保持高度自适应 messageTextView.isScrollEnabled = false self.invalidateIntrinsicContentSize() } }
步骤3:修改intrinsicContentSize,限制最大高度
更新你的intrinsicContentSize计算逻辑,确保输入框高度不会超过我们设定的最大值:
override var intrinsicContentSize: CGSize { let sizeToFitIn = CGSize(width: messageTextView.bounds.size.width, height: .greatestFiniteMagnitude) let newSize = messageTextView.sizeThatFits(sizeToFitIn) // 取自适应高度和最大高度的最小值 let finalHeight = min(newSize.height, maxTextViewHeight) return CGSize(width: bounds.width, height: finalHeight) }
步骤4:记得移除观察者,避免内存泄漏
在InputAccessoryView的销毁方法里移除KVO观察者:
deinit { messageTextView.removeObserver(self, forKeyPath: "contentSize") }
额外注意点
- 如果你的APP支持动态字体调整(用户在系统设置里修改字体大小),记得监听字体变化事件,重新计算
maxTextViewHeight - 如果你的
messageTextView设置了textContainer.lineFragmentPadding,计算最大高度时也要把这个值考虑进去(不过sizeThatFits已经会自动处理这个,所以一般不用额外加)
内容的提问来源于stack exchange,提问作者Michael Hsu
相关产品推荐
相关产品推荐

