SwiftUI中基于NSViewRepresentable封装NSTextView:选中范围文本粗体样式重置问题
问题原因分析
你的问题主要来自两个方面:
updateNSView的不当实现
你当前的updateNSView每次都会调用nsView.textStorage?.setAttributedString(self.text),这会在text绑定值变化时完全覆盖NSTextView的文本内容,同时重置它的typingAttributes(输入时使用的默认样式)为初始的常规字重状态。所以当你设置完粗体后,后续输入会回到默认样式。未同步更新
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
相关产品推荐
相关产品推荐

