93

I'm looking to blur a view's background but don't want to have to break out into UIKit to accomplish it (eg. a UIVisualEffectView) I'm digging through docs and got nowhere, seemingly there is no way to live-clip a background and apply effects to it. Am I wrong or looking into it the wrong way?

Matteo Pacini
  • 21,796
  • 7
  • 67
  • 74
Sonicjet
  • 940
  • 1
  • 6
  • 8

9 Answers9

218

1. The Native SwiftUI way:

Just add .blur() modifier on anything you need to be blurry like:

Image("BG")
   .blur(radius: 20)

Blur Demo Note the top and bottom of the view

Note that you can Group multiple views and blur them together.


2. The Visual Effect View:

You can bring the prefect UIVisualEffectView from the UIKit:

VisualEffectView(effect: UIBlurEffect(style: .dark))

With this tiny struct:

struct VisualEffectView: UIViewRepresentable {
    var effect: UIVisualEffect?
    func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView { UIVisualEffectView() }
    func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) { uiView.effect = effect }
}

VEV Demo


3. iOS 15: Materials

You can use iOS predefined materials with one line code:

.background(.ultraThinMaterial)

Demo

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
29

I haven't found a way to achieve that in SwiftUI yet, but you can use UIKit stuff via UIViewRepresentable protocol.

struct BlurView: UIViewRepresentable {

    let style: UIBlurEffect.Style

    func makeUIView(context: UIViewRepresentableContext<BlurView>) -> UIView {
        let view = UIView(frame: .zero)
        view.backgroundColor = .clear
        let blurEffect = UIBlurEffect(style: style)
        let blurView = UIVisualEffectView(effect: blurEffect)
        blurView.translatesAutoresizingMaskIntoConstraints = false
        view.insertSubview(blurView, at: 0)
        NSLayoutConstraint.activate([
            blurView.heightAnchor.constraint(equalTo: view.heightAnchor),
            blurView.widthAnchor.constraint(equalTo: view.widthAnchor),
        ])
        return view
    }

    func updateUIView(_ uiView: UIView,
                      context: UIViewRepresentableContext<BlurView>) {

    }

}

Demo:

struct ContentView: View {

    var body: some View {
        NavigationView {
            ZStack {
                List(1...100) { item in
                    Rectangle().foregroundColor(Color.pink)
                }
                .navigationBarTitle(Text("A List"))
                ZStack {
                    BlurView(style: .light)
                        .frame(width: 300, height: 300)
                    Text("Hey there, I'm on top of the blur")

                }
            }
        }
    }

}

I used ZStack to put views on top of it.

ZStack {
 // List
 ZStack {
    // Blurred View
    // Text
 }
}

And ends up looking like this:

enter image description here

Matteo Pacini
  • 21,796
  • 7
  • 67
  • 74
  • 6
    This blurs content in a view but I’m looking for for something that allows blurring of underlying content, much like how a UI VisualEffectView or navigationController works whereas you can have an item that blurs the content it overlays and only within the frame of the BlurringView rather than child content of a BlurringView. I suppose more like how BlendMode works. – Sonicjet Jun 16 '19 at 13:02
  • 1
    Isn't that what this is doing? – Chad Parker Dec 23 '19 at 03:00
  • 1
    How do I reduce the amount of blur? – Peter Schorn Jul 20 '20 at 15:44
  • When I navigate the page second time the blur views do not appear. Is it because the updateUIView function is empty? – C.Aglar Oct 22 '20 at 18:00
  • This is genius! It works - and if you use style 'systemChromeMaterial', it mimics how the menu bars have blur / transparency. Nice job. – FontFamily Nov 20 '20 at 01:47
  • Is it possible to make the white lines underneath the blurred view more visible? – SmoothPoop69 Jan 12 '21 at 01:33
26

The simplest way is here by Richard Mullinix:

struct Blur: UIViewRepresentable {
    var style: UIBlurEffect.Style = .systemMaterial

    func makeUIView(context: Context) -> UIVisualEffectView {
        return UIVisualEffectView(effect: UIBlurEffect(style: style))
    }

    func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
        uiView.effect = UIBlurEffect(style: style)
    }
}

Then just use it somewhere in your code like background:

    //...
    MyView()
        .background(Blur(style: .systemUltraThinMaterial))
Lindemann
  • 3,336
  • 3
  • 29
  • 27
cyber-bot
  • 497
  • 1
  • 6
  • 12
10

New in iOS 15 , SwiftUI has a brilliantly simple equivalent to UIVisualEffectView, that combines ZStack, the background() modifier, and a range of built-in materials.

ZStack {
    Image("niceLook")

    Text("Click me")
        .padding()
        .background(.thinMaterial)
}

You can adjust the “thickness” of your material – how much of the background content shines through – by using one of several material types. From thinnest to thickest, they are:

.ultraThinMaterial
.thinMaterial
.regularMaterial
.thickMaterial
.ultraThickMaterial
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
6

As mentioned by @mojtaba, it's very peculiar to see white shade at top of image when you set resizable() along with blur().

As simple trick is to raise the Image padding to -ve.

 var body: some View {

        return
            ZStack {

                Image("background_2").resizable()
                    .edgesIgnoringSafeArea(.all)
                    .blur(radius: 5)
                    .scaledToFill()
                    .padding(-20) //Trick: To escape from white patch @top & @bottom


        }
  }

Result: SwiftUI Image blue trick

byJeevan
  • 3,728
  • 3
  • 37
  • 60
4

I have found an interesting hack to solve this problem. We can use UIVisualEffectView to make a live "snapshot" of its background. But this "snapshot" will have an applied effect of UIVisualEffectView. We can avoid applying this effect using UIViewPropertyAnimator.

I didn't find any side effects of this hack. You can find my solution here: my GitHub Gist

Code

/// A View in which content reflects all behind it
struct BackdropView: UIViewRepresentable {

    func makeUIView(context: Context) -> UIVisualEffectView {
        let view = UIVisualEffectView()
        let blur = UIBlurEffect()
        let animator = UIViewPropertyAnimator()
        animator.addAnimations { view.effect = blur }
        animator.fractionComplete = 0
        animator.stopAnimation(false)
        animator.finishAnimation(at: .current)
        return view
    }
    
    func updateUIView(_ uiView: UIVisualEffectView, context: Context) { }
    
}

/// A transparent View that blurs its background
struct BackdropBlurView: View {
    
    let radius: CGFloat
    
    @ViewBuilder
    var body: some View {
        BackdropView().blur(radius: radius)
    }
    
}

Usage

ZStack(alignment: .leading) {
    Image(systemName: "globe")
        .resizable()
        .frame(width: 200, height: 200)
        .foregroundColor(.accentColor)
        .padding()
    BackdropBlurView(radius: 6)
        .frame(width: 120)
}

Usage example

  • This is nice because it uses the old `.blur(radius:)`, which gives more control over the amount of blurring than the new material styles. Even the `ultraThinMaterial` blurs quite a bit compared to, say, `.blur(radius:3)`. The normal use of `.blur` is to apply it to the view you want to blur, but the approach here gives you a view that you can insert in a ZStack and blur the contents _underneath_. The other approaches that do this use the new (heavily blurring) materials. – James Toomey Jan 23 '23 at 18:56
2
@State private var amount: CGFLOAT = 0.0

var body: some View {
    VStack{
       Image("Car").resizable().blur(radius: amount, opaque: true)
    }
}

Using "Opaque: true" with blur function will eliminate white noise

1

There is a very useful but unfortunately private (thanks Apple) class CABackdropLayer

It draws a copy of the layers below, I found it useful when using blend mode or filters, It can also be used for blur effect

Code

open class UIBackdropView: UIView {

  open override class var layerClass: AnyClass {
    NSClassFromString("CABackdropLayer") ?? CALayer.self
  }
}

public struct Backdrop: UIViewRepresentable {

  public init() {}

  public func makeUIView(context: Context) -> UIBackdropView {
    UIBackdropView()
  }

  public func updateUIView(_ uiView: UIBackdropView, context: Context) {}
}

public struct Blur: View {

  public var radius: CGFloat
  public var opaque: Bool

  public init(radius: CGFloat = 3.0, opaque: Bool = false) {
    self.radius = radius
    self.opaque = opaque
  }

  public var body: some View {
    Backdrop()
      .blur(radius: radius, opaque: opaque)
  }
}

Usage

struct Example: View {

  var body: some View {
    ZStack {
      YourBelowView()
      YourTopView()
        .background(Blur())
        .background(Color.someColor.opacity(0.4))
    }
  }
}

Source

0

Sometimes we need a transparent blur effect. Here will be a solution.

struct TransparentBlurView: UIViewRepresentable {
    typealias UIViewType = UIVisualEffectView

    func makeUIView(context: Context) -> UIVisualEffectView {
        let view = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialLight))

        return view
    }

    func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
        DispatchQueue.main.async {
            if let backdropLayer  = uiView.layer.sublayers?.first {
                backdropLayer.filters?.removeAll(where: { filter in
                    String(describing: filter) != "gaussianBlur"
                })
            }
        }
    }
}

enter image description here

ZYiOS
  • 5,204
  • 3
  • 39
  • 45