CALayer Masks

UI doesn’t have to look like a rectangle. You might want a different shape for your icon. You can do that with layer masks. Let’s learn how to set them up.
Download the example files. You’ll find an app with two square buttons.

If you head over to the assets, you see I have a thumbs-up and a thumbs-down image and its inverse image.

I’ll take those images and make them mask images cutting the solid buttons.
Head to the ViewController.swift code. I’ve already added images in the assets folder and assigned them to constants in the view controller.
I also created a function for you configureMask, for adding masks to the buttons.

 func configureMask(button:UIButton, with image:UIImage!){ 

A mask is an area, in our case a CALayer, where its opacity determines visibility to an underlying layer. Solid areas appear, areas with an alpha of zero disappear.
Mask layers are CALayers embedded in another CALayers. Instantiate a new CALayer

let maskLayer = CALayer()

Next, set its frame. This frame will be relative to its view, which will be the button. I’ll start with the origin at (0,0) to start in the upper left corner of the button.

maskLayer.frame.origin = CGPoint(x: 0, y: 0)  

I’ll make the frame the same size as the button. I’ll come back to this in a minute.

maskLayer.frame.size = button.frame.size

Once you have a frame, set the contents of the maskLayer to an image. This is a CGImage so I’ll use the image’s cgImage property to get one.

  maskLayer.contents = image.cgImage

Finally, in the button, add  to the layer’s mask property maskLayer.

   button.layer.mask = maskLayer

I’ll use this method to configure both buttons. For the auto layout I defined, the size of the button is not known until the subviews are laid out. I need that for the mask to be correct. So I’ll add it in the method viewDidLayoutSubviews.

override func viewDidLayoutSubviews() {
        configureMask(button: upButton, with: upImage)
        configureMask(button: downButton, with: downImage)
    }

I’ll change the mask when we tap a button by changing the button’s mask contents. I’ll do that in the upButton IBAction.

@IBAction func upButton(_ sender: UIButton) {
    upButton.layer.mask?.contents = inverseUpImage?.cgImage
    downButton.layer.mask?.contents = downImage?.cgImage
}

The configureMask method replaces the maskLayer. I could use it to change the button’s mask as well.

@IBAction func downButton(_ sender: UIButton) {
    configureMask(button: upButton, with: upImage)
    configureMask(button: downButton, with: inverseDownImage)
}

We are ready to run. You can use any simulator, but I’ll use an iPad 11 inch for this one. Run, and you get two buttons with the first mask.


Click a button. The mask changes.

Click the other button. The mask changes again.

Stop the app. I added two more backgrounds for you. Go to the storyboard and change the button backgrounds to Tile Floor and Wood table. Run again, tap a button,  and you get those photos masked.

That’s the very basics of CALayer masks. With a few lines of code, you can make masks to make any shape, from icons like these to circles and squares. Since masks are a CALayer, You can even make more complex masks by masking the mask’s layer. Play around with masks a bit, You’ll find them a very useful tool to make any view something other than a rectangle.

The Whole Code

Here’s the code for this lesson. If you want to download the full prjoect you can do so on GitHub.

//
//  ViewController.swift
//  CALayerMask Demo
//
//  An exercise file for iOS Development Tips Weekly
//  by Steven Lipton (C)2018, All rights reserved
//  For videos go to http://bit.ly/TipsLinkedInLearning
//  For code go to http://bit.ly/AppPieGithub
//

import UIKit

class ViewController: UIViewController {
    let upImage = UIImage(named:"thumb_up_48pt")
    let downImage = UIImage(named: "thumb_down_48pt")
    let inverseUpImage = UIImage(named: "invert_thumb_up_48pt")
    let inverseDownImage = UIImage(named: "invert_thumb_down_48pt")

   
    
    @IBOutlet weak var upButton: UIButton!
    @IBOutlet weak var downButton: UIButton!
    
    @IBAction func upButton(_ sender: UIButton) {
       sender.layer.mask?.contents = inverseUpImage?.cgImage
        downButton.layer.mask?.contents = downImage?.cgImage
    }
    
    @IBAction func downButton(_ sender: UIButton) {
        configureMask(button: upButton, with: upImage)
        configureMask(button: downButton, with: inverseDownImage)
    }
    
    func configureMask(button:UIButton, with image:UIImage!){
        let masklayer = CALayer()
        masklayer.frame.origin = CGPoint(x: 0, y: 0 )
        masklayer.frame.size = button.frame.size
        masklayer.contents = image.cgImage
        button.layer.mask = masklayer
    }
    
    override func viewDidLayoutSubviews() {
        configureMask(button: upButton, with: upImage)
        configureMask(button: downButton, with: downImage)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
    }

}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.