NSRangeException от String.subscript.getter - PullRequest
0 голосов
/ 12 октября 2019

Я написал этот mid метод в Extension.

func mid(fromIndex: Int, toIndex: Int) -> String {

    var _fromIndex = 0

    if( fromIndex >= self.count ) {
        return ""
    }
    else if( fromIndex > 0 ){
        _fromIndex = fromIndex
    }

    var _toIndex = 0

    if( toIndex >= self.count ) {
        _toIndex = self.count - 1
    }
    else if( toIndex > 0 ){
        _toIndex = toIndex
    }

    if( _fromIndex > _toIndex ){
        return ""
    }

    return String(self[self.index(self.startIndex, offsetBy: _fromIndex)...self.index(self.startIndex, offsetBy: _toIndex)])
}

и использую этот метод для извлечения удаленной пользователем строки.

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    defer{
        if text.isEmpty {
            let deletedText = textView.attributedText.string.mid(fromIndex: range.lowerBound, toIndex: range.upperBound)
        }
    }
}

Но иногда это вызывает исключение.

Fatal Exception: NSRangeException *** -[NSBigMutableString characterAtIndex:]: Index 672 out of bounds; string length 672 specialized String.subscript.getter

Я уже проверяю размер строки перед использованием String.subscript.getter.

if( toIndex >= self.count ) {
    _toIndex = self.count - 1
}

И текстовая операция должна выполняться в основном потоке. Почему я все еще получаю это исключение? Неужели я неправильно понял индекс строки в Swift?

UPDATE1

Согласно ответу @ Wattholm. Я использую этот способ, чтобы получить текст с NSRange из UITextView.

let contents: String = textView.text

if let _range = Range(range, in: contents) {
    let deletedText = String(contents[_range])
}
else {
    logHelper.w("Failed to get deletedText from NSRange, try UITextRange.")

    if let startPosition = textView.position(from: textView.beginningOfDocument, offset: range.location),
       let endPosition = textView.position(from: startPosition, offset: range.length),
       let textRange = textView.textRange(from: startPosition, to: endPosition), 
       let deletedText = textView.text(in: textRange) {

        // do something with deletedText
    }
    else {
        logHelper.w("Failed to get deletedText from UITextRange, try NSString.")

        let deletedText = (contents as NSString).mid(fromIndex: range.lowerBound, toIndex: range.upperBound)
    }
}

Но он все равно попадает во второй else и выбрасывает NSRangeException. Это означает, что длина и индекс по-прежнему не соответствуют друг другу.

UPDATE2

Пытался использовать NSString.substring, но все еще продолжаю получать это исключение.

let deletedText = (contents as NSString).substring(with: range)

Возможно, есть ошибка в UITextView?

Ответы [ 2 ]

2 голосов
/ 12 октября 2019

Я могу ошибаться, но я думаю, что проблема здесь связана с внутренними различиями между String и NSString или между Range и NSRange. Количество строк не всегда будет таким же, как длина NSString.

Возможно, существует более простой способ сделать это, но я попытался создать расширение для NSString так же, как вы его создали дляString, а затем приведите «contents» (которое, как я полагаю, является String; здесь, похоже, отсутствует некоторый код, так как «contents» нигде не был объявлен), чтобы оно работало правильно, так как textview предоставляет нам NSRange. :

extension NSString {
    func mid(fromIndex: Int, toIndex: Int) -> NSString {

        var _fromIndex = 0

        if( fromIndex >= self.length ) {
            return ""
        }
        else if( fromIndex > 0 ){
            _fromIndex = fromIndex
        }

        var _toIndex = 0

        if( toIndex >= self.length ) {
            _toIndex = self.length - 1
        }
        else if( toIndex > 0 ){
            _toIndex = toIndex
        }

        if( _fromIndex > _toIndex ){
            return ""
        }

        return NSString(string: self.substring(with: NSRange(location: _fromIndex, length: _toIndex - _fromIndex + 1)))
    }
}

И для просмотра текста:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    defer{
        if text.isEmpty {
            let deletedText = (contents as NSString).mid(fromIndex: range.lowerBound, toIndex: range.upperBound - 1)
        }
    }
}

Другой способ сделать это - преобразовать NSRange в Range, что, вероятно, является лучшим способом, особенно если вам требуетсячто количество символов будет точно таким же, как строка считает своих символов в Swift. Range: NSRange в функции textview можно преобразовать в Range следующим образом:

let swiftRange = Range (range, in: rangeText)

, где rangeText должен быть заменен свойством text, котороедиапазон относится к.

0 голосов
/ 26 октября 2019

Это версия swiftier , в которой используется надежное преобразование из NSRange в Range<String.Index> и наоборот.

extension String {

    func mid(fromIndex: Int, toIndex: Int) -> String {
        guard toIndex >= fromIndex else  { return "" }
        let nsRange = NSRange(location: fromIndex, length: toIndex + 1 - fromIndex)
        guard let range = Range(nsRange, in: self) else { return "" }
        return String(self[range])
    }
}

На самом деле расширение не требуетсякак вы уже получили NSRange, просто напишите

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
    defer{
        if text.isEmpty {
            let textViewString = textView.attributedText.string
            guard let swiftRange = Range(range, in: textViewString) else { return }
            let deletedText = String(textViewString[swiftRange])
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...