Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply multiple masks to UIView

I have a question about how to apply multiple masks to a UIView that already has a mask.

The situation:
I have a view with an active mask that creates a hole in its top left corner, this is a template UIView that is reused everywhere in the project. Later in the project I would like to be able to create a second hole but this time in the bottom right corner, this without the need to create a completely new UIView.

The problem:
When I apply the bottom mask, it of course replaces the first one thus removing the top hole ... Is there a way to combine them both? And for that matter to combine any existing mask with a new one?

Thank you in advance!

like image 592
Thomas Avatar asked Jun 06 '18 11:06

Thomas


People also ask

How do I combine multiple masks?

Just go to the power window tab and click top left "Linear" "Circle" etc. Masks can intersect, subtract or add, there are two buttons on right of each mask. Another technique is to add a parallel node or layer mixer node and combine different masked nodes.

How to add multiple masks to one node?

Just go to the power window tab and click top left "Linear" "Circle" etc. Masks can intersect, subtract or add, there are two buttons on right of each mask. Another technique is to add a parallel node or layer mixer node and combine different masked nodes. George Deierling wrote: You can have many masks in one node.

How do you add a circle to a mask?

Just go to the power window tab and click top left "Linear" "Circle" etc. Masks can intersect, subtract or add, there are two buttons on right of each mask. Thanks, but Isn't the Power Window available on the Color page only?


3 Answers

Based on @Sharad's answer, I realised that re-adding the view's rect would enable me to combine the original and new mask into one.

Here is my solution:

func cutCircle(inView view: UIView, withRect rect: CGRect) {

    // Create new path and mask
    let newMask = CAShapeLayer()
    let newPath = UIBezierPath(ovalIn: rect)

    // Create path to clip
    let newClipPath = UIBezierPath(rect: view.bounds)
    newClipPath.append(newPath)

    // If view already has a mask
    if let originalMask = view.layer.mask,
        let originalShape = originalMask as? CAShapeLayer,
        let originalPath = originalShape.path {

        // Create bezierpath from original mask's path
        let originalBezierPath = UIBezierPath(cgPath: originalPath)

        // Append view's bounds to "reset" the mask path before we re-apply the original
        newClipPath.append(UIBezierPath(rect: view.bounds))

        // Combine new and original paths
        newClipPath.append(originalBezierPath)

    }

    // Apply new mask
    newMask.path = newClipPath.cgPath
    newMask.fillRule = kCAFillRuleEvenOdd
    view.layer.mask = newMask
}
like image 156
Thomas Avatar answered Oct 23 '22 21:10

Thomas


This is code I have used in my project to create one circle and one rectangle mask in UIView, you can replace the UIBezierPath line with same arc code :

func createCircleMask(view: UIView, x: CGFloat, y: CGFloat, radius: CGFloat, downloadRect: CGRect){
    self.layer.sublayers?.forEach { ($0 as? CAShapeLayer)?.removeFromSuperlayer() }

    let mutablePath      = CGMutablePath()
    mutablePath.addArc(center: CGPoint(x: x, y: y + radius), radius: radius, startAngle: 0.0, endAngle: 2 * 3.14, clockwise: false)
    mutablePath.addRect(view.bounds)
    let path             = UIBezierPath(roundedRect: downloadRect, byRoundingCorners: [.topLeft, .bottomRight], cornerRadii: CGSize(width: 5, height: 5))
    mutablePath.addPath(path.cgPath)

    let mask             = CAShapeLayer()
    mask.path            = mutablePath
    mask.fillRule        = kCAFillRuleEvenOdd
    mask.backgroundColor = UIColor.clear.cgColor


    view.layer.mask      = mask
}

Pass your same UIView, it removes previous layers and applies new masks on same UIView.

Here mask.fillRule = kCAFillRuleEvenOdd is important. If you notice there are 3 mutablePath.addPath() functions, what kCAFillRuleEvenOdd does is, it first creates a hole with the arc then adds Rect of that view's bound and then another mask to create 2nd hole.

like image 34
Sharad Chauhan Avatar answered Oct 23 '22 20:10

Sharad Chauhan


You can do something like the following, if you don't only have "simple shapes" but actual layers from e.g. other views, like UILabel or UIImageView.

let maskLayer = CALayer()
maskLayer.frame = viewToBeMasked.bounds
maskLayer.addSublayer(self.imageView.layer)
maskLayer.addSublayer(self.label.layer)

viewToBeMasked.bounds.layer.mask = maskLayer

So basically I just create a maskLayer, that contains all the other view's layer as sublayer and then use this as a mask.

like image 2
d4Rk Avatar answered Oct 23 '22 20:10

d4Rk