如何实现NSVisualEffectView的模糊半径调整功能?
如何在macOS应用中实现可调整的模糊半径效果
我在使用macOS的Terminal应用时注意到,该应用允许用户调整模糊半径,而非仅调整透明度!我希望在自己的应用中实现该功能,让用户也能调整模糊半径。我编写了基础应用代码,但目前仅能修改透明度,无法实现像下图那样的模糊半径调整效果。

我的现有代码
import Cocoa import SwiftUI import Combine // Protocol to manage blur effect updates protocol BlurEffectUpdatable { func updateBlurEffect(radius: CGFloat) } class AppDelegate: NSObject, NSApplicationDelegate, BlurEffectUpdatable { private lazy var window: NSWindow = NSWindow() private var blurEffect: NSVisualEffectView = NSVisualEffectView() private var blurManager = BlurManager() // Initialize BlurManager here private var cancellables: Set<AnyCancellable> = [] func applicationDidFinishLaunching(_ aNotification: Notification) { window.styleMask = [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView] window.backingType = .buffered window.isReleasedWhenClosed = false window.center() window.setFrameAutosaveName("Main Window") window.title = "My Blur" window.isMovableByWindowBackground = true window.backgroundColor = .clear // Create the blur effect blurEffect = NSVisualEffectView() blurEffect.blendingMode = .behindWindow blurEffect.material = .hudWindow blurEffect.state = .active blurEffect.autoresizingMask = [.width, .height] // Observe changes to radius in BlurManager and update the blur effect accordingly blurManager.$radius .sink { [weak self] radius in self?.updateBlurEffect(radius: radius) } .store(in: &cancellables) let hostingView = NSHostingView(rootView: ContentView(blurManager: blurManager)) hostingView.translatesAutoresizingMaskIntoConstraints = false let containerView = NSView() containerView.addSubview(blurEffect) containerView.addSubview(hostingView) blurEffect.frame = containerView.bounds hostingView.frame = containerView.bounds NSLayoutConstraint.activate([ hostingView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), hostingView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor) ]) window.contentView = containerView window.makeKeyAndOrderFront(nil) } // Conform to the BlurEffectUpdatable protocol func updateBlurEffect(radius: CGFloat) { print(radius) // I want the blur radius get updated not opacity! blurEffect.layer?.opacity = Float(radius/20.0) } } struct ContentView: View { @ObservedObject var blurManager: BlurManager var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") // Slider for adjusting the blur radius Slider(value: $blurManager.radius, in: 0...20, step: 1.0) { Text("Blur Radius") } .padding() Text("Blur Radius: \(blurManager.radius, specifier: "%.1f")") } .frame(width: 400, height: 200) } } // BlurManager class to manage the blur radius class BlurManager: ObservableObject { @Published var radius: CGFloat = 10.0 }
环境说明
- macOS 12.7.6
- Xcode 14.2
- 最低部署目标:12.0
解决方案
NSVisualEffectView的模糊效果由系统预设的material决定,无法直接修改模糊半径。要实现自定义可调的模糊效果,需要使用Core Image的高斯模糊滤镜(CIFilter.gaussianBlur),直接对窗口背景进行模糊处理。
修改步骤
- 移除原有的
NSVisualEffectView,改用普通NSView承载模糊滤镜 - 为模糊视图开启图层支持(
wantsLayer = true) - 创建
CIFilter高斯模糊实例,绑定到视图图层 - 在
updateBlurEffect方法中更新滤镜的模糊半径参数
修改后的完整代码
import Cocoa import SwiftUI import Combine import CoreImage protocol BlurEffectUpdatable { func updateBlurEffect(radius: CGFloat) } class AppDelegate: NSObject, NSApplicationDelegate, BlurEffectUpdatable { private lazy var window: NSWindow = NSWindow() private var blurView: NSView = NSView() private var blurFilter: CIFilter! private var blurManager = BlurManager() private var cancellables: Set<AnyCancellable> = [] func applicationDidFinishLaunching(_ aNotification: Notification) { window.styleMask = [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView] window.backingType = .buffered window.isReleasedWhenClosed = false window.center() window.setFrameAutosaveName("Main Window") window.title = "My Blur" window.isMovableByWindowBackground = true window.backgroundColor = .clear // 开启窗口背景模糊捕获能力 window.collectionBehavior = [.fullScreenAuxiliary, .canJoinAllSpaces] // 初始化模糊视图 blurView.wantsLayer = true blurView.autoresizingMask = [.width, .height] // 创建高斯模糊滤镜 blurFilter = CIFilter(name: "CIGaussianBlur") blurFilter.setValue(blurManager.radius, forKey: kCIInputRadiusKey) // 将滤镜绑定到视图图层 blurView.layer?.backgroundFilters = [blurFilter] blurView.layer?.masksToBounds = false blurView.layer?.allowsEdgeAntialiasing = true // 监听滑块值变化更新模糊半径 blurManager.$radius .sink { [weak self] radius in self?.updateBlurEffect(radius: radius) } .store(in: &cancellables) let hostingView = NSHostingView(rootView: ContentView(blurManager: blurManager)) hostingView.translatesAutoresizingMaskIntoConstraints = false hostingView.wantsLayer = true // 确保SwiftUI内容显示在模糊层上方 hostingView.layer?.zPosition = 1 let containerView = NSView() containerView.addSubview(blurView) containerView.addSubview(hostingView) blurView.frame = containerView.bounds hostingView.frame = containerView.bounds NSLayoutConstraint.activate([ hostingView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), hostingView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor), hostingView.widthAnchor.constraint(equalToConstant: 400), hostingView.heightAnchor.constraint(equalToConstant: 200) ]) window.contentView = containerView window.makeKeyAndOrderFront(nil) } func updateBlurEffect(radius: CGFloat) { blurFilter.setValue(radius, forKey: kCIInputRadiusKey) blurView.layer?.setNeedsDisplay() } } struct ContentView: View { @ObservedObject var blurManager: BlurManager var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") Slider(value: $blurManager.radius, in: 0...20, step: 1.0) { Text("模糊半径") } .padding() Text("当前模糊半径: \(blurManager.radius, specifier: "%.1f")") } .frame(width: 400, height: 200) .background(Color.clear) } } class BlurManager: ObservableObject { @Published var radius: CGFloat = 10.0 }
关键说明
- 使用
CIGaussianBlur滤镜直接控制模糊半径,范围0-20与滑块设置匹配 - 窗口开启
collectionBehavior确保背景模糊能正确捕获系统桌面内容 - SwiftUI视图设置
zPosition保证交互内容显示在模糊层上方 - 调用
layer?.setNeedsDisplay()触发滤镜参数更新后的视图重绘
内容的提问来源于stack exchange,提问作者Mango
相关产品推荐
相关产品推荐

