How to Apply Multiple Masks to Uiview

How to apply multiple masks to UIView

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
}

Add multiple mask to CALayer (1 is for adding corner)

I ended up using this answer on stackoverflow with some custom to add shadow and corner on it.

Here is playground gist

Masking multiple iOS UIViews with a single UIView

Looks like the answer is no, you can't use the same UIView instance to mask multiple views: apparently the masking view's layer is being integrated into the layer hierarchy of the view that is being masked.

The best way would be to create a new UIView mask instance for each of the views you are trying to mask. Alternatively you could copy the existing view via NSKeyedArchiver:

let archivedData = NSKeyedArchiver.archivedDataWithRootObject(viewMask)
let viewMaskCopy = NSKeyedUnarchiver.unarchiveObjectWithData(archivedData) as! UIView

Although I would suggest simply creating new masking views instances the way you instantiated viewMask in the first place.

You may also group the masked views in a containing UIView in storyboard, and apply the mask to that view instead.

Using an UIView as a Mask in another UIView on Swift

You can set the alpha property from your mask view and add in front of the other view, for instance:

let maskView = UIView()
maskView.backgroundColor = UIColor(white: 0, alpha: 0.5) //you can modify this to whatever you need
maskView.frame = CGRect(x: 0, y: 0, width: imageView.frame.width, height: imageView.frame.height)

yourView.addSubview(maskView)

EDIT: Now that you edited your question with an image, now I see what you need, so here is how you can accomplish that.

func setMask(with hole: CGRect, in view: UIView){

// Create a mutable path and add a rectangle that will be h
let mutablePath = CGMutablePath()
mutablePath.addRect(view.bounds)
mutablePath.addRect(hole)

// Create a shape layer and cut out the intersection
let mask = CAShapeLayer()
mask.path = mutablePath
mask.fillRule = kCAFillRuleEvenOdd

// Add the mask to the view
view.layer.mask = mask

}

With this function, all you need is to have a view and create a shape that it's going to be a hole in that view, for instance:

// Create the view (you can also use a view created in the storyboard)
let newView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height))
newView.backgroundColor = UIColor(white: 0, alpha: 1)

// You can play with these values and find one that fills your need
let rectangularHole = CGRect(x: view.bounds.width*0.3, y: view.bounds.height*0.3, width: view.bounds.width*0.5, height: view.bounds.height*0.5)

// Set the mask in the created view
setMask(with: rectangularHole, in: newView)

Simply mask a UIView with a rectangle

Thanks to the link from MSK, this is the way I went with which works well:

// Create a mask layer and the frame to determine what will be visible in the view.
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGRect maskRect = CGRectMake(0, 0, 50, 100);

// Create a path with the rectangle in it.
CGPathRef path = CGPathCreateWithRect(maskRect, NULL);

// Set the path to the mask layer.
maskLayer.path = path;

// Release the path since it's not covered by ARC.
CGPathRelease(path);

// Set the mask of the view.
viewToMask.layer.mask = maskLayer;

Custom layer mask for UIView

You shouldn't be creating or updating layers in drawRect:. The layoutSubviews method is an appropriate time for that. You can also reuse the mask. You also have some retain/release problems.

- (void)layoutSubviews {
[super layoutSubviews];
[self layoutMask];
}

- (void)layoutMask {
CAShapeLayer *maskLayer = [self subviewMaskLayer];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:_subView.bounds
byRoundingCorners:UIRectCornerBottomRight cornerRadii:(CGSize){150.0, 150.0}];
maskLayer.path = path.CGPath;
}

- (CAShapeLayer *)subviewMaskLayer {
CAShapeLayer *mask = (CAShapeLayer *)_subView.layer.mask;
if (mask == nil) {
mask = [CAShapeLayer layer];
_subView.layer.mask = mask;
}
return mask;
}

Masking a UIView

#import <QuartzCore/QuartzCore.h>

needed to be added.



Related Topics



Leave a reply



Submit