10

How can I add a blur view to a label? The label is in front of a UIImage and I wanted the background of the label to be blurred, so that the user can read the text better. I get the Blur effect inside the bounds of the label, but the text itself disappears (maybe also gets blurred, idk why). I also tried to add a label programmatically, but I didn't get it working. I'm thankful for any kind of help!

let blur = UIBlurEffect(style: .Light)
    let blurView = UIVisualEffectView(effect: blur)

    blurView.frame = findATeamLabel.bounds
    findATeamLabel.addSubview(blurView)
beginner_T
  • 417
  • 1
  • 6
  • 21

5 Answers5

7

You can make your own BlurredLabel which can blur/unblur its text. Through a CoreImage blur filter you take the text of the label, blur it in an image, and display that image on top of the label.

class BlurredLabel: UILabel {

    func blur(_ blurRadius: Double = 2.5) {        
        let blurredImage = getBlurryImage(blurRadius)
        let blurredImageView = UIImageView(image: blurredImage)
        blurredImageView.translatesAutoresizingMaskIntoConstraints = false
        blurredImageView.tag = 100
        blurredImageView.contentMode = .center
        blurredImageView.backgroundColor = .white
        addSubview(blurredImageView)
        NSLayoutConstraint.activate([
            blurredImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
            blurredImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
        ])
    }

    func unblur() {
        subviews.forEach { subview in
            if subview.tag == 100 {
                subview.removeFromSuperview()
            }
        }
    }

    private func getBlurryImage(_ blurRadius: Double = 2.5) -> UIImage? {
        UIGraphicsBeginImageContext(bounds.size)
        layer.render(in: UIGraphicsGetCurrentContext()!)
        guard let image = UIGraphicsGetImageFromCurrentImageContext(),
            let blurFilter = CIFilter(name: "CIGaussianBlur") else {
            UIGraphicsEndImageContext()
            return nil
        }
        UIGraphicsEndImageContext()

        blurFilter.setDefaults()

        blurFilter.setValue(CIImage(image: image), forKey: kCIInputImageKey)
        blurFilter.setValue(blurRadius, forKey: kCIInputRadiusKey)

        var convertedImage: UIImage?
        let context = CIContext(options: nil)
        if let blurOutputImage = blurFilter.outputImage,
            let cgImage = context.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
            convertedImage = UIImage(cgImage: cgImage)
        }

        return convertedImage
    }
}

PS: Please make sure to improve this component as of your requirements (for example avoid blurring if already blurred or you could remove the current blurred and apply the blurred again if text has changed).

PSPS: Take into consideration also that applying blur to something makes its content bleeds out, so either set clipsToBounds = false to the BlurredLabel or find out other way to accomplish your visual effect in order to avoid the blurred image looks like is not in same position as the label unblurred text that was previously.

To use it you can simply create a BlurredLabel:

let blurredLabel = BlurredLabel()
blurredLabel.text = "56.00 €"

And on some button tap maybe you could achieve blurring as of blurredLabel.blur() and unblurring as of blurredLabel.unblur().

This is the output achieved with blur() through a blurRadius of 2.5:

To read more about Gaussian Blur, there is a good article on Wikipedia: https://en.wikipedia.org/wiki/Gaussian_blur

denis_lor
  • 6,212
  • 4
  • 31
  • 55
  • 1
    This works great if you call the blur() function after the views have been layed out. However, in my case, the labels are in UITableViewCells and thus I have no way of calling the blur() function when the labels have been layed out. Any ideas? – Ludovico Verniani Jul 26 '20 at 07:20
  • 1
    blur label after initialization causes "Unexpectedly found nil while unwrapping an Optional value" in `UIGraphicsGetCurrentContext()!` – XY L Oct 04 '20 at 16:43
  • how to make the label is blurred after initialization? – XY L Oct 04 '20 at 16:59
  • 1
    The question got answered here, https://stackoverflow.com/questions/64201384/why-does-uigraphicsgetcurrentcontext-return-nil-after-uigraphicsbeginimagecontex/ – XY L Oct 05 '20 at 03:53
2

You could try sending it to the back of the view hierarchy for the label. Try

findATeamLabel.sendSubviewToBack(blurView)

chedabob
  • 5,835
  • 2
  • 24
  • 44
  • 2
    I also tried that, but still everything is just blurred and no text – beginner_T Mar 25 '16 at 18:10
  • @beginner_T I just found out this post after I was having the same challenge and I provided an answer with a custom UILabel that can blur its text, check it here: https://stackoverflow.com/a/62224908/3564632 – denis_lor Jun 05 '20 at 22:27
2

Swift 5 - Blur as UIView extension

extension UIView {
    
    struct BlurableKey {
        static var blurable = "blurable"
    }
    
    func blur(radius: CGFloat) {
        guard superview != nil else { return }
        
        UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
        layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        guard
            let blur = CIFilter(name: "CIGaussianBlur"),
            let image = image
        else {
            return
        }
  
        blur.setValue(CIImage(image: image), forKey: kCIInputImageKey)
        blur.setValue(radius, forKey: kCIInputRadiusKey)
        
        let ciContext  = CIContext(options: nil)
        let boundingRect = CGRect(
            x:0,
            y: 0,
            width: frame.width,
            height: frame.height
        )
        
        guard
            let result = blur.value(forKey: kCIOutputImageKey) as? CIImage,
            let cgImage = ciContext.createCGImage(result, from: boundingRect)
        else {
            return
        }
                
        let blurOverlay = UIImageView()
        blurOverlay.frame = boundingRect
        blurOverlay.image = UIImage(cgImage: cgImage)
        blurOverlay.contentMode = .left
        
        addSubview(blurOverlay)
     
        objc_setAssociatedObject(
            self,
            &BlurableKey.blurable,
            blurOverlay,
            objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
        )
    }
    
    func unBlur() {
        guard
            let blurOverlay = objc_getAssociatedObject(self, &BlurableKey.blurable) as? UIImageView
        else {
            return
        }
        
        blurOverlay.removeFromSuperview()
        
        objc_setAssociatedObject(
            self,
            &BlurableKey.blurable,
            nil,
            objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
        )
    }
    
    var isBlurred: Bool {
        return objc_getAssociatedObject(self, &BlurableKey.blurable) is UIImageView
    }
}
Shalugin
  • 1,092
  • 2
  • 10
  • 15
1

I got it working by adding just a View behind the label (the label is NOT inside that view, just in front of it). Then I added the blur effect to the view... I still think there should be an easier way.

beginner_T
  • 417
  • 1
  • 6
  • 21
0

None of the answers worked for me, the accepted answer dont work with background of different color of white, or label color different of black based on the comments it goes to another question yet the answer made the blur move to the right a lot. So after some reading adjusting the CGRect on the result image.

   class BlurredLabel: UILabel {
    
    var isBlurring = false {
        didSet {
            setNeedsDisplay()
        }
    }
    
    var blurRadius: Double = 8 {
        didSet {
            blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
        }
    }
    
    lazy var blurFilter: CIFilter? = {
        let blurFilter = CIFilter(name: "CIGaussianBlur")
        blurFilter?.setDefaults()
        blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
        return blurFilter
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        layer.isOpaque = false
        layer.needsDisplayOnBoundsChange = true
        layer.contentsScale = UIScreen.main.scale
        layer.contentsGravity = .center
        isOpaque = false
        isUserInteractionEnabled = false
        contentMode = .redraw
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    override func display(_ layer: CALayer) {
        let bounds = layer.bounds
        guard !bounds.isEmpty && bounds.size.width < CGFloat(UINT16_MAX) else {
            layer.contents = nil
            return
        }
        UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, layer.contentsScale)
        if let ctx = UIGraphicsGetCurrentContext() {
            self.layer.draw(in: ctx)
            
            var image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
            if isBlurring {
                blurFilter?.setValue(CIImage(cgImage: image!), forKey: kCIInputImageKey)
                let ciContext = CIContext(cgContext: ctx, options: nil)
                if let blurOutputImage = blurFilter?.outputImage {
                    let boundingRect = CGRect(
                                x:0,
                                y: blurOutputImage.extent.minY,
                                width: blurOutputImage.extent.width,
                                height: blurOutputImage.extent.height
                            )
                    
                    
                    image = ciContext.createCGImage(blurOutputImage, from: boundingRect)
                }
                 
            }
            layer.contents = image
            
        }
        UIGraphicsEndImageContext()
    }
}
cabaji99
  • 1,305
  • 11
  • 9