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
Most Efficient/Realtime Way to Get Pixel Values from iOS Camera Feed in Swift
Create Navbar Programmatically with Button and Title Swift
Custom Sequence for Swift Dictionary
My Uiviews Muck-Up When I Combine Uipangesturerecognizer and Autolayout
Swift Get Specific Value from Firebase Database
Fbsdkcorekit.Framework/Fbsdkcorekit: No Matching Architecture in Universal Wrapper
App Groups Forsecurityapplicationgroupidentifier Returns Nil
How to Get Multiple Buttons from a Single Tableviewcell
Swiftui Multiple Navigationlinks in Form/Sheet - Entry Stays Highlighted
Saving Already Created Live Photos
Import Xctest into a Dynamic Framework
How to Automatically Create an Initializer for a Swift Class
How to Programmatically Check and Open an Existing App in iOS 8
iOS with Parse. Pfuser.Currentuser() Not Getting Cached. Returns Nil After App Restart
Different Cell in Tableview Swift 3