You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何实现UITextView文本的逐行高亮?

解决UITextView逐行高亮的问题

我看了你的代码,问题主要出在这几个地方:

  • 循环里的DispatchQueue.main.asyncAfter会让所有延迟任务几乎同时触发(循环执行太快,每个任务都只延迟0.5秒),而且每次都把newStr设置成单个行的属性字符串,还直接把attArr[m]转成普通字符串——这就是你看到NSBackgroundColor 1101的原因,属性字符串直接转字符串会输出它的内部属性信息。
  • 你在设置延迟任务后立刻移除了高亮属性,等延迟任务执行时,那行的高亮已经被删掉了,自然看不到效果。
  • 没有维护整个文本的完整属性,每次只替换成单行,导致文本显示混乱。

下面是修正后的实现思路和代码:

正确的实现步骤

  1. 先把完整文本转换成全局的可变属性字符串,这样我们可以持续修改它的属性而不用每次重新拼接。
  2. 计算每一行在整个字符串中的实际范围(要注意换行符的位置,避免把换行符也高亮)。
  3. 用串行的延迟操作(递归调用+延迟),依次处理每一行:先取消上一行的高亮,再给当前行添加高亮,最后更新UITextView的内容。

完整代码示例

class ViewController: UIViewController {
    @IBOutlet weak var textView: UITextView!
    
    // 全局保存完整的可变属性字符串,方便持续修改
    private var fullAttributedText: NSMutableAttributedString!
    // 记录上一次高亮的行范围,用于取消高亮
    private var lastHighlightedRange: NSRange?
    // 当前处理的行索引
    private var currentLineIndex = 0
    // 存储每一行在完整字符串中的范围
    private var lineRanges: [NSRange] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTextViewContent()
    }
    
    private func setupTextViewContent() {
        let str = "This is\n some placeholder\n text\nwith newlines."
        fullAttributedText = NSMutableAttributedString(string: str)
        textView.attributedText = fullAttributedText
        
        // 计算每一行的实际范围(不包含换行符)
        let lines = str.components(separatedBy: "\n")
        var currentLocation = 0
        for line in lines {
            let lineLength = line.count
            lineRanges.append(NSMakeRange(currentLocation, lineLength))
            // 跳过换行符的位置(最后一行没有换行符)
            currentLocation += lineLength + (line == lines.last ? 0 : 1)
        }
    }
    
    // 调用这个方法开始逐行高亮
    func startLineByLineHighlight() {
        currentLineIndex = 0
        lastHighlightedRange = nil
        highlightNextLine()
    }
    
    private func highlightNextLine() {
        // 所有行处理完,取消最后一行的高亮
        guard currentLineIndex < lineRanges.count else {
            if let range = lastHighlightedRange {
                fullAttributedText.removeAttribute(.backgroundColor, range: range)
                textView.attributedText = fullAttributedText
            }
            return
        }
        
        // 1. 取消上一行的高亮
        if let lastRange = lastHighlightedRange {
            fullAttributedText.removeAttribute(.backgroundColor, range: lastRange)
        }
        
        // 2. 高亮当前行
        let currentRange = lineRanges[currentLineIndex]
        fullAttributedText.addAttribute(.backgroundColor, value: UIColor.yellow, range: currentRange)
        lastHighlightedRange = currentRange
        
        // 3. 更新UITextView显示
        textView.attributedText = fullAttributedText
        
        // 4. 延迟0.5秒处理下一行
        currentLineIndex += 1
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            self.highlightNextLine()
        }
    }
}

代码说明

  • 在setupTextViewContent里提前计算好每一行的NSRange,确保后续能精准定位要高亮的文本。
  • startLineByLineHighlight是触发高亮的入口方法,调用它就开始逐行展示效果。
  • highlightNextLine是核心逻辑:先清除上一行的高亮,再给当前行添加高亮,更新文本后递归调用自己处理下一行,直到所有行处理完毕。
  • 用递归+延迟的方式保证高亮操作是串行执行的,不会出现所有行同时高亮的问题。

这样就能实现你想要的逐行高亮、自动切换的效果,也不会再出现奇怪的属性字符串输出了。

内容的提问来源于stack exchange,提问作者electronicwall

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:13:05