8

My goal is to create a blur view like the view on the top right corner of the below image. transparent view on top of ocean and sand

I've tried the top 3 answers of this post, but they all have the same problem—the blur view only has 1 color when there are multiple colors underneath it. This is one of the solutions I've tried:

import SwiftUI

struct ContentView: View {
    var body: some View {
        ZStack {
            VStack{
                ForEach(0..<20, id: \.self){ num in
                    Rectangle()
                        .frame(height: 20)
                        .padding(.vertical, 6)
                }
            }
            Blur(style:  .systemThinMaterialLight)
                .mask(
                    VStack(spacing: 0) {
                        Rectangle()
                            .frame(width: 347, height: 139)
                            .padding(.top, 0)
                        Spacer()
                    }
                )
                .allowsHitTesting(false)
        }
    }
}

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)
    }
}

blur view on top of black and white stripes

As you can see, the blur view is just a single gray view. You can't even see the black and white stripes underneath the blur view.

I want the blur view to be more transparent, like the one you see in the first image where the ocean, the sand, and the shadow are still visible through the blur view. How can I create such a view in SwiftUI?

5
  • 1
    Add your code too. Commented Jan 12, 2021 at 14:47
  • Does this answer your question stackoverflow.com/a/65595112/12299030? Commented Jan 12, 2021 at 15:56
  • @Asperi No that's actually one of the solution I've tried😢 I just added my own code to the question and pointed out the issue with that solution. Commented Jan 13, 2021 at 6:46
  • UIBlurEffect.Style.systemMaterial might be too "dense", try with other values that blur the background less. Commented Jan 15, 2021 at 11:54
  • Have you solved the problem? I have a similar design with swift Commented Aug 6, 2021 at 8:46

7 Answers 7

7

This code comes very close to your question, it only works from IOS15 up tough:

ZStack{
  Image("background")
  //then comes your look trough button, here just a Rectangle:
  Rectangle()
 .foregroundColor(.secondary)
 .background(.ultraThinMaterial)
 .frame(width: 100, height: 100)
 //then you can add opacity to see a bit more of the background:
 .opacity(0.95)
}
Sign up to request clarification or add additional context in comments.

1 Comment

you don't need to set opacity if using .background(.ultraThinMaterial) for blurring
6

Just use the foregroundStyle modifier new in SwiftUI.

Example:

RoundedRectangle(cornerRadius: 5)
   .frame(width: 30, height: 30)
   .foregroundStyle(.ultraThinMaterial)

Comments

4

Rather than use two images, I'd prefer to use a binding. For this, I added an image named "lyon" to the assets.

Here is my solution, minus some maths:

ContentView

struct ContentView: View {
    @State private var image: UIImage = UIImage(named: "lyon")!
    var body: some View {
        ZStack{
            Image(uiImage: image)
                .resizable()
                .aspectRatio(contentMode: .fill)
            IceCube(image: image)
        }
        .ignoresSafeArea(.all)
    }
}

IceCube()

This view does the lifting:

struct IceCube: View {

    @State private var rectPosition = CGPoint(x: 150, y: 150)
    
    @State private var cutout: UIImage?

    let image: UIImage

    let frameSide: CGFloat = 180

    var body: some View {
        
        Image(uiImage: cutout ?? image)
            .frame(width: frameSide, height: frameSide)
            .blur(radius: 5)
            .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
            .overlay(RoundedRectangle(cornerRadius: 12, style: .continuous).stroke(Color.red, lineWidth: 3))
            .onAppear(perform: {
                processImage()
            })
            .position(rectPosition)
            .gesture(DragGesture().onChanged({ value in
                self.rectPosition = value.location
                processImage()
            }))
        
    }
    
    func processImage() {
        
        //TODO: - Find the image scale size from ContentView and also figure out how much it begins to the left/top of the screen.
        
        cutout = croppedImage(from: image, croppedTo: CGRect(x: rectPosition.x, y: rectPosition.y, width: frameSide, height: frameSide))
    }
}


//MARK: - image processing functions.

func croppedImage(from image: UIImage, croppedTo rect: CGRect) -> UIImage {
    
    UIGraphicsBeginImageContext(rect.size)
    let context = UIGraphicsGetCurrentContext()
    
    let drawRect = CGRect(x: -rect.origin.x, y: -rect.origin.y, width: image.size.width, height: image.size.height)
    
    context?.clip(to: CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height))
    
    image.draw(in: drawRect)
    
    let subImage = UIGraphicsGetImageFromCurrentImageContext()
    
    UIGraphicsEndImageContext()
    return subImage!
}

Obviously, avoid the forced unwrapping in a real project.

If you need some ideas for the math, look at my repo for cropping and sizing an image on GitHub here:

https://github.com/Rillieux/PhotoSelectAndCrop/blob/main/Sources/ImageMoveAndScaleSheet%2BExtensions.swift

The basic idea is that when you want to crop the square from the original image, that original image may not be exactly the same size and dimensions as what you see in the screen. For example, if you use .aspectRatio(contentMode: .fit) the image may be scaled down, and so you need to account for that. I didn't do that yet, which is why the blur does not really match what is lies over. But, I think you'll get the idea.

Blurred overlay

Also, my example here could be greatly improved for your case specifically, by using a view builder, something like this:

struct SafeEdgesBlurContainer<V: View>: View {

    var containedView: V 

    //Snipped code

    var body: some View {
        ZStack {
            Image(uiImage: cutout ?? image)
            containedView
    }

 ...

Then use it like this:

IceCube(image: UIImage(named: "lyon")!, containedView: Text("4,7")

Comments

0

This method applies a Gaussian blur to a view.

func blur(radius: CGFloat, opaque: Bool = false) -> some View

Parameters:

  • radius: The radial size of the blur. A blur is more diffuse when its radius is large.
  • opaque: A Boolean value that indicates whether the blur renderer permits transparency in the blur output. Set to true to create an opaque blur, or set to false to permit transparency.
Image("your_Image")
   .resizable()
   .frame(width: 300, height: 300)
   .blur(radius: 20)

4 Comments

Please add some explanation in the answer and properly format your code.
I don't want to add a blur effect to the entire image. I want to have a small blurred transparent view on top of the image. Like the one you see in the first image of my post. I've tried adding a blur modifier to a rounded rectangle and put it on top of the image, but it just doesn't look right
So you can create your picture after that you can add view (addsubview) with clear color and blur effect on the the top of image.
@IlahiCharfeddine I've tried that. Doesn't work. It looks nothing like the blur view I showed in the first image.
0

i wrote a configurable 'View extension' for tvOS (but probably would work on ios as well), save these next two files:

import Foundation
import SwiftUI

extension View
{
    /**
     function that creates the background color that "shines" through the 'IceCube'
     */
    func createColorBGLayer(at: CGRect,
                            background: Color,
                            cornerRadius: CGFloat) -> some View
    {
        return background
            .clipShape(
                RoundedRectangle(cornerRadius: cornerRadius)
                    .offset(x: at.minX + 5, y: at.minY + 5)
                    .size(CGSize(width: at.width - 10, height: at.height - 10))
            )
    }
    
    /**
     function that creates the 'IceCube' (a clipped image with blur)
     */
    func createIceLayer(at: CGRect,
                        backgroundOpacity: CGFloat,
                        cornerRadius: CGFloat,
                        blurRadius: CGFloat) -> some View
    {
        return self
            .opacity(backgroundOpacity)
            .clipShape(
                RoundedRectangle(cornerRadius: cornerRadius)
                    .offset(x: at.minX, y: at.minY)
                    .size(CGSize(width: at.width, height: at.height))
            )
            .blur(radius: blurRadius)
    }
    
    /**
     function that creates the text layer in the center of the 'IceCube'
     */
    func createTextLayer(at: CGRect,
                         textString: String,
                         fontSize: CGFloat,
                         fontDesign: Font.Design,
                         fontWeight: Font.Weight,
                         foregroundColor: Color) -> some View
    {
        // calculate render width and height of text using provided font (without actually rendering)
        let sizeOfText: CGSize = textString.sizeUsingFont(fontSize: fontSize, weight: fontWeight)
        let textOffsetX = at.minX + ((at.width - sizeOfText.width) / 2)
        let textOffsetY = at.minY + ((at.height - sizeOfText.height) / 2)
        
        // render text in center of iceCube
        return GeometryReader { proxy in
            Text(textString)
                .font(Font.system(size: fontSize, design: fontDesign))
                .fontWeight(fontWeight)
                .foregroundColor(foregroundColor)
                // put the text in the middle of the blured rectangle
                .offset(x: textOffsetX, y: textOffsetY)
        }
    }
    
    /**
     main function to create the ice cube ontop of this extended view
     */
    func iceCube(at: CGRect,
                 textString: String = "",
                 fontSize: CGFloat = 40,
                 fontWeight: Font.Weight = Font.Weight.regular,
                 fontDesign: Font.Design = Font.Design.rounded,
                 foregroundColor: Color = Color.white,
                 background: Color = Color.white,
                 backgroundOpacity: CGFloat = 0.9,
                 cornerRadius: CGFloat = 30,
                 blurRadius: CGFloat = 8) -> some View
    {
        
        // clipped image at the original position blurred and rounded corner
        return self
            .overlay(
                ZStack {
                // first layer color white for a beat of glare
                createColorBGLayer(at: at, background: background, cornerRadius: cornerRadius)
                
                // second layer a blur round corner clip from the image
                createIceLayer(at: at, backgroundOpacity: backgroundOpacity, cornerRadius: cornerRadius, blurRadius: blurRadius)
                
                // text on top of the blurred part (use geometry to reset text position)
                createTextLayer(at: at, textString: textString, fontSize: fontSize, fontDesign: fontDesign, fontWeight: fontWeight, foregroundColor: foregroundColor)
            })
    }
}

String extension to calculate text render width and height (without rendering it) so it can be used from within extansion

import Foundation
import UIKit
import SwiftUI

extension String
{
    func sizeUsingFont(fontSize: CGFloat, weight: Font.Weight) -> CGSize
    {
        var uiFontWeight = UIFont.Weight.regular
        
        switch weight {
        case Font.Weight.heavy:
            uiFontWeight = UIFont.Weight.heavy
        case Font.Weight.bold:
            uiFontWeight = UIFont.Weight.bold
        case Font.Weight.light:
            uiFontWeight = UIFont.Weight.light
        case Font.Weight.medium:
            uiFontWeight = UIFont.Weight.medium
        case Font.Weight.semibold:
            uiFontWeight = UIFont.Weight.semibold
        case Font.Weight.thin:
            uiFontWeight = UIFont.Weight.thin
        case Font.Weight.ultraLight:
            uiFontWeight = UIFont.Weight.ultraLight
        case Font.Weight.black:
            uiFontWeight = UIFont.Weight.black
        default:
            uiFontWeight = UIFont.Weight.regular
        }
        
        let font = UIFont.systemFont(ofSize: fontSize, weight: uiFontWeight)
        let fontAttributes = [NSAttributedString.Key.font: font]
        return self.size(withAttributes: fontAttributes)
    }
}

and use it like this:

import Foundation
import SwiftUI

struct TestView: View
{
    let iceCubePos1: CGRect = CGRect(x: 1100, y: 330, width: 500, height: 200)
    let iceCubePos2: CGRect = CGRect(x: 400, y: 130, width: 300, height: 200)
    let iceCubePos3: CGRect = CGRect(x: 760, y: 50, width: 200, height: 150)

    var body: some View
    {
        Image("SomeImageFromAssets")
            .resizable()
            .iceCube(at: iceCubePos1, textString: "Hello again")
            .iceCube(at: iceCubePos2, textString: "One", fontSize: 60.0, fontWeight: Font.Weight.heavy, fontDesign: Font.Design.rounded, foregroundColor: Color.black, background: Color.black, backgroundOpacity: 0.8, cornerRadius: 0, blurRadius: 9)
            .iceCube(at: iceCubePos3, textString: "U2")
            .ignoresSafeArea(.all)
            .scaledToFit()
    }
}

and it should look like this:

iceCube code result

Comments

-1
CustomView()
.background(.ultraThinMaterial)
.opacity(0.8)

I think this code can be applied

3 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From Review
-2

just add Two picture one named "beach" the seconde name "beach1" and try this.enter image description here

            ZStack {
                
                Image("beach").resizable().frame(width: 400, height: 800, alignment: .center)
                
                VStack{
                    
                    HStack{
                        Spacer()
                        Text(" 4,7 ")
                            .font(Font.system(size:25).bold())
                            .foregroundColor(Color.black)
                            .background(
                       
                                Image("beach1").resizable().frame(width: 80, height: 80, alignment: .center)
                                    .blur(radius: 5)
                                )
                            .frame(width: 80, height: 80, alignment: .center)
                            .overlay(
                                RoundedRectangle(cornerRadius: 15).stroke(Color.black, lineWidth: 3)
                                    )
                            .padding(.top,55.0)
                            .padding(.trailing,15.0)
                    }
                    
                    Spacer()
                }

            }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.