从Unicode字符符号获取范围。Swift

3

我使用textView(_: shouldChangeTextIn: replacementText:)函数来根据情况更改输入数据。我使用range,但是在使用unicode字符符号(例如( ͡° ͜ʖ ͡°))时无法获取Swift范围。请告诉我如何完成它?

 func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

        let maxLenthNotReached = textView.text.count + (text.count - range.length) <= maxTextLength

        if maxLenthNotReached {
            guard let newRange = Range(range, in: identityString) else { return false }
            identityString = identityString.replacingCharacters(in: newRange, with: text)
        }

        return maxLenthNotReached
    }

示例项目

应用程序崩溃示例 http://take.ms/ojIJq

更新:我更改了此方法,但在删除时仍然出现崩溃

"entering data" ""
"testString" "༼ つ ͡° ͜ʖ ͡° ༽つ( ͡° ͜ʖ ͡"
"entering data" ""

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    debugPrint("textView.text", textView.text)
    testString = textView.text.replacingCharacters(in: Range(range, in: textView.text)!, with: text)//
    debugPrint("testString", testString)
    return true
}
更新 1:我在textView中输入了这些字符。
( ͡° ͜ʖ ͡°)༼ つ ͡° ͜ʖ ͡° ༽つ

在删除了最右边的三个符号 ° ༽つ 后,我开始从右向左删除字符,然后车子表情就被删除了,但是我无法确定范围,因为我设置了保护措施以防止应用程序崩溃。如果我移除保护措施,当然会导致应用崩溃。

完整代码:

class ViewController: UIViewController {

    // MARK: - IBOutlets
    @IBOutlet private weak var textView: UITextView! {
        didSet {
            textView.delegate = self
            textView.text = "( ͡° ͜ʖ ͡°)༼ つ ͡° ͜ʖ ͡° ༽つ"
        }
    }

    // MARK: - Properties

    private var testString = ""

}


extension ViewController: UITextViewDelegate {

    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        guard let newRange = Range(range, in: textView.text) else {
            return false
        }
        testString = textView.text.replacingCharacters(in: newRange, with: text)
        return true
    }

}
更新2:在与Martin交谈后,我发现并提供了一个细节,即这个问题只会在使用Google键盘时出现,而在默认键盘下,一切正常。

我原本的文本是"( ͡° ͜ʖ ͡°)༼ つ ͡° ͜ʖ ͡° ༽つ”,这个文本只是用来举例说明。如果我从左向右开始删除这个文本,应用程序就会崩溃。Martin要求在应用程序崩溃之前在控制台中显示最新数据,崩溃前的最后一次打印是textView" "( ͡° ͜ʖ ͡°)༼ つ ͡° ͜ʖ ͡" "range" {27, 1}


一个自包含的可重现的示例会很有帮助。 - Martin R
你应该在textView.text中搜索范围,而不是identityString。 - Martin R
你能否将它制作成一个自包含的示例,而不需要下载项目和进行一些UI交互吗?就像这样:let textViewText = "...", let replacementText = "...", let range = ... , ... - Martin R
@MartinR 是的,抱歉,请问还有一个细节。我使用谷歌键盘删除,但它并没有按预期工作。再次抱歉。这是使用谷歌键盘的结果,但当我切换到默认键盘时,一切都按预期工作。http://take.ms/1NnPU - Alexander Khitev
让我们在聊天中继续这个讨论 - Martin R
显示剩余2条评论
1个回答

3

在讨论中发现:

  • OP is using the Google keyboard,
  • the text view delegate method is called with

    textView.text = "( ͡° ͜ʖ ͡°)༼ つ ͡° ͜ʖ ͡"
    range = { 27, 1 }
    
  • and then

    let newRange = Range(range, in: textView.text)
    

    returns nil.

这是因为范围指向存储为UTF-16代理对的字符的“中间”部分。以下是一个简化的自包含示例:

let text = "Hello !"
let range = NSRange(location: 7, length: 1)
let newRange = Range(range, in: text)
print(newRange as Any) // nil   

这看起来像是一个bug(在Google键盘中?),但有可能有一个解决方法。

“诀窍”是确定“组合字符序列”的最接近的周围范围,下面是如何做到这一点 (参见从任何UTF-16偏移量找到对应的String.Index,该索引位于字符边界上):

extension String {
    func safeRange(from nsRange: NSRange) -> Range<String.Index>? {
        guard nsRange.location >= 0 && nsRange.location <= utf16.count else { return nil }
        guard nsRange.length >= 0 && nsRange.location + nsRange.length <= utf16.count else { return nil }
        let from = String.Index(encodedOffset: nsRange.location)
        let to = String.Index(encodedOffset: nsRange.location + nsRange.length)
        return rangeOfComposedCharacterSequences(for: from..<to)
    }
}

现在
let newRange = textView.text.safeRange(from: range)

返回一个包含整个字符的String范围。 在我们简化的例子中:

let text = "Hello !"
let range = NSRange(location: 7, length: 1)
let newRange = text.safeRange(from: range)
print(newRange as Any) // Optional(Range(...))   
print(text.replacingCharacters(in: newRange!, with: "")) // Hello !

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接