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

UITextView点击回车时textViewDidChangeSelection调用两次致光标跳转问题

UITextView点击回车后光标跳转到末尾问题修复

问题场景

为了监听用户点击回车的操作,实现了UITextView的如下代理方法:

// 文本内容即将变更时调用
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if (text == "\n") {
        // 用户点击回车时的自定义逻辑
        return true
    }
    // 其他逻辑
    return true
}

// 选中区域变更时调用
func textViewDidChangeSelection(_ textView: UITextView) {
    // 自定义逻辑
}

上述实现中,用户点击回车键时textViewDidChangeSelection会被触发两次。第二次触发时,textView.selectedRange会被系统自动修改为指向文本视图的最后一个字符,最终表现为:用户在文本中间任意一行的末尾点击回车插入新行后,光标会直接跳转至整个文本的末尾,而非停留在新插入行的位置。

复现步骤

  1. 输入多行文本,示例如下:
Line 1.
Bla bla bla
line 3
  1. 将光标移动到第一行的末尾位置
  2. 点击回车键
    异常表现:新行可以被插入到正确位置,但光标会直接跳转至整个文本视图的末尾。

注意事项

  • 现有公开讨论中关于textViewDidChangeSelection被多次调用的通用解决方案,无法修复该场景下的光标跳转问题
  • 最小复现项目中仅需查看EditorTextView.swift文件即可定位问题

解决方案

该问题是UITextView的系统代理异常行为导致的,第一次textViewDidChangeSelection触发是插入换行符后光标正常移动到新行的位置,第二次触发是系统内部的无效回调,覆盖了正确的光标位置,可通过两种方案修复:

方案1:拦截异常回调(兼容系统默认文本处理逻辑)

首先在自定义TextView类中新增两个状态属性:

// 标记是否正在处理回车事件
private var isHandlingReturn = false
// 存储回车插入后正确的光标位置
private var expectedSelectedRange: NSRange?

修改shouldChangeTextIn代理方法:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if text == "\n" {
        isHandlingReturn = true
        // 计算插入换行后的正确光标位置:原光标位置+1(换行符长度)
        expectedSelectedRange = NSRange(location: range.location + 1, length: 0)
        // 你的自定义回车逻辑
        return true
    }
    // 其他逻辑
    return true
}

修改textViewDidChangeSelection代理方法,拦截异常回调:

func textViewDidChangeSelection(_ textView: UITextView) {
    if isHandlingReturn, let expectedRange = expectedSelectedRange {
        // 强制修正为正确的光标位置
        if textView.selectedRange != expectedRange {
            textView.selectedRange = expectedRange
            // 重置状态标记
            isHandlingReturn = false
            expectedSelectedRange = nil
        }
        return
    }
    // 你的原有选中变更逻辑
}

方案2:手动处理文本插入(彻底避免多余回调)

如果你的业务不需要依赖系统默认的文本处理逻辑,可以手动完成换行插入和光标设置,完全跳过系统的文本变更流程,从根源避免二次回调:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    if text == "\n" {
        // 手动插入换行符
        let mutableText = textView.text as NSString
        textView.text = mutableText.replacingCharacters(in: range, with: "\n")
        // 手动设置正确的光标位置
        textView.selectedRange = NSRange(location: range.location + 1, length: 0)
        // 你的自定义回车逻辑
        // 返回false,禁止系统处理本次文本变更
        return false
    }
    return true
}

内容的提问来源于stack exchange,提问作者Sami Almalki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.11 21:48:03