Search code examples
swiftcalayercore-image

Apply CIFilters on UI elements


I want to apply an CIFilter on an UI element. I tried to apply it onto the views layer via the .filters member. However the filter won`t get applied.


Solution

  • Here's an approach: use UIGraphicsGetImageFromCurrentImageContext to generate a UIImage, apply the filter to that and overlay an image view containing the filtered image over your original component.

    Here's a way to do that with a blur (taken from my blog):

    Getting a blurred representation of a UIView is pretty simple: I need to begin an image context, use the view's layer's renderInContext method to render into the context and then get a UIImage from the context:

    UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
    
    layer.renderInContext(UIGraphicsGetCurrentContext()!)
    
    let image = UIGraphicsGetImageFromCurrentImageContext()
    
    
    UIGraphicsEndImageContext();
    

    Once I have the image populated, it's a fairly standard workflow to apply a Gaussian blur to it:

    guard let blur = CIFilter(name: "CIGaussianBlur") else
    {
        return
    }
    
    blur.setValue(CIImage(image: image), forKey: kCIInputImageKey)
    blur.setValue(blurRadius, forKey: kCIInputRadiusKey)
    
    let ciContext  = CIContext(options: nil)
    
    let result = blur.valueForKey(kCIOutputImageKey) as! CIImage!
    
    let boundingRect = CGRect(x: -blurRadius * 4,
        y: -blurRadius * 4,
        width: frame.width + (blurRadius * 8),
        height: frame.height + (blurRadius * 8))
    
    let cgImage = ciContext.createCGImage(result, fromRect: boundingRect)
    
    
    let filteredImage = UIImage(CGImage: cgImage)
    

    A blurred image will be larger than its input image, so I need to be explicit about the size I require in createCGImage.

    The next step is to add a UIImageView to my view and hide all the other views. I've subclassed UIImageView to BlurOverlay so that when it comes to removing it, I can be sure I'm not removing an existing UIImageView:

    let blurOverlay = BlurOverlay()
    blurOverlay.frame = boundingRect
    
    blurOverlay.image = filteredImage
    
    subviews.forEach{ $0.hidden = true }
    
    
    addSubview(blurOverlay)
    

    When it comes to de-blurring, I want to ensure the last subview is one of my BlurOverlay remove it and unhide the existing views:

    func unBlur()
    {
        if let blurOverlay = subviews.last as? BlurOverlay
        {
            blurOverlay.removeFromSuperview()
    
            subviews.forEach{ $0.hidden = false }
        }
    
    }
    

    Finally, to see if a UIView is currently blurred, I just need to see if its last subview is a BlurOverlay:

    var isBlurred: Bool
    {
        return subviews.last is BlurOverlay
    }