Access underlying NSView instances, inline with your SwiftUI views
NSViewProxy
NSViewProxy
, its associated structs, and extensions on SwiftUI.View
allow you to access the NSView
and/or NSWindow
/window members (e.g. NSToolbar
, NSTabBar
) in which a SwiftUI View
will draw.
This is an improvement upon other accessor/proxy methods which rely on using DispatchQueue.main.async(execute:)
to modify content after the first draw cycle.
NSViewProxyView
executes any logic given in NSViewProxy
callbacks prior to the first draw of modified SwiftUI View
s — effectively eliminating any occurrences of FOUC (flash of unstyled content).
Example App
Check out the repo for NSViewProxyExample
Usage
Simply chain the modifier onto the view whose context you wish to acquire:
import SwiftUI;
import AppKit;
import NSViewProxy;
// ...
TextEditor(text: self.$output)
.font(.body.monospaced())
.proxy(as: NSTextView.self, using: { nsTextView in
/// # NSTextView
///
/// Modify things like the insertion point color...
///
nsTextView.insertionPointColor = .systemPink;
///
/// ...or add a custom text highlight color.
///
let attrString = NSMutableAttributedString(" ");
attrString.addAttributes(
nsTextView.selectedTextAttributes,
range: NSMakeRange(0, 1));
attrString.addAttributes(
[NSAttributedString.Key.backgroundColor: NSColor.systemPurple],
range: NSMakeRange(0, 1))
nsTextView.selectedTextAttributes = attrString.attributes(
at: 0, effectiveRange: nil);
})
// ...
NSViewProxy
offers a range of different ways to access NSView
instances:
Generic Access to Proxied SwiftUI View
Access the SwiftUI View
itself without any typecasting.
Signature
.proxy(using: (NSView) -> Void)
Example
TextEditor(text: $text)
.proxy{ nsView in
print(nsView.frame);
}
Typecast Access to Proxied SwiftUI View
Access the SwiftUI View
itself — typecasting to the given type (e.g. NSButton.self
).
Signature
.proxy<T: NSView>(as: T.Type, using: (T) -> Void)
Example
TextEditor(text: $text)
.proxy(as: NSTextView.self, using: { nsTextView in
// Upcasting give us access to the `string` property.
print(nsTextView.string);
})
Related NSView
of Proxied SwiftUI View
Access an ancestor or descendant of the SwiftUI View.
Signature
`.proxy<T: NSView>(to: ViewRelationship<T>, using: (T) -> Void)`
Example (Typecast Descendant)
TextEditor(text: $text)
.proxy(
to: .closest(.descendant, representing: NSScrollView.self),
using: { nsScrollView in
/// We can access the NSScrollView that's wrapped in
/// `TextEditor`.
///
nsScrollView.scrollToEndOfDocument(nil);
})
Example (Generic Ancestor by Regex)
TextEditor(text: $text)
.proxy(
to: .ancestor(like: /NSTabBar/),
using: { nsTabBar in
/// We can access the undocumented `NSTabBar`,
/// but you'll need a header file to use it...
///
/// ... or you can do something like this:
///
guard let buttons = (nsTextView as AnyObject)
.value(forKey: "tabButtons") as? Array<NSButton>
else { return }
print(buttons);
})
Example (Generic Ancestor by Conditional)
TextEditor(text: $text)
.proxy(
to: .descendant(passing: { nsView in
guard let nsView as? NSClipView else {
return false;
}
return true;
}), using: { nsClipView in
/// I don't have anything interesting to
/// say about `NSClipView`, please keep
/// reading.
///
print(nsClipView);
})
Any Element in NSWindow
NSViewProxy
pre-defined utilities to access common elements in your SwiftUI app including:
Utility | Description |
---|---|
.window |
The NSWindow in which the proxied SwiftUI View will draw. |
.titlebarContainer |
The titlebar container of the NSWindow containing the proxied SwiftUI View . |
.titlebar |
The titlebar of the NSWindow containing the proxied SwiftUI View (yes, there’s a difference). |
.toolbar |
The toolbar of the NSWindow containing the proxied SwiftUI View . |
.contentView |
The outermost view of the NSWindow containing the proxied SwiftUI View before hitting NSThemeFrame . |
.tab |
The current tab of the NSWindow containing the proxied SwiftUI View . |
.tabBar |
The tab bar of the NSWindow containing the proxied SwiftUI View (undocumented). |
Signature
.proxy<T: NSObject>(to: GlobalElement<T>, using: (T) -> Void)
Example
TextEditor(text: $text)
.proxy(to: .toolbar, using: { nsToolbar in
/// We can customize the window's toolbar by
/// adding or removing our own items.
///
nsToolbar.removeItem(at: 0);
})
Contributing
If you experience an issue, please raise one or feel free to open a PR. I can usually be reached via DM on Twitter as @stephancasas, so please feel free to follow or get in touch if you’d like to see more of my work.
License
MIT