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

SwiftUI中基于NSViewRepresentable封装NSTextView:选中范围文本粗体样式重置问题

问题原因分析

你的问题主要来自两个方面:

  1. updateNSView的不当实现
    你当前的updateNSView每次都会调用nsView.textStorage?.setAttributedString(self.text),这会在text绑定值变化时完全覆盖NSTextView的文本内容,同时重置它的typingAttributes(输入时使用的默认样式)为初始的常规字重状态。所以当你设置完粗体后,后续输入会回到默认样式。

  2. 未同步更新typingAttributes
    当你给选中文本添加粗体后,NSTextView的输入样式(typingAttributes)并没有被更新为粗体。这意味着光标所在位置的后续输入会使用默认的常规样式,而不是继承已设置的粗体属性。

修复方案

下面是修改后的完整代码,我会标注关键改动:

import SwiftUI
struct TextView: NSViewRepresentable {
    @Binding var text: NSAttributedString
    @Binding var range: NSRange
    // 添加绑定同步当前输入样式,让后续输入能继承对应样式
    @Binding var currentTypingAttributes: [NSAttributedString.Key: Any]

    class Coordinator: NSObject, NSTextViewDelegate {
        var control: TextView

        init (_ control: TextView) {
            self.control = control
        }

        func textDidChange(_ notification: Notification) {
            guard let view = notification.object as? NSTextView else { return }
            // 仅当文本真的变化时更新绑定,避免循环触发更新
            if view.attributedString() != control.text {
                control.text = view.attributedString()
            }
            // 同步当前输入样式到绑定变量
            control.currentTypingAttributes = view.typingAttributes
        }

        func textViewDidChangeSelection(_ notification: Notification) {
            guard let view = notification.object as? NSTextView else { return }
            DispatchQueue.main.async {
                self.control.range = view.selectedRange()
                // 选中范围变化时,自动获取当前位置的样式作为输入样式
                let attributes = view.textStorage?.attributes(at: view.selectedRange().location, effectiveRange: nil) ?? [:]
                self.control.currentTypingAttributes = attributes
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    func makeNSView(context: Context) -> NSTextView {
        let view = NSTextView()
        view.allowsUndo = true
        view.isEditable = true
        view.isSelectable = true
        view.isRichText = true
        view.delegate = context.coordinator
        // 初始化输入样式
        view.typingAttributes = currentTypingAttributes
        return view
    }

    func updateNSView(_ nsView: NSTextView, context: Context) {
        // 仅当外部文本和视图内文本不一致时更新,避免覆盖用户输入状态
        if nsView.attributedString() != self.text {
            nsView.textStorage?.setAttributedString(self.text)
        }
        // 同步输入样式,确保外部修改后视图能应用正确的输入规则
        if nsView.typingAttributes != self.currentTypingAttributes {
            nsView.typingAttributes = self.currentTypingAttributes
        }
    }
}

struct ContentView: View {
    @State var text:NSAttributedString = NSAttributedString("Je suis un texte youpi")
    @State var range:NSRange = NSRange()
    // 初始化输入样式为默认常规字体
    @State var currentTypingAttributes: [NSAttributedString.Key: Any] = [
        .font: NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .regular)
    ]

    var body: some View {
        VStack {
            Button {
                print("ok bold", range)
                let mutableStr = NSMutableAttributedString(attributedString: text)
                // 基于选中位置的现有字体转换为粗体,保证字号和原有文本一致
                let existingFont = mutableStr.attributes(at: range.location, effectiveRange: nil)[.font] as? NSFont ?? NSFont.systemFont(ofSize: NSFont.systemFontSize)
                let boldFont = NSFontManager.shared.convert(existingFont, toHaveTrait: .boldFontMask)
                let attributes = [NSAttributedString.Key.font: boldFont]
                mutableStr.addAttributes(attributes, range: range)
                self.text = mutableStr
                
                // 更新输入样式为粗体,确保后续输入保持粗体
                self.currentTypingAttributes = attributes
            } label: {
                Image(systemName: "bold")
            }
            .keyboardShortcut("b", modifiers: [.command])

            TextView(text: $text, range: $range, currentTypingAttributes: $currentTypingAttributes)
                .padding()
        }
    }
}
关键改动说明
  • 优化updateNSView逻辑:添加内容和样式的对比判断,只有当外部绑定值和视图内部状态不一致时才更新,避免每次绑定变化都重置视图的光标位置和输入样式。
  • 同步输入样式:新增currentTypingAttributes绑定,设置粗体后同步更新该值,让NSTextView后续输入自动使用粗体;同时选中范围变化时,自动继承当前位置的样式,保证输入连贯性。
  • 智能字体转换:不再硬编码字号,而是基于选中位置的现有字体转换为粗体,避免出现字号不匹配的问题。
  • 避免循环更新:在textDidChange中添加文本内容对比,仅当视图文本和绑定文本真的不同时才更新绑定,防止循环触发updateNSView。

这样修改后,你设置的粗体样式会稳定保持,后续输入也会继承当前的粗体属性,不会再被重置为常规字重。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 19:32:30