SwiftUI跨平台开发中如何在macOS端控制模糊效果强度
macOS端实现与iOS端
fractionComplete等效的可控模糊效果 问题背景
iOS端可通过UIViewPropertyAnimator的fractionComplete属性自由控制UIVisualEffectView的模糊强度,在同时支持iOS、macOS的跨平台SwiftUI项目中,iOS端代码运行效果符合预期,但macOS端NSVisualEffectView未提供公开的、与fractionComplete功能完全一致的属性,无法直接实现同等的模糊强度可控效果,现有实现代码如下:
import SwiftUI struct ContentView: View { var body: some View { ZStack { Text("Hello, world!") .font(Font.system(size: 50.0)) .padding() .background(Color.yellow) .fixedSize() Text("Hello, world!") .font(Font.system(size: 25.0)) .foregroundColor(Color.blue) .padding(20) .background(UnicornView()) .border(Color.blue) } } }
#if os(iOS) struct UnicornView: UIViewControllerRepresentable { class UnicornControllerModel: UIViewController { var animator = UIViewPropertyAnimator(duration: 1, curve: .linear) var visualEffectView = UIVisualEffectView(effect: nil) override func viewDidLoad() { super.viewDidLoad() animator.addAnimations { self.visualEffectView.effect = UIBlurEffect(style: .systemUltraThinMaterial) } animator.fractionComplete = 0.2 view = visualEffectView } } func makeUIViewController(context: UIViewControllerRepresentableContext<UnicornView>) -> UnicornControllerModel { return UnicornControllerModel() } func updateUIViewController(_ uiViewController: UnicornControllerModel, context: UIViewControllerRepresentableContext<UnicornView>) { } } #endif
#if os(macOS) struct UnicornView: NSViewControllerRepresentable { class UnicornControllerModel: NSViewController { var animator = NSAnimation(duration: 1, animationCurve: .linear) var visualEffectView = NSVisualEffectView() init() { super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() visualEffectView.material = .popover visualEffectView.blendingMode = .withinWindow view = visualEffectView } override fun loadView() { view = NSView() } } func makeNSViewController(context: NSViewControllerRepresentableContext<UnicornView>) -> UnicornControllerModel { return UnicornControllerModel() } func updateNSViewController(_ nsViewController: UnicornControllerModel, context: NSViewControllerRepresentableContext<UnicornView>) { } } #endif
实现方案
macOS端公开API下没有和iOSfractionComplete1:1对应的属性,可以通过手动控制NSVisualEffectView关联的原生层滤镜参数,实现0~1区间任意调整模糊强度的效果,和iOS端逻辑完全对齐,不需要使用私有API,符合App Store审核要求。
修改后的macOS端UnicornView实现代码如下:
#if os(macOS) struct UnicornView: NSViewControllerRepresentable { // 模糊强度,和iOS端fractionComplete取值范围一致:0~1 var fractionComplete: CGFloat = 0.2 class UnicornControllerModel: NSViewController { var visualEffectView = NSVisualEffectView() // 保存系统原生滤镜默认参数,用于进度计算 private var defaultBlurRadius: CGFloat = 0 private var defaultSaturation: CGFloat = 0 var targetFraction: CGFloat = 0.2 override func loadView() { view = NSView() } override func viewDidLoad() { super.viewDidLoad() visualEffectView.material = .popover visualEffectView.blendingMode = .withinWindow visualEffectView.wantsLayer = true visualEffectView.state = .active view.addSubview(visualEffectView) visualEffectView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ visualEffectView.leadingAnchor.constraint(equalTo: view.leadingAnchor), visualEffectView.trailingAnchor.constraint(equalTo: view.trailingAnchor), visualEffectView.topAnchor.constraint(equalTo: view.topAnchor), visualEffectView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // 视图加载完成后获取系统默认毛玻璃参数 DispatchQueue.main.async { [weak self] in guard let self = self, let filters = self.visualEffectView.layer?.backgroundFilters else { return } for filter in filters { if let blur = filter as? CIFilter, blur.name == "CIGaussianBlur" { self.defaultBlurRadius = blur.value(forKey: "inputRadius") as? CGFloat ?? 30 } if let sat = filter as? CIFilter, sat.name == "CIColorControls" { self.defaultSaturation = sat.value(forKey: "inputSaturation") as? CGFloat ?? 1.8 } } self.updateBlurIntensity(fraction: self.targetFraction) } } func updateBlurIntensity(fraction: CGFloat) { guard let filters = visualEffectView.layer?.backgroundFilters else { return } let clampedFraction = max(0, min(1, fraction)) for filter in filters { if let blur = filter as? CIFilter, blur.name == "CIGaussianBlur" { blur.setValue(defaultBlurRadius * clampedFraction, forKey: "inputRadius") } if let sat = filter as? CIFilter, sat.name == "CIColorControls" { // 饱和度随模糊强度同步调整,匹配原生毛玻璃质感 let targetSat = 1 + (defaultSaturation - 1) * clampedFraction sat.setValue(targetSat, forKey: "inputSaturation") } } } } func makeNSViewController(context: NSViewControllerRepresentableContext<UnicornView>) -> UnicornControllerModel { let controller = UnicornControllerModel() controller.targetFraction = fractionComplete return controller } func updateNSViewController(_ nsViewController: UnicornControllerModel, context: NSViewControllerRepresentableContext<UnicornView>) { nsViewController.updateBlurIntensity(fraction: fractionComplete) } } #endif
实现说明
- 逻辑完全对齐iOS端
fractionComplete行为,取值范围为0(无模糊效果)到1(系统默认满强度模糊),默认值0.2和原有iOS代码效果一致 - 直接修改系统原生
NSVisualEffectView的层滤镜参数,保留原生毛玻璃的所有视觉特性,不会出现自定义模糊的质感偏差 - 支持动态调整强度,在SwiftUI中修改传入的
fractionComplete值即可实时更新模糊程度,和iOS端调用逻辑一致 - 所有使用的API均为公开API,无私有API调用风险
内容的提问来源于stack exchange,提问作者swiftPunk
相关产品推荐
相关产品推荐

