Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

How to achieve a pure backdrop blur effect without predefined tint color in SwiftUI / UIKit?

Hi everyone,

I’m currently trying to create a pure backdrop blur effect in my iOS app (SwiftUI / UIKit), similar to the backdrop-filter: blur(20px) effect in CSS. My goal is simple: • Apply a Gaussian blur (radius ~20px) to the background content • Overlay a semi-transparent black layer (opacity 0.3) • Avoid any predefined color tint from UIBlurEffect or .ultraThinMaterial, etc.

However, every method I’ve tried so far (e.g., .ultraThinMaterial, UIBlurEffect(style:)) always introduces a built-in tint, which makes the result look gray or washed out. Even when layering a black color with opacity 0.3 over .ultraThinMaterial, it doesn’t give the clean, transparent-black + blur look I want.

What I’m looking for: • A clean 20px blur effect (like CIGaussianBlur) • No color shift/tint added by default • A layer of black at 30% opacity on top of the blur • Ideally works live (not a static snapshot blur)

Has anyone achieved something like this in UIKit or SwiftUI? Would really appreciate any insights, workarounds, or libraries that can help.

Thanks in advance! Ben

You're going to have to provide screenshots of what you want it to look like, e.g. an example of the effect that CSS creates; and some code showing us what you've already tried.

You can remove unnecessary filters (luminanceCurveMap, colorSaturate, colorBrightness) from UIVisualEffectView and leave only gaussianBlur. Example: https://github.com/VAndrJ/VAFrostedGlassView/blob/main/VAFrostedGlassView/Classes/VAThicknessVisualEffectView.swift

CAFilter is not public, but here is some information: https://theapplewiki.com/wiki/Dev:CAFilter

Or use a third-party, GPUImage3 for example: https://github.com/BradLarson/GPUImage3

check

//
//  BlurEffectView.swift
//  KakaduKit
//
//  Created by Игорь Чикичев on 31.03.2025.
//

import SwiftUI
import QuartzCore
import UIKit

/*
 Использование:
 BlurEffectViewRepresentable(style: .systemMaterial, intensity: 0.5)
 */

// MARK: - SwiftUI Обёртка
public struct BlurEffectViewRepresentable: UIViewRepresentable {
    public var style: UIBlurEffect.Style
    @Binding var intensity: CGFloat
    
    public init(style: UIBlurEffect.Style, intensity: Binding<CGFloat>) {
        self.style = style
        self._intensity = intensity
    }
    
    public func makeUIView(context: Context) -> BlurEffectView {
        let view = BlurEffectView()
        view.effect = UIBlurEffect(style: style)
        view.intensity = intensity
        return view
    }
    
    public func updateUIView(_ uiView: BlurEffectView, context: Context) {
        uiView.effect = UIBlurEffect(style: style)
        uiView.intensity = intensity
    }
}

public class BlurIntensityLayer: CALayer {
    @NSManaged var intensity: CGFloat
    
    override init(layer: Any) {
        super.init(layer: layer)
        if let layer = layer as? BlurIntensityLayer {
            self.intensity = layer.intensity
        }
    }
    
    override init() {
        super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    public override class func needsDisplay(forKey key: String) -> Bool {
        return key == #keyPath(intensity) || super.needsDisplay(forKey: key)
    }
    
    public override func action(forKey event: String) -> CAAction? {
        guard event == #keyPath(intensity) else {
            return super.action(forKey: event)
        }
        
        let animation = CABasicAnimation(keyPath: event)
        animation.fromValue = (self.presentation() ?? self).intensity
        return animation
    }
}

public class BlurEffectView: UIView {
    public override class var layerClass: AnyClass {
        return BlurIntensityLayer.self
    }
    
    @objc
    @IBInspectable
    public dynamic var intensity: CGFloat {
        set { self.blurIntensityLayer.intensity = newValue }
        get { return self.blurIntensityLayer.intensity }
    }
    
    @IBInspectable
    public var effect = UIBlurEffect(style: .dark) {
        didSet {
            self.setupPropertyAnimator()
        }
    }
    
    private let visualEffectView = UIVisualEffectView(effect: nil)
    private var propertyAnimator: UIViewPropertyAnimator!
    private var blurIntensityLayer: BlurIntensityLayer {
        return self.layer as! BlurIntensityLayer
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setupView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.setupView()
    }
    
    deinit {
        guard let animator = propertyAnimator else { return }
        DispatchQueue.main.async {
            animator.stopAnimation(true)
        }
    }
    
    private func setupPropertyAnimator() {
        self.propertyAnimator?.stopAnimation(true)
        self.propertyAnimator = UIViewPropertyAnimator(duration: 1, curve: .linear)
        self.propertyAnimator.addAnimations { [weak self] in
            self?.visualEffectView.effect = self?.effect
        }
        self.propertyAnimator.pausesOnCompletion = true
    }
    
    private func setupView() {
        self.backgroundColor = .clear
        self.isUserInteractionEnabled = false
        
        self.addSubview(self.visualEffectView)
        self.visualEffectView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            visualEffectView.topAnchor.constraint(equalTo: topAnchor),
            visualEffectView.bottomAnchor.constraint(equalTo: bottomAnchor),
            visualEffectView.leadingAnchor.constraint(equalTo: leadingAnchor),
            visualEffectView.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])
        
        self.setupPropertyAnimator()
    }
    
    public override func display(_ layer: CALayer) {
        guard let presentationLayer = layer.presentation() as? BlurIntensityLayer else {
            return
        }
        let clampedIntensity = max(0.0, min(1.0, presentationLayer.intensity))
        self.propertyAnimator.fractionComplete = clampedIntensity
    }
}
How to achieve a pure backdrop blur effect without predefined tint color in SwiftUI / UIKit?
 
 
Q