Reputation: 255
I need to implement an onboarding like in the image below. I can't apply blur avoiding the icon. I know how to do it using UIViewRepresentable, but I want to reach the goal using SwiftUI 2.0(min iOS 14.0). Is there a way to create such a masked blur without UIViewRepresentable?
UPD. I have a view hierarchy. I need to blur it(a gaussian blur effect with radius 5) and cover it with a black tint with an opacity 0.3 and a mask with a hole. Everything is ok, but the blur modifier also applies an effect to an element from the hole. It must not be blurred(like "Flag" icon at the screenshot). I can't separate this element from the view hierarchy. This onboarding view modifier must be reusable across the app.
Upvotes: 1
Views: 815
Reputation: 255
I found a solution to my problem. I used some code from other answers and wrote my OnboardingViewModifier. I used content from ViewModifier body function twice in a ZStack. The first one is an original view, the second one is blurred and masked. It gave me the needed result.
extension Path {
var reversed: Path {
let reversedCGPath = UIBezierPath(cgPath: cgPath)
.reversing()
.cgPath
return Path(reversedCGPath)
}
}
struct ShapeWithHole: Shape {
let hole: CGRect
let cornerRadius: CGFloat
func path(in rect: CGRect) -> Path {
var path = Rectangle().path(in: rect)
path.addPath(RoundedRectangle(cornerRadius: cornerRadius).path(in: hole).reversed)
return path
}
}
struct OnboardingViewModifier<DescriptionView>: ViewModifier where DescriptionView: View {
let hole: CGRect
let isPresented: Bool
@ViewBuilder let descriptionOverlay: () -> DescriptionView
func body(content: Content) -> some View {
content
.disabled(isPresented)
.overlay(overlay(content))
}
@ViewBuilder
func overlay(_ content: Content) -> some View {
if isPresented {
ZStack {
content
.blur(radius: 5)
.disabled(isPresented)
Color.black.opacity(0.3)
descriptionOverlay()
}
.compositingGroup()
.mask(ShapeWithHole(hole: hole, cornerRadius: 25))
.ignoresSafeArea(.all)
}
}
}
extension View {
func onboardingWithHole<DescriptionView>(
isPresented: Bool,
hole: CGRect,
@ViewBuilder descriptionOverlay: @escaping () -> DescriptionView) -> some View where DescriptionView: View {
modifier(OnboardingViewModifier(hole: hole, isPresented: isPresented, descriptionOverlay: descriptionOverlay))
}
}
Upvotes: -1
Reputation: 1850
There are two ways of doing so.
Either use the blur just on the background:
struct ContentView: View {
var body: some View {
VStack {
Icon()
Spacer()
}.background(Image("cat").blur(radius: 2.5))
}
}
Or, when having a entire view in the background you can use the ZStack
and just blur the View you want to be blurred. Make sure that the View/ Element you don't want blurred is above. Like so:
struct ContentView2: View {
var body: some View {
ZStack {
Image("cat").blur(radius: 2.5)
VStack {
Icon()
Spacer()
}
}
}
}
I used both times a entire View for the Icon which is probably a little over engineered but it gives you the idea:
struct Icon: View {
var body: some View {
Image(systemName: "pencil.circle.fill")
.resizable()
.frame(width: 50, height: 50)
.padding()
.foregroundColor(.red)
}
}
Both giving you this as result:
Upvotes: 2