问题描述
需求简述:需要在文本输入框的光标所在位置插入字符串,要求插入之后光标停留在插入的字符串之后。
初始实现:
// 需要插入的文字
let addText = "new"
var newText = textView.text
// 获取光标位置
let range = textView.selectedRange
// 获取该位置在字符串中的索引
let index = str.index(newText.startIndex, offset: range.location)
// 将文字插入到原字符串中
newText.insert(addText, atIndex: index)
// 将新文本更新到文本框
textView.text = newText
遇到的问题:
- 通过
UITextView
的selectedRange
,方法可以获取光标位置。但是如果字符串中存在 emoji,系统方法获取的光标位置会出现偏差,导致和String
的count
属性对不上。 - 通过
textView.text = newText
的方式更新文本框内容时,光标会直接跳到最后。
问题产生原因
因为 UITextView
的 selectedRange
属性是 NSRange
类型,它的计算字符取值和 Objc 语言 NSString
的 length
属性是一致的,是基于 UTF-16 的长度。通过 Swift 的 String
的 count
属性返回的只是 Unicode 字符个数。
于是,一个英文、中文字符 UITextView
的 selectedRange
属性而言占位是 1,而一个emoji 对于 UITextView
的 selectedRange
属性而言,占位可能在 2-4 之间。因此导致和 String
的 count
属性对不上。
解决方案
在 Swift 中通过 utf16.count
可以获取到 String
或者 Character
类型的对象基于 UTF-16 的长度。
/// 获取换算后的光标位置
/// - Parameters:
/// - location: 系统 API 获取的光标位置
/// - text: 输入框原文本
/// - Returns: 换算后的光标位置
func getRealLocation(location: Int, text: String) -> Int {
guard location != 0 else {
return location
}
var tempLocation = 0, index = 0
for (i, ch) in editText.enumerated() {
// ch.utf16.count 可以获取所占用字符数量
tempLocation += ch.utf16.count
if tempLocation == location {
index = i + 1
break
}
}
return index
}
通过设置 UITextView
的 selectedRange
属性可以改变光标的位置,可以参考下面代码。
let location = range.location + (newText.count - textView.text.count)
// 将新文本更新到文本框
textView.text = newTextv
// 将光标设置在插入的字符串之后
textView.selectedRange = NSRange(location: location, length: 0)
最终代码:
// 需要插入的文字
let addText = "new"
var newText = textView.text
// 获取光标位置
let range = textView.selectedRange
let location = getRealLocation(location: range.location, text: newText)
// 获取该位置在字符串中的索引
let index = str.index(newText.startIndex, offset: location)
// 将文字插入到原字符串中
newText.insert(addText, atIndex: index)
textView.isScrollEnabled = false
let location = range.location + (newText.count - textView.text.count)
// 将新文本更新到文本框
textView.text = newTextv
textView.selectedRange = NSRange(location: location, length: 0)
textView.isScrollEnabled = true
参考
- https://blog.csdn.net/wangooo/article/details/108817258
- https://www.jianshu.com/p/027d28069d07
- https://a1049145827.github.io/2019/04/23/Swift-how-to-correctly-get-the-length-of-a-string