我的 NSWindow 的阴影在切换屏幕时被切断了?

My NSWindow's shadow is getting cut off when switching screens?

我想要一个背景模糊的 NSWindow,所以我在 . I also tried doing it with just the NSWindow using https://github.com/lukakerr/NSWindowStyles#:~:text=true-,6.%20Vibrant%20background,A,-vibrant.[=17= 的帮助下为 NSVisualEffectView 创建了一个包装器,用于我的 ContentView() ]

struct VisualEffectView: NSViewRepresentable
{
    let material: NSVisualEffectView.Material
    let blendingMode: NSVisualEffectView.BlendingMode
    
    func makeNSView(context: Context) -> NSVisualEffectView
    {
        let visualEffectView = NSVisualEffectView()
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
        visualEffectView.state = NSVisualEffectView.State.active
        return visualEffectView
    }

    func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context)
    {
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
    }
}

它工作正常并且看起来很棒,但是,当我将 window 移动到另一个屏幕时 - 并在两个屏幕之间暂停 window,然后将其移动到下一个 window- 它砍掉了 NSWindow 阴影的一部分。

移动屏幕时是这样的⤵︎

有没有办法防止这种影子斩波的发生?

界面:SwiftUI
生命周期:Appkit AppDelegate

想通了!谢天谢地,没有任何黑客大声笑

规则

为了在没有问题中令人讨厌的伪影的情况下实现这种外观,您必须按照 macOS 要求的方式做一些事情。

1.不要设置你的 NSWindow.backgroundColor = .clear!
这首先是造成上述令人讨厌的人工制品的原因!保持 window 的颜色不变将确保 window 在更换屏幕时正常工作。 NSVisualEffectView 捕获 window 后面的图像并将其用作背景,因此无需使任何东西透明。

2。确保在 window 的 styleMask 中包含 .titled
如果不这样做,将呈现没有圆角的 window。如果您尝试向 SwiftUI 视图添加圆角(就像我所做的那样),您仍然会在 NSWindow 本身上有一个不透明的背景。如果您随后将 window 的背景颜色设置为 .clear(就像我再次做的那样),阴影切割问题就会接踵而至!但是,这并不意味着标题栏会妨碍,它不会,我们稍后会谈到。

3。将您的 NSVisualEffectView 添加到您的 SwiftUI 视图!
我发现这比将视觉效果作为子视图添加到 NSWindow.contentView 更容易。

解决方案

1.因此,请先设置您的 NSWindow 和 AppDelegate! ⤵︎
您所做的只是确保标题栏存在但隐藏。

import Cocoa
import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {

    var window: NSWindow!

    func applicationDidFinishLaunching(_ aNotification: Notification) {

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Create the window and set the content view.
        // Note: You can add any styleMasks you want, just don't remove the ones below.
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 300, height: 200),
            styleMask: [.titled, .fullSizeContentView],
            backing: .buffered, defer: false)

        // Hide the titlebar
        window.titlebarAppearsTransparent = true
        window.titleVisibility = .hidden

        // Hide all Titlebar Controls
        window.standardWindowButton(.miniaturizeButton)?.isHidden = true
        window.standardWindowButton(.closeButton)?.isHidden = true
        window.standardWindowButton(.zoomButton)?.isHidden = true

        // Set the contentView to the SwiftUI ContentView()
        window.contentView = NSHostingView(rootView: contentView)

        // Make sure the window is movable when grabbing it anywhere
        window.isMovableByWindowBackground = true

        // Saves frame position between opening / closing
        window.setFrameAutosaveName("Main Window")

        // Display the window
        window.makeKeyAndOrderFront(nil)
        window.center()
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
}

此时您的 window 可能看起来像这样(如果从一个空白项目开始)。您可以看到 'Hello world!' 由于标题栏而没有完全居中。 ⤵︎


2。设置 NSWindow 后,是时候执行 ContentView() ⤵︎
在这里,您只想为 NSVisualEffectView 创建一个包装器并将其添加为背景。 然后 确保从视图中删除安全区域!这确保摆脱任何 space 标题栏在视图中的占用。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(VisualEffectView(material: .popover, blendingMode: .behindWindow))

            // Very important! (You could technically just ignore the top so you do you)
            .edgesIgnoringSafeArea(.all)
    }
}

/// Takes the image directly behind the window and uses that to create a blurred material. It can technically be added anywhere but most often it's used as a backing material for sidebars and full windows.
struct VisualEffectView: NSViewRepresentable {
    let material: NSVisualEffectView.Material
    let blendingMode: NSVisualEffectView.BlendingMode

    func makeNSView(context: Context) -> NSVisualEffectView {
        let visualEffectView = NSVisualEffectView()
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
        visualEffectView.state = NSVisualEffectView.State.active
        return visualEffectView
    }

    func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) {
        visualEffectView.material = material
        visualEffectView.blendingMode = blendingMode
    }
}

此时您的视图应该看起来像您想要的那样,没有任何负面影响!享受 <3(如果您对此解决方案有任何问题,请发表评论!)

资源

感谢@eonil 以这种巧妙的方式保持圆角。没有这个答案就无法解决这个问题⤵︎
https://whosebug.com/a/27613308/13142325

感谢 lukakerr 制作这份 NSWindow 样式列表! https://github.com/lukakerr/NSWindowStyles