I want to apply a chat bubble mask to an image view, and I would like the mask image to scale to fit in the frame of the image view. This is the code I am using to create and apply the mask:
let mask = CALayer()
let maskImage = UIImage(named: "chatGif")!
mask.contents = maskImage.CGImage
mask.frame = CGRectMake(0, 0, imageView.frame.width, imageView.frame.height)
imageView.layer.mask = mask
imageView.layer.masksToBounds = true
And this is my mask:
However this is what I get:
And when I add slicing to the mask image (i.e. create resizable cap insets) I get this:
I made a minimal project that demonstrates the issue and put it here
I do not fundamentally understand why the right and bottom sides of my mask image are getting clipped out of view. The mask's frame and bounds match the image view's, and the image used for the mask is actually smaller than the image view, so if anything I would expect it to not cover the whole thing, rather than getting partially clipped.
Can someone please explain what's happening here? How do I prevent this behavior?
EDIT: I've been playing around with this some more, and the results make a little more sense when I set the mask's size to match the maskImage
size like so:
let mask = CALayer()
let maskImage = UIImage(named: "chatGif")!
mask.contents = maskImage.CGImage
mask.frame = CGRectMake(0, 0, maskImage.size.width, maskImage.size.height)
imageView.layer.mask = mask
imageView.layer.masksToBounds = true
This gives me:
Or with slicing on the mask asset, I get the image in J.Hunter's answer. This is still not what I want because the mask is not being stretched to fill the imageView
and a large portion around the sides is masked out that shouldn't be, but I can at least understand what's happening in this case. I am setting the mask's frame to a size smaller than the imageView's frame so of course it doesn't fill the entire area.
However I would expect setting the frame of the mask to match that of the imageView
would make the right and bottom edges of the mask that I can see in that last screenshot line up with the right and bottom edges of the imageView
. If I print out the masks's frame, bounds, and contentsRect
, they are exactly what I would expect: the frame and bounds match the imageView
and the contentsRect
is (0.0, 0.0, 1.0, 1.0)
. It was my understanding that a CALayer
will display a portion of its contents based on the contentsRect
, so unless that rect was smaller than 1.0x1.0, the entire contents image should be displayed. Right? But why is that not what I'm seeing?
Okay, I finally figured out what's going on. I added the same debug print statements to the sample project I made that I had put in my actual project and discovered that the imageView
s frame at the time I was setting the mask was much larger that I expected it, and not its final display size. That makes sense because in the sample project I was setting the mask in viewDidLoad
, before any subviews had been laid out.
I didn't realize this was the case when printing out values in my actual project because my UIImageView
is in a UITableViewCell
, so it's un-initialized size was the same as the placeholder size in the storyboard, which wasn't that much larger than the final size, so it didn't read as obviously too big.
Now if only I could get the last 4 hours of my life back.
EDIT: To clarify, the initial code works fine if it's run after the image view has it's final layout size set. So you should set the mask in viewDidLayoutSubviews
, or if it's a table/collection cell you can check if the frame size has changed in layoutSubviews.