You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现NSVisualEffectView的模糊半径调整功能?

如何在macOS应用中实现可调整的模糊半径效果

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

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),直接对窗口背景进行模糊处理。

修改步骤

  1. 移除原有的NSVisualEffectView,改用普通NSView承载模糊滤镜
  2. 为模糊视图开启图层支持(wantsLayer = true)
  3. 创建CIFilter高斯模糊实例,绑定到视图图层
  4. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 06:04:56