Получить диапазон из символов символов Юникода. Быстрый

Я использую функцию textView(_: shouldChangeTextIn: replacementText:) для изменения входных данных в зависимости от ситуации. Я использую диапазон, но не могу получить диапазон 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. После разговора с Мартином я обнаружил и сообщил одну деталь: эта проблема возникает только с клавиатурой Google, а с клавиатурой по умолчанию все работает должным образом.

Первоначальная строка, которая у меня была, была "( ͡° ͜ʖ ͡????????°)༼ つ ͡° ͜ʖ ͡????° ༽つ”, эта строка используется для примера. Если я начну удалять эту строку слева направо, я получаю сбой приложения, Мартин попросил показать последние данные в консоли до сбоя приложения, последняя печать перед сбоем textView" "( ͡° ͜ʖ ͡????????°)༼ つ ͡° ͜ʖ ͡????" "range" {27, 1}


person Alexander Khitev    schedule 06.06.2018    source источник
comment
Был бы полезен автономный воспроизводимый пример.   -  person Martin R    schedule 07.06.2018
comment
Вы должны искать диапазон в textView.text, а не в identityString.   -  person Martin R    schedule 07.06.2018
comment
@MartinR Я обновил свой пост. Я ищу в textView.text, но все еще получаю сбой приложения   -  person Alexander Khitev    schedule 07.06.2018
comment
Можете ли вы сделать его самостоятельным примером, который не требует загрузки проекта и некоторого взаимодействия с пользовательским интерфейсом? Что-то вроде пусть textViewText = ..., пусть replaceText = ..., пусть диапазон = ..., ...????   -  person Martin R    schedule 07.06.2018
comment
Не могу воспроизвести. Я запустил ваш код с let newRange = Range(range, in: textView.text)! и заданной начальной строкой и смог удалить все символы справа без сбоев.   -  person Martin R    schedule 07.06.2018
comment
@MartinR Да, извините, пожалуйста, еще одну деталь. Удаляю с помощью гугл-клавиатуры, но с ней не работает как положено. Еще раз извините. Вот результат с клавиатурой Google, но когда я переключаюсь на клавиатуру по умолчанию, все работает как положено. take.ms/1NnPU   -  person Alexander Khitev    schedule 07.06.2018
comment
Давайте продолжим обсуждение в чате.   -  person Martin R    schedule 07.06.2018


Ответы (1)


Как выяснилось в ходе обсуждения:

  • ОП использует клавиатуру Google,
  • метод делегата текстового представления вызывается с

    textView.text = "( ͡° ͜ʖ ͡????????°)༼ つ ͡° ͜ʖ ͡????"
    range = { 27, 1 }
    
  • а потом

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

    возвращает nil.

Причина в том, что диапазон указывает на «середину» символа ????, который хранится как суррогатная пара UTF-16. Вот упрощенный автономный пример:

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

Мне это кажется ошибкой (в клавиатуре 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 ????!
person Martin R    schedule 07.06.2018