Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add a CALayer sublayer centered over UIImageView

  • Xcode 7.2
  • Swift 2

I am trying to overlay an Image with what I am calling a "BlurFilterMask". It is a CALayer that I dynamically add with Swift Code, here is the BlurFilterMask:

class BlurFilterMask : CALayer {

    private let GRADIENT_WIDTH : CGFloat = 50.0

    var origin : CGPoint = CGPointZero {
        didSet {
            setNeedsDisplay()
        }
    }

    var diameter : CGFloat = 50.0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override init() {
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func drawInContext(ctx: CGContext) {
         CGContextSaveGState(ctx)

        let clearRegionRadius : CGFloat  = self.diameter * 0.5
        let blurRegionRadius : CGFloat  = clearRegionRadius + GRADIENT_WIDTH

        let baseColorSpace = CGColorSpaceCreateDeviceRGB();
        let colours : [CGFloat] = [0.0, 0.0, 0.0, 0.0,     // Clear region
        0.0, 0.0, 0.0, 0.6] // blur region color
        let colourLocations : [CGFloat] = [0.0, 0.0]
        let gradient = CGGradientCreateWithColorComponents (baseColorSpace, colours, colourLocations, 2)


        CGContextDrawRadialGradient(ctx, gradient, self.origin, clearRegionRadius, self.origin, blurRegionRadius, .DrawsAfterEndLocation);

    }

}

In my UIViewController.viewDidLoad() I call addMaskOverlay() and here is the implementation:

 func addMaskOverlay(){
        let blurFilterMask = BlurFilterMask()

        blurFilterMask.diameter = 80 
        blurFilterMask.frame = heroImageView.bounds
        blurFilterMask.origin = heroImageView.center
        blurFilterMask.shouldRasterize = true

        heroImageView.layer.addSublayer(blurFilterMask)

        blurFilterMask.setNeedsDisplay()
    }

No matter what Simulator I run in, be it iPhone 5 or iPhone 6 or iPad 2, for examples, I always get the same center, width and height:

  • print(heroImageView.center) = (300.0, 67.5)
  • print(CGRectGetWidth(heroImageView.bounds)) = 600.0
  • print(CGRectGetHeight(heroImageView.bounds)) //135.0

But the center of my BlurFilterMask is always somewhere different, depending on the device simulator and it never starts in the center of UIImageView, example in iPhone 5:

enter image description here

And here is iPhone 6:

enter image description here

I thought maybe I need vertically centering and horizontally centering constraints on my CALayer BlurFilterMask so I tried adding constraints:

heroImageView.layer.addSublayer(blurFilterMask)

let xConstraint = NSLayoutConstraint(item: blurFilterMask, attribute: .CenterX, relatedBy: .Equal, toItem: heroImageView, attribute: .CenterX, multiplier: 1, constant: 0)

let yConstraint = NSLayoutConstraint(item: blurFilterMask, attribute: .CenterY, relatedBy: .Equal, toItem: heroImageView, attribute: .CenterY, multiplier: 1, constant: 0)

heroImageView.addConstraint(xConstraint)
heroImageView.addConstraint(yConstraint)

But I cannot add Constraints to a CALayer I do not think, I get an error when I try:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* +[NSLayoutConstraint constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:]: Constraint items must each be an instance of UIView, or NSLayoutGuide.'**

It seems like the issue might be constraints but if I cannot add constraints to a CALayer...

That code didn't help...

Not sure where to go from here, any suggestions would be much appreciated.

like image 360
Brian Ogden Avatar asked Feb 15 '16 02:02

Brian Ogden


1 Answers

The issue is that when viewDidLoad is called the layout has not been done yet so your measurements are being taken from the initial state of the view controller , most likely from the storyboard size if thats what you are using.

You need to call blurFilterMask once layout has completed and the final size of heroImageView is known.

You can do this by overriding viewDidLayoutSubviews which is ...

Called to notify the view controller that its view has just laid out its subviews.

Read the docs as there are some conditions but in your case this does work leaving you with this..

class ViewController: UIViewController {

    @IBOutlet var heroImageView: UIImageView!

    var blurFilterMask:BlurFilterMask! = nil

    func resetMaskOverlay(){

        if blurFilterMask == nil {

            blurFilterMask = BlurFilterMask()

            blurFilterMask.diameter = 120

            heroImageView.layer.addSublayer(blurFilterMask)

        }

        blurFilterMask.frame = heroImageView.bounds
        blurFilterMask.origin = heroImageView.center

    }


    override func viewDidLayoutSubviews() {
        resetMaskOverlay()
    }

}

I assumed this line in BlurFilterMask

    CGContextTranslateCTM(ctx, 0.0, yDiff)*/

Was meant to be commented out as it didn't make much sense leaving...

class BlurFilterMask : CALayer {

    let GRADIENT_WIDTH : CGFloat = 50.0

     var origin : CGPoint = CGPointZero {
        didSet {
            setNeedsDisplay()
        }
    }
    var diameter : CGFloat = 50.0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func drawInContext(ctx: CGContext) {
        CGContextSaveGState(ctx)

        let clearRegionRadius : CGFloat  = self.diameter * 0.5
        let blurRegionRadius : CGFloat  = clearRegionRadius + GRADIENT_WIDTH

        let baseColorSpace = CGColorSpaceCreateDeviceRGB();
        let colours : [CGFloat] = [0.0, 0.0, 0.0, 0.0,     // Clear region
            0.0, 0.0, 0.0, 0.6] // blur region color
        let colourLocations : [CGFloat] = [0.0, 0.0]
        let gradient = CGGradientCreateWithColorComponents (baseColorSpace, colours, colourLocations, 2)


        CGContextDrawRadialGradient(ctx, gradient, self.origin, clearRegionRadius, self.origin, blurRegionRadius, .DrawsAfterEndLocation);

    }

 }

test1-5s test1-6s+

like image 193
Warren Burton Avatar answered Oct 19 '22 13:10

Warren Burton