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会被系统自动修改为指向文本视图的最后一个字符,最终表现为:用户在文本中间任意一行的末尾点击回车插入新行后,光标会直接跳转至整个文本的末尾,而非停留在新插入行的位置。
复现步骤
- 输入多行文本,示例如下:
Line 1. Bla bla bla line 3
- 将光标移动到第一行的末尾位置
- 点击回车键
异常表现:新行可以被插入到正确位置,但光标会直接跳转至整个文本视图的末尾。
注意事项
- 现有公开讨论中关于
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
相关产品推荐
相关产品推荐

