简体   繁体   中英

How to reliably retrieve a window's background color on macOS with SwiftUI?

Is there a Swifty way to detect the background color of a window in SwiftUI on macOS, that would work reliably regardless of the current theme (Dark Mode or Light Mode)?

For example, if one were to make a solid rectangle that "blends in" with the window's background, which color would they use?

This answer suggests the use of NSColor.xxxBackgroundColor : SwiftUI: Get the Dynamic Background Color (Dark Mode or Light Mode)

However, this doesn't quite work for me. Here's some test code (Xcode 12.5, Swift 5.4) that makes three rectangles of various NSColor s. I am looking for the one that blends in with the background.

struct TestView: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("This text is on the default background")
            HStack(spacing: 30) {
                Text("windowBackgroundColor")
                    .frame(width: 200, height: 100)
                    .background(Color(NSColor.windowBackgroundColor))
                Text("underPageBackgroundColor")
                    .frame(width: 200, height: 100)
                    .background(Color(NSColor.underPageBackgroundColor))
                Text("textBackgroundColor")
                    .frame(width: 200, height: 100)
                    .background(Color(NSColor.textBackgroundColor))
            }
        }
        .padding(20)
    }
}

In Dark mode, it seems NSColor.underPageBackgroundColor matches the window's background. 在此处输入图像描述

But in Light mode, nothing matches: 在此处输入图像描述

Swifty way to get Window Background

The window's background is not composed by a color, is a NSVisualEffectView with .windowBackground as material .

You can achieve that with this code:

struct EffectView: NSViewRepresentable {
    @State var material: NSVisualEffectView.Material = .headerView
    @State var blendingMode: NSVisualEffectView.BlendingMode = .withinWindow

    func makeNSView(context: Context) -> NSVisualEffectView {
        let view = NSVisualEffectView()
        view.material = material
        view.blendingMode = blendingMode
        return view
    }
    
    func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
        nsView.material = material
        nsView.blendingMode = blendingMode
    }
}

And apply that to your view:

struct ContentView: View {
    var body: some View {
        EffectView(material: .windowBackground)
            .overlay(Text("Window Real Background"))
            .padding(30)
    }
}

The output is this (nothing to see because it mimetizes with background):

在此处输入图像描述

The reason why the background is an NSVisualEffectView is because the Window Tinting of macOS Big Sur, that changes the background according to wallpaper predominant color:

在此处输入图像描述

You can use @Environment variables to get the ColorScheme that is being produced. In iOS I often use it like this, however it should translate to MacOS as well. There is no way to GET a view's color dynamically, because it is a set value that is not accessible. The best you can do is set the view, in a known state, and then pass that color around as needed. In my example I just used Color.black and Color.white but you can easily assign any color to a variable and pass it around so that it is known.

@Environment(\.colorScheme) var colorScheme

struct TestView: View {

    var body: some View {
        VStack(spacing: 20) {
            Text("This text is on the default background")
            HStack(spacing: 30) {
                Text("windowBackgroundColor")
                    .frame(width: 200, height: 100)
                    .background(colorScheme == .dark ? Color.black : Color.white)
                Text("underPageBackgroundColor")
                    .frame(width: 200, height: 100)
                    .background(colorScheme == .dark ? Color.black : Color.white)
                Text("textBackgroundColor")
                    .frame(width: 200, height: 100)
                    .background(colorScheme == .dark ? Color.black : Color.white)
            }
        }
        .background(colorScheme == .dark ? Color.black : Color.white)
        .padding(20)
    }
}

In your case you can alternate the colors to the system colors that you're wanting to use. Setting based on the colorScheme for the particular color you want on that view. Eg...

var windowBGColor = NSColor.windowBackgroundColor
var underPageBGColor = NSColor.underPageBackgroundColor
var textBGColor = NSColor.textBackgroundColor

var sampleColor = colorScheme == .dark ? windowBGColor : textBGColor

The windowBackground is a real window's background color, you just compare it not with real window background, but with theme view below hosting controller view

Here is a hierarchy (you can verify it yourself in view debug mode)

演示

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM