You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在macOS AppKit+SwiftUI环境下实现无模糊液态玻璃(Liquid Glass)效果遇到问题

在macOS AppKit+SwiftUI环境下实现无模糊液态玻璃(Liquid Glass)效果遇到问题

我懂你想要的那种完全通透的液态玻璃效果——没有模糊感,能直接透出背后的桌面或窗口内容,就像悬浮在屏幕上的一层“透明玻璃条”对吧?我之前在做类似的状态栏小工具时也踩过一模一样的坑,咱们一步步把这个问题搞定。

先分析你当前代码的核心问题

你用了SwiftUI的.glassEffect(.clear),但这个效果在AppKit混合场景下不能单独生效——它需要依赖AppKit的NSVisualEffectView作为底层载体,而你当前的容器只是普通的NSView,相当于没有给玻璃效果提供“渲染画布”,所以自然看不到效果。另外,自定义的BarWindow也需要做些小调整,确保它能正确配合玻璃效果和按钮交互。

解决方案分步来

1. 先修复自定义BarWindow的配置

如果你的BarWindowNSWindow的子类,添加这两个重写属性,确保窗口能响应按钮点击(否则即使玻璃效果出来了,按钮也可能没反应):

class BarWindow: NSWindow {
    // 允许窗口成为关键窗口,这样按钮能接收点击事件
    override var canBecomeKey: Bool { true }
    override var canBecomeMain: Bool { true }
    
    override func contentViewDidChange() {
        super.contentViewDidChange()
        // 确保contentView的层是透明的,不遮挡玻璃效果
        contentView?.wantsLayer = true
        contentView?.layer?.backgroundColor = NSColor.clear.cgColor
    }
}

2. 重构窗口容器,用NSVisualEffectView替代普通NSView

把你原来的普通NSView容器换成NSVisualEffectView——这是AppKit实现玻璃效果的核心视图,SwiftUI的.glassEffect就是基于它封装的。修改setupWindow()里的容器部分:

@MainActor private func setupWindow() {
    let barHeight: CGFloat = 35
    let barHorizontalCut: CGFloat = 10
    let barVerticalCut: CGFloat = 2
    
    guard let mainScreen = NSScreen.main else { return }
    let screenFrame = mainScreen.frame
    let frame = NSRect(
        x: barHorizontalCut,
        y: screenFrame.height - barHeight - barVerticalCut,
        width: screenFrame.width - barHorizontalCut * 2,
        height: barHeight - barVerticalCut
    )
    
    window = BarWindow(
        contentRect: frame,
        styleMask: [.borderless],
        backing: .buffered,
        defer: false
    )
    window.isOpaque = false
    window.backgroundColor = .clear
    window.level = NSWindow.Level(rawValue: NSWindow.Level.statusBar.rawValue - 1)
    window.ignoresMouseEvents = false
    window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle]
    window.hasShadow = false
    window.orderBack(nil)
    
    // 👇 这里替换成NSVisualEffectView作为容器
    let visualEffectView = NSVisualEffectView(frame: frame)
    visualEffectView.wantsLayer = true
    // 配置无模糊的玻璃材质,对应SwiftUI的.clear效果
    visualEffectView.material = .fullScreenUI
    visualEffectView.blendingMode = .behindWindow // 让玻璃效果透出背后内容
    visualEffectView.state = .active
    visualEffectView.isEmphasized = false
    visualEffectView.cornerRadius = 16
    visualEffectView.masksToBounds = true
    
    // 初始化SwiftUI宿主视图
    let hostingView = NSHostingView(rootView: BarContentView()
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .glassEffect(.clear)) // 保留SwiftUI的glassEffect,和底层的NSVisualEffectView配合
    
    hostingView.translatesAutoresizingMaskIntoConstraints = false
    // 把宿主视图添加到visualEffectView的contentView里
    visualEffectView.contentView.addSubview(hostingView)
    
    // 添加约束
    NSLayoutConstraint.activate([
        hostingView.leadingAnchor.constraint(equalTo: visualEffectView.contentView.leadingAnchor),
        hostingView.trailingAnchor.constraint(equalTo: visualEffectView.contentView.trailingAnchor),
        hostingView.topAnchor.constraint(equalTo: visualEffectView.contentView.topAnchor),
        hostingView.bottomAnchor.constraint(equalTo: visualEffectView.contentView.bottomAnchor)
    ])
    
    // 把visualEffectView设置为窗口的contentView
    window.contentView = visualEffectView
    window.makeKeyAndOrderFront(nil)
}

3. 简化SwiftUI按钮的玻璃效果配置

你原来的timeButton里,.glassEffect(.clear)加在内部的HStack上有点冗余,把它移到Button外面,确保整个按钮区域都覆盖玻璃效果:

func timeButton(width: CGFloat, height: CGFloat, cornerRadius: CGFloat) -> some View {
    TimelineView(.periodic(from: .now, by: 1.0)) { timeline in
        Button(action: {
            let url = URL(fileURLWithPath: "/System/Applications/Calendar.app")
            NSWorkspace.shared.open(url)
        }) {
            HStack(spacing: 4) {
                Image(systemName: "calendar")
                    .foregroundColor(.white)
                    .font(.system(size: 12))
                Text(timeline.date.formatted(date: .abbreviated, time: .standard))
                    .foregroundColor(.white)
                    .font(.system(size: 12))
            }
            .frame(width: width, height: height)
        }
        .frame(width: width, height: height)
        .cornerRadius(cornerRadius)
        .glassEffect(.clear) // 移到这里,覆盖整个按钮区域
    }
}

最后检查几个关键配置(别漏!)

  • 确保window.isOpaque始终是falsebackgroundColor.clear——这两个是透明窗口的基础。
  • 不要给任何中间视图(比如容器、宿主视图)设置不透明的背景色,所有视图的背景都要设为.clear,否则会遮挡玻璃效果。
  • 如果你的BarContentView有其他子视图,也要确保它们的背景是透明的,避免挡住玻璃效果。

这样调整后,你应该就能看到完全通透的液态玻璃效果了——按钮和整个状态栏条都是透明的,能清晰透出背后的内容,同时保留玻璃效果的“液态”质感(比如系统的动态色调变化会自动应用到玻璃上)。

火山引擎 最新活动