4

I have created a custom view class. I have a image view that I want to blur. I have added the blurring effect in the code below, but obviously it doesn't work because the bounds of the image view is wrong. Due to auto layout haven't calculated the correct sizes for height and width of the image view yet. However which method should I override to access correct position and size attributes?

And what would be the solution for only adding it once?

class ProductListingCell: UICollectionViewCell {

    @IBOutlet weak var nameLabel: UILabel!    
    @IBOutlet weak var sharpImageView: UIImageView!
    @IBOutlet weak var blurImageView: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()


        let blurEffect = UIBlurEffect(style: .Light)
        let visualEffectView = UIVisualEffectView(effect: blurEffect)
        visualEffectView.frame = blurImageView.bounds
        blurImageView.addSubview(visualEffectView)
    }
}

(edit) Maybe an answer:

Just after posting the question I found this method. Would this be the correct way to do it?

override func layoutSubviews() {
    super.layoutSubviews()

    layoutIfNeeded()
    let blurEffect = UIBlurEffect(style: .Light)
    let visualEffectView = UIVisualEffectView(effect: blurEffect)
    visualEffectView.frame = blurImageView.bounds
    blurImageView.addSubview(visualEffectView)
}
LuckyLuke
  • 47,771
  • 85
  • 270
  • 434
  • 1
    The layoutSubviews is what you want. You should add that as an answer. Be aware that this method may be called multiple times, so you will want to keep a reference to the visualEffectView and replace it when called again. – picciano Dec 12 '15 at 01:16
  • Since you're using autolayout, you should be creating autolayout constraints in code instead of setting the frame. – Tom Harrington Dec 12 '15 at 01:17
  • @TomHarrington So you would change the statement where I set the frame = bounds with auto layout? – LuckyLuke Dec 12 '15 at 01:33
  • @picciano Is it correct that I need the `layoutIfNeeded()` call too? (It doesn't work without - so obviously, but why?) – LuckyLuke Dec 12 '15 at 01:34
  • @LuckyLuke yes, only you would need to create the constraints after adding the subview instead of before. – Tom Harrington Dec 12 '15 at 01:45

1 Answers1

3

Yes, your are correct: layoutSubviews() is the way to go.

However, you don't need and actually shouldn't use layoutIfNeeded() inside the layoutSubviews() body. This is because what layoutIfNeeded() does is that checks if a particular internal flag is set on the view (it's set whenever you or the system call setNeedsLayout()) and if that's the case it calls layoutSubviews() on the view (the very same method from where you called it). With other words: You might create a never ending recursion cycle here if you do anything that invalidates the current layout in the layoutSubviews() method before calling layoutIfNeeded().

I tested your code with and without the layoutIfNeeded() call in a sample project and both implementations yield the same (correct) result. Please check if you made any other changes to your code.

When layoutSubviews() is called on your custom view you can be sure that the system has set the view's frame after resolving all layout constraints that constrain the view to its sibling views and its superview. However, you need to call super.layoutSubviews() first in order to properly position all of the view's subviews.

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • I had same problem as OP and normally would give the exact same advice as you have given. However I ended up having to call layoutIfNeeded in layoutSubviews to get all my subview frames to update. This is only a problem when using auto layout. – Brett Jan 05 '19 at 03:48