使用 blendMode 防止带有 ContentView 的透明 NSWindow 闪烁
Prevent transparent NSWindow with a ContentView using blendMode from flickering
下面创建了一个透明的 NSWindow
和一个 ContentView
,它使用 blendMode
来创建滤色器叠加效果,这样 window 后面的所有东西看起来都是混合的(灰色在这种情况下是单色的)。它按预期工作,除非 window 未激活或被拖动,在这种情况下 ContentView
在正常(无混合)和混合之间闪烁; ContentView
在某些情况下也显示为脏,即当不活动时 ContentView
正在部分渲染且未完全更新。
我是否遗漏了与 NSWindow
事件相关的 ContentView
生命周期/刷新方面的内容,我的 NSWindow 设置是否正确,或者这是一个潜在的错误?本质上,不使用 blendMode
时不会出现此问题,因为使用透明 NSWindow
和半不透明 ContentView
进行测试时表现正常。
我在 Big Sur 11.6.2 上使用 Xcode 12.5.1,目标是 10.15
使用 AppKit App Delegate 生命周期模板重现的代码:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.edgesIgnoringSafeArea(.top)
.blendMode(BlendMode.color)
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.isOpaque = false
window.backgroundColor = .clear
window.level = .floating
window.isMovable = true
window.isMovableByWindowBackground = true
window.titlebarAppearsTransparent = true
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
3 月 7 日更新
问题仍然存在,在 Monterey 12.2.1 上使用 Xcode 13.2.1,并以 12.2
为目标
3 月 8 日更新
添加默认的 NSVisualEffectView
背景视图会带来更高的稳定性,因为当 window 处于活动状态并被拖动时,视图不再在不透明和透明之间闪烁。
剩下的唯一问题是在应用程序之间切换时失去焦点,这有时会导致视图变得不透明,尽管重新聚焦 window 可以解决问题。
解决方法是在 NSWindow
上启用 hidesOnDeactivate
,并结合 applicationShouldHandleReopen
,这样 window 会在失去焦点时消失并且问题不可见用户,但理想情况下 window 应始终保持可见状态,直到关闭。
struct VisualEffectView: NSViewRepresentable {
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(VisualEffectView())
}
}
这感觉有点老套,我敢肯定知识渊博的人有更优雅的解决方案,但是在视图中添加 Timer
以强制重绘完全解决了闪烁问题,并且会因此似乎回答了这个问题。 注意:此方法还省去了对虚拟对象的需要 NSVisualEffectView
。
struct ContentView: View {
@State var currentDate = Date()
let timer = Timer.publish(every: 1.0 / 60.0, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
Rectangle()
.fill(.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Text("\(currentDate)")
.foregroundColor(.clear)
.onReceive(timer) { input in
currentDate = input
}
}
}
}
下面创建了一个透明的 NSWindow
和一个 ContentView
,它使用 blendMode
来创建滤色器叠加效果,这样 window 后面的所有东西看起来都是混合的(灰色在这种情况下是单色的)。它按预期工作,除非 window 未激活或被拖动,在这种情况下 ContentView
在正常(无混合)和混合之间闪烁; ContentView
在某些情况下也显示为脏,即当不活动时 ContentView
正在部分渲染且未完全更新。
我是否遗漏了与 NSWindow
事件相关的 ContentView
生命周期/刷新方面的内容,我的 NSWindow 设置是否正确,或者这是一个潜在的错误?本质上,不使用 blendMode
时不会出现此问题,因为使用透明 NSWindow
和半不透明 ContentView
进行测试时表现正常。
我在 Big Sur 11.6.2 上使用 Xcode 12.5.1,目标是 10.15
使用 AppKit App Delegate 生命周期模板重现的代码:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.edgesIgnoringSafeArea(.top)
.blendMode(BlendMode.color)
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.isOpaque = false
window.backgroundColor = .clear
window.level = .floating
window.isMovable = true
window.isMovableByWindowBackground = true
window.titlebarAppearsTransparent = true
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
3 月 7 日更新
问题仍然存在,在 Monterey 12.2.1 上使用 Xcode 13.2.1,并以 12.2
为目标3 月 8 日更新
添加默认的 NSVisualEffectView
背景视图会带来更高的稳定性,因为当 window 处于活动状态并被拖动时,视图不再在不透明和透明之间闪烁。
剩下的唯一问题是在应用程序之间切换时失去焦点,这有时会导致视图变得不透明,尽管重新聚焦 window 可以解决问题。
解决方法是在 NSWindow
上启用 hidesOnDeactivate
,并结合 applicationShouldHandleReopen
,这样 window 会在失去焦点时消失并且问题不可见用户,但理想情况下 window 应始终保持可见状态,直到关闭。
struct VisualEffectView: NSViewRepresentable {
func makeNSView(context: Context) -> NSVisualEffectView {
let view = NSVisualEffectView()
return view
}
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(VisualEffectView())
}
}
这感觉有点老套,我敢肯定知识渊博的人有更优雅的解决方案,但是在视图中添加 Timer
以强制重绘完全解决了闪烁问题,并且会因此似乎回答了这个问题。 注意:此方法还省去了对虚拟对象的需要 NSVisualEffectView
。
struct ContentView: View {
@State var currentDate = Date()
let timer = Timer.publish(every: 1.0 / 60.0, on: .main, in: .common).autoconnect()
var body: some View {
ZStack {
Rectangle()
.fill(.black)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Text("\(currentDate)")
.foregroundColor(.clear)
.onReceive(timer) { input in
currentDate = input
}
}
}
}