Окрашивание текста в UITextView с NSAttributedString очень медленно - PullRequest
0 голосов
/ 16 мая 2018

Я создаю простой просмотрщик кода / редактор поверх UITextView, и поэтому я хочу раскрасить некоторые ключевые слова (переменные, функции и т. Д.), Чтобы их было легко просматривать, как в IDE. Я использую NSAttributedString для этого и раскрашиваю в диапазоне, используя функции apply(...) в цикле (см. Ниже). Однако, когда есть много слов для раскраски, он начинает становиться очень медленным и глушить клавиатуру (не столько на симуляторе, сколько на медленном устройстве на реальном устройстве). Я думал, что мог бы использовать многопоточность для решения этой проблемы, но когда я запускаю функцию apply в DispatchQueue.global().async {...}, она вообще ничего не окрашивает. Обычно, если какой-либо вызов пользовательского интерфейса должен выполняться в главном потоке, он выведет сообщение об ошибке / сбое, и поэтому я могу найти, куда добавить DispatchQueue.main.sync {...}, и я пробовал в разных местах, но он все еще не работает. Любые предложения о том, как я мог бы решить эту проблему?


Обновление звонка

func textViewDidChange(_ textView: UITextView) {
    updateLineText()
}

Функция обновления

var wordToColor = [String:UIColor]()

func updateLineText() {

    var newText = NSMutableAttributedString(string: content)

    // some values are added to wordToColor here dynamically. This is quite fast and can be done asynchronously.

    // when this is run asynchronously it doesn't color at all...
    for word in wordToColor.keys {
        newText = apply(string: newText, word: word)
    }

    textView.attributedText = newText
}

Применить функции

func apply (string: NSMutableAttributedString, word: String) -> NSMutableAttributedString {
    let range = (string.string as NSString).range(of: word)
    return apply(string: string, word: word, range: range, last: range)
}

func apply (string: NSMutableAttributedString, word: String, range: NSRange, last: NSRange) -> NSMutableAttributedString {
    if range.location != NSNotFound {

        if (rangeCheck(range: range)) {
            string.addAttribute(NSAttributedStringKey.foregroundColor, value: wordToColor[word], range: range)
            if (range.lowerBound != 0) {
                let index0 = content.index(content.startIndex, offsetBy: range.lowerBound-1)
                if (content[index0] == ".") {
                    string.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.purple, range: range)
                }
            }

        }

        let start = last.location + last.length
        let end = string.string.count - start
        let stringRange = NSRange(location: start, length: end)
        let newRange = (string.string as NSString).range(of: word, options: [], range: stringRange)
        apply(string: string, word: word, range: newRange, last: range)
    }
    return string
}

Ответы [ 2 ]

0 голосов
/ 16 мая 2018

У меня есть функция ведения журнала, в которую я записываю все сделанные мной сервисные вызовы и могу искать определенную строку в этих журналах.Я отображаю текст в текстовом поле и выделяю текст при поиске.Я использую ниже func с Regex, и это не медленно.Надеюсь, это поможет вам.

    func searchText(searchString: String) {
    guard let baseString = loggerTextView.text else {
        return
    }
    let attributed = NSMutableAttributedString(string: baseString)
    do {
        let regex = try! NSRegularExpression(pattern: searchString,options: .caseInsensitive)
        for match in regex.matches(in: baseString, options: NSRegularExpression.MatchingOptions(), range: NSRange(location: 0, length: baseString.count)) as [NSTextCheckingResult] {
            attributed.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellow, range: match.range)
        }
        attributed.addAttribute(NSFontAttributeName, value: UIFont.regularFont(ofSize: 14.0), range: NSRange(location: 0, length: attributed.string.count))
        self.loggerTextView.attributedText = attributed
    }
}
0 голосов
/ 16 мая 2018

Это будет скорее некоторый анализ и некоторые предложения, а не полная реализация кода.

Ваш текущий код полностью повторно сканирует весь текст и повторно применяет все атрибуты для каждого символа, который пользователь вводит в текстовом представлении. Очевидно, это очень неэффективно.

Одним из возможных улучшений было бы внедрение делегата shouldChangeTextInRange. Затем вы можете начать с существующей приписанной строки, а затем обработать только изменяемый диапазон. Возможно, вам придется обработать немного текста с обеих сторон, но это будет гораздо эффективнее, чем повторная обработка всего текста.

Вы могли бы объединить два, возможно. Если текущий текст меньше подходящего размера, выполните полное сканирование. Как только он достигнет критического размера, выполните частичное обновление.

Еще одним соображением является все сканирование и создание строки атрибута в фоновом режиме, но сделать ее прерываемой. Каждый текст обновляет вашу отмену и текущую обработку и начинается снова. На самом деле не обновляйте текстовое представление вновь рассчитанным атрибутивным текстом, пока пользователь не перестанет набирать текст достаточно долго для завершения обработки.

Но я бы использовал Инструменты и профилировал код. Посмотрите, что это занимает больше всего времени. Это найти слова? Это создает атрибутивную строку? Постоянно ли устанавливается свойство attributedText текстового представления?

Вы также можете рассмотреть возможность углубиться в основной текст. Возможно, UITextView просто не подходит для вашей задачи.

...