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

Ios 使用NSAttributedString在UITextView中为文本着色非常慢,ios,swift,multithreading,textview,nsattributedstring,Ios,Swift,Multithreading,Textview,Nsattributedstring,我在UITextView上制作了一个简单的代码查看器/编辑器,因此我想给一些关键字(变量、函数等)上色,以便像在IDE中一样轻松查看。我正在使用NSAttributedString来执行此操作,并在循环中使用函数apply(…)在范围内着色(见下文)。然而,当有很多单词需要着色时,它开始变得非常慢,并且干扰键盘(在模拟器上不是那么多,但在实际设备上非常慢)。我想我可以使用线程来解决这个问题,但是当我在DispatchQueue.global().async{…}中运行apply函数时,它根本不会

我在
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
}

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

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

一个可能的改进是实现
shouldChangeTextInRange
委托。然后,您可以从现有的属性字符串开始,然后仅处理正在更改的范围。您可能需要在两侧处理一点文本,但这比重新处理整个文本要高效得多

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

另一个需要考虑的问题是在后台扫描和创建属性字符串,但要使其可中断。每个文本更新您的取消和当前处理,然后重新开始。在用户停止输入足够长的时间以完成处理之前,不要使用新计算的属性文本更新文本视图

但我会利用仪器来分析代码。看看什么花的时间最多。是不是找到了文字?它正在创建属性字符串吗?是否经常设置文本视图的
attributedText
属性


<>你也可以考虑深入研究核心文本。也许
UITextView
不太适合您的任务。

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

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

一个可能的改进是实现
shouldChangeTextInRange
委托。然后,您可以从现有的属性字符串开始,然后仅处理正在更改的范围。您可能需要在两侧处理一点文本,但这比重新处理整个文本要高效得多

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

另一个需要考虑的问题是在后台扫描和创建属性字符串,但要使其可中断。每个文本更新您的取消和当前处理,然后重新开始。在用户停止输入足够长的时间以完成处理之前,不要使用新计算的属性文本更新文本视图

但我会利用仪器来分析代码。看看什么花的时间最多。是不是找到了文字?它正在创建属性字符串吗?是否经常设置文本视图的
attributedText
属性


<>你也可以考虑深入研究核心文本。也许
UITextView
不太适合您的任务。

我有一个日志功能,可以记录我所做的所有服务调用,并可以在日志中搜索特定字符串。我在文本字段中显示文本,并在搜索时突出显示文本。我在正则表达式中使用below func,它并不慢。希望对你有帮助

    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
    }
}

我有一个日志功能,在其中我记录我所做的所有服务调用,并可以在日志中搜索特定字符串。我在文本字段中显示文本,并在搜索时突出显示文本。我在正则表达式中使用below func,它并不慢。希望对你有帮助

    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
    }
}

谢谢你的建议!非常感谢您花时间回答如此详细的问题。我进行完整扫描的原因是,例如,如果您声明了一个var,那么它必须在任何地方对该var进行着色(如果该var是全局的,并且在前面的函数中调用,则可能在文本层次结构中的声明之前)。我肯定需要做一些更智能的东西来处理这个问题,而且
shouldChangeTextInRange
会很有用。我喜欢中断以前对函数调用的解决方案,但是由于某些原因,我无法在后台运行apply。您需要更新您的问题,并提供有关使用后台线程的具体细节。也许一个简化版本的代码,使用一个属性化的字符串复制您的问题,并在后台通过文本视图进行更新,这将是一个很好的补充问题。关注该问题,而不考虑本问题中讨论的性能问题。感谢您的建议!非常感谢您花时间回答如此详细的问题。我进行完整扫描的原因是,例如,如果您声明了一个var,那么它必须在任何地方(可能甚至在