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

如何让UIViewRepresentable中的UITextView随NSMutableAttributedString属性更新?

问题原因与解决方案

问题原因

核心矛盾在于SwiftUI对引用类型的变更检测逻辑,以及UITextView的内部工作机制:

  • SwiftUI的@Binding仅在引用本身发生变化时触发视图更新,不会检测引用对象的内部内容修改。
  • 初始状态下,将外部NSMutableAttributedString赋值给UITextView的attributedText时,UITextView会创建一份内部副本(因为attributedText是只读属性,设置时会复制内容到自身的textStorage),此时外部字符串和UITextView内部存储是两个独立实例。
  • 点击下划线按钮时,你修改的是外部字符串的内部属性,但引用未改变,SwiftUI不会触发updateUIView,UITextView自然不会更新显示。
  • 输入字符后,textViewDidChange将UITextView自身的textStorage赋值给外部@Binding变量,此时两者指向同一个实例。后续修改该实例的属性时,UITextView会直接响应内部存储的变化,所以UI能正常更新。

解决方案

推荐第二种方案,更贴合UIKit组件的原生工作机制:

方案1:强制更新引用触发重绘

每次修改后创建新的NSMutableAttributedString实例,让@Binding检测到引用变化:

@objc func underline() {
    guard selectedRange.length > 0 else { return }
    // 复制原内容并创建新实例
    let newString = NSMutableAttributedString(attributedString: innerMutableString)
    newString.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: selectedRange)
    innerMutableString = newString
}

方案2:直接操作UITextView的textStorage(推荐)

直接修改UITextView内部的存储,既保证UI实时更新,又同步到外部变量:

  1. 修改Coordinator,添加UITextView的弱引用:
class Coordinator: NSObject, UITextViewDelegate {
    @Binding var innerMutableString: NSMutableAttributedString
    @Binding var selectedRange: NSRange
    weak var textView: UITextView? // 新增弱引用

    init(innerMutableString: Binding<NSMutableAttributedString>, selectedRange: Binding<NSRange>, textView: UITextView?) {
        self._innerMutableString = innerMutableString
        self._selectedRange = selectedRange
        self.textView = textView
    }
    
    func textViewDidChange(_ textView: UITextView) {
        innerMutableString = textView.textStorage
    }
    
    func textViewDidChangeSelection(_ textView: UITextView) {
        selectedRange = textView.selectedRange
    }
    
    @objc func underline() {
        guard let textView = textView, selectedRange.length > 0 else { return }
        // 直接操作textStorage,UI实时更新
        textView.textStorage.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: selectedRange)
        // 同步到外部变量
        innerMutableString = textView.textStorage
    }
}
  1. 在makeUIView中给Coordinator绑定textView:
func makeUIView(context: Context) -> some UITextView {
    let textView = UITextView()
    textView.font = UIFont(name: "Helvetica", size: 30.0)
    textView.delegate = context.coordinator
    
    let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: textView.frame.size.width, height: 44))
    let underlineButton = UIBarButtonItem(
        image: UIImage(systemName: "underline"),
        style: .plain,
        target: context.coordinator,
        action: #selector(context.coordinator.underline))
    toolBar.items = [underlineButton]
    textView.inputAccessoryView = toolBar
    
    // 绑定textView到Coordinator
    context.coordinator.textView = textView
    return textView
}
  1. 优化updateUIView避免不必要的赋值:
func updateUIView(_ uiView: UIViewType, context: Context) {
    // 仅当内容不同时赋值,防止循环更新
    if !uiView.attributedText.isEqual(to: outerMutableString) {
        uiView.attributedText = outerMutableString
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 19:45:51