Aha, in that case you will ultimately need to use an
NSVisualEffectView
, but that's actually quite easy to wrap, since there aren't many properties to think about, there's no real ‘content’ as such, and the values for just about everything can be set in the
updateNSView(_:context:)
method, which lends itself well to hooking its properties into the environment.
Here's what I was able to knock together just now for an environment-supporting
VisualEffectView
:
struct VisualEffectMaterialKey: EnvironmentKey {
typealias Value = NSVisualEffectView.Material?
static var defaultValue: Value = nil
}
struct VisualEffectBlendingKey: EnvironmentKey {
typealias Value = NSVisualEffectView.BlendingMode?
static var defaultValue: Value = nil
}
struct VisualEffectEmphasizedKey: EnvironmentKey {
typealias Value = Bool?
static var defaultValue: Bool? = nil
}
extension EnvironmentValues {
var visualEffectMaterial: NSVisualEffectView.Material? {
get { self[VisualEffectMaterialKey.self] }
set { self[VisualEffectMaterialKey.self] = newValue }
}
var visualEffectBlending: NSVisualEffectView.BlendingMode? {
get { self[VisualEffectBlendingKey.self] }
set { self[VisualEffectBlendingKey.self] = newValue }
}
var visualEffectEmphasized: Bool? {
get { self[VisualEffectEmphasizedKey.self] }
set { self[VisualEffectEmphasizedKey.self] = newValue }
}
}
struct VisualEffectView<Content: View>: NSViewRepresentable {
private let material: NSVisualEffectView.Material
private let blendingMode: NSVisualEffectView.BlendingMode
private let isEmphasized: Bool
private let content: Content
fileprivate init(
content: Content,
material: NSVisualEffectView.Material,
blendingMode: NSVisualEffectView.BlendingMode,
emphasized: Bool) {
self.content = content
self.material = material
self.blendingMode = blendingMode
self.isEmphasized = emphasized
}
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
let wrapper = NSHostingView(rootView: content)
// Not certain how necessary this is
view.autoresizingMask = [.width, .height]
wrapper.autoresizingMask = [.width, .height]
wrapper.frame = view.bounds
view.addSubview(wrapper)
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
nsView.material = context.environment.visualEffectMaterial ?? material
nsView.blendingMode = context.environment.visualEffectBlending ?? blendingMode
nsView.isEmphasized = context.environment.visualEffectEmphasized ?? isEmphasized
}
}
extension View {
func visualEffect(
material: NSVisualEffectView.Material,
blendingMode: NSVisualEffectView.BlendingMode = .behindWindow,
emphasized: Bool = false
) -> some View {
VisualEffectView(
content: self,
material: material,
blendingMode: blendingMode,
emphasized: emphasized
)
}
}
You can wrap any existing view via the
visualEffect(material:blendingMode:emphasized:)
modifier, and you can set any of the three values from higher up the view chain via the properties on
EnvironmentValues
.
A plain use for an entire
ContentView
would go something like this:
NavigationView {
MasterView(dates: self.$dates)
.frame(minWidth: 210, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.clear)
DetailView(selectedDate: Date())
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
.navigationViewStyle(DoubleColumnNavigationViewStyle())
.frame(minWidth: 600, maxWidth: .infinity, minHeight: 400, maxHeight: .infinity)
.visualEffect(material: .sidebar)
Alternatively, if you'd like to see the environment at work, you can do something like this:
NavigationView {
MasterView(dates: self.$dates)
.frame(minWidth: 210, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.clear)
DetailView(selectedDate: Date())
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.visualEffect(material: .sidebar)
}
.navigationViewStyle(DoubleColumnNavigationViewStyle())
.frame(minWidth: 600, maxWidth: .infinity, minHeight: 400, maxHeight: .infinity)
.environment(\.visualEffectMaterial, .dark)
If you wanted to hook it up to
PreferenceKey
s to enable subviews to affect the background, then you'd need to add a
@State
property to hold the prefs values, then you'd need to decide which has precedence—environment from above, or preferences from below. With that decision, you can change
updateNSView(_:context:)
to use the values you've chosen.
I shall probably write this one up & post it at alanquatermain.me at some point, it was quite fun and it's a good example of how to integrate with several parts of the SwiftUI ecosystem.