使用NSAttributedString在UITextView中着色文本非常慢

时间:2018-05-16 01:46:47

标签: ios swift multithreading textview nsattributedstring

我在UITextView之上创建了一个简单的代码查看器/编辑器,因此我想为一些关键字(变量,函数等)着色,以便它易于在IDE中查看。我使用NSAttributedString执行此操作并使用循环中的函数apply(...)在范围内着色(请参见下文)。然而,当有很多单词要着色时,它开始变得非常慢并且干扰键盘(在模拟器上没有那么多但在实际设备上它真的很慢)。我以为我可以使用线程来解决这个问题,但是当我在DispatchQueue.global().async {...}中运行apply函数时,它根本没有任何颜色。通常,如果需要在主线程中运行某些UI调用,它将打印出错误/崩溃,因此我可以找到添加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 :(得分:1)

这将是一些分析和一些建议,而不是完整的代码实现。

您当前的代码会完全重新扫描所有文本,并为用户在文本视图中键入的每个字符重新应用所有属性。显然这是非常低效的。

一种可能的改进是实施shouldChangeTextInRange代表。然后,您可以从现有的属性字符串开始,然后只处理要更改的范围。您可能需要在任何一方处理一些文本,但这比重新处理整个文本更有效。

你也许可以将两者结合起来。如果当前文本小于某个适当的大小,请执行完整扫描。达到临界尺寸后,进行部分更新。

另一个考虑因素是在后台进行所有扫描和创建属性字符串,但要使其可中断。每个文本都会更新您的取消和当前处理并重新开始。实际上,请不要使用新计算的属性文本更新文本视图,直到用户停止输入足够长的时间来完成处理。

但我会使用仪器并分析代码。看看它花了最多的时间。它找到了这些字吗?它是否创建了属性字符串?是否经常设置文本视图的attributedText属性?

您也可以考虑深入了解Core Text。也许UITextView并不适合你的任务。

答案 1 :(得分:1)

我有一个记录器功能,我可以记录我所做的所有服务调用,并可以搜索该日志中的特定字符串。我在文本字段中显示文本,并在搜索时突出显示文本。我使用下面的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
    }
}