Is there a method to blur a background in SwiftUI?
SwiftSwiftuiSwift Problem Overview
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?
Swift Solutions
Solution 1 - Swift
1. The Native SwiftUI way:
Just add .blur()
modifier on anything you need to be blurry like:
Image("BG")
.blur(radius: 20)
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 }
}
3. iOS 15: Materials
You can use iOS predefined materials with one line code:
.background(.ultraThinMaterial)
Solution 2 - Swift
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:
Solution 3 - Swift
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))
Solution 4 - Swift
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
}
}
Solution 5 - Swift
@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
Solution 6 - Swift
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
Solution 7 - Swift
Button("Test") {}
.background(Rectangle().fill(Color.red).blur(radius: 20))
Solution 8 - Swift
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))
}
}
}