选中内容变化时通过NSViewRepresentable传递NSTextView属性至SwiftUI父视图
How to Sync NSTextView Selection Attributes with SwiftUI Parent View
Got it, let's fix this problem for you. The main hurdle here was not realizing you can grab the NSTextView instance directly from the selection change notification — no custom delegates or extra subscriptions needed. Here's how to make it work:
Updated Coordinator Implementation
class Coordinator: NSObject, NSTextViewDelegate { var parent: TextViewMacOS init(_ parent: TextViewMacOS) { self.parent = parent } func textViewDidChangeSelection(_ notification: Notification) { // Pull the active NSTextView from the notification's object guard let textView = notification.object as? NSTextView else { return } // Sync typing attributes directly to the SwiftUI binding parent.currentTypingAttributes = textView.typingAttributes // Get attributes at the current selection/cursor position let selectedRange = textView.selectedRange() if let attributes = textView.textStorage?.attributes(at: selectedRange.lowerBound, effectiveRange: nil) { parent.currentTextAttributes = attributes } } }
Key Details to Note
- Access the TextView from the Notification: The
notification.objectproperty is exactly theNSTextViewthat triggered the selection change. A safe downcast gives you full access to all its properties and methods. - Handle Both Selection & Cursor States: Whether the user has text selected or just a cursor, using
selectedRange.lowerBoundensures you fetch attributes at the active position. - Mixed Attributes Edge Case: If the selected text has mixed formatting (e.g., part bold, part italic), this code will grab attributes from the start of the selection. If you need to detect mixed attributes, you can compare the returned
effectiveRangeto the full selected range.
Full Updated TextViewMacOS Struct
Here's the complete implementation with basic NSTextView setup included:
import SwiftUI struct TextViewMacOS: NSViewRepresentable { @Binding var currentTypingAttributes: [NSAttributedString.Key : Any] @Binding var currentTextAttributes: [NSAttributedString.Key : Any] class Coordinator: NSObject, NSTextViewDelegate { var parent: TextViewMacOS init(_ parent: TextViewMacOS) { self.parent = parent } func textViewDidChangeSelection(_ notification: Notification) { guard let textView = notification.object as? NSTextView else { return } parent.currentTypingAttributes = textView.typingAttributes let selectedRange = textView.selectedRange() if let attributes = textView.textStorage?.attributes(at: selectedRange.lowerBound, effectiveRange: nil) { parent.currentTextAttributes = attributes } } } func makeCoordinator() -> Coordinator { Coordinator(self) } public func makeNSView(context: Context) -> NSTextView { let textView = NSTextView() textView.delegate = context.coordinator textView.typingAttributes = currentTypingAttributes // Set initial attributes return textView } public func updateNSView(_ textView: NSTextView, context: Context) { // Sync SwiftUI attributes to NSTextView (avoid loops by checking for changes) if textView.typingAttributes != currentTypingAttributes { textView.typingAttributes = currentTypingAttributes } } }
Why This Approach Works
You don't need to reinvent the wheel here. AppKit's standard NSTextViewDelegate pattern provides everything you need via the notification object. This keeps your code clean, aligned with platform best practices, and avoids unnecessary complexity like custom delegates.
内容的提问来源于stack exchange,提问作者santi.gs
相关产品推荐
相关产品推荐

