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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:22:47