iOS Invert Mask in Drawrect

iOS invert mask in drawRect

With even odd filling on the shape layer (maskLayer.fillRule = kCAFillRuleEvenOdd;) you can add a big rectangle that covers the entire frame and then add the shape you are masking out. This will in effect invert the mask.

CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
CGMutablePathRef maskPath = CGPathCreateMutable();
CGPathAddRect(maskPath, NULL, someBigRectangle); // this line is new
CGPathAddPath(maskPath, nil, myPath.CGPath);
[maskLayer setPath:maskPath];
maskLayer.fillRule = kCAFillRuleEvenOdd; // this line is new
CGPathRelease(maskPath);
self.layer.mask = maskLayer;

SwiftUI add inverted mask

Here is a demo of possible approach of creating inverted mask, by SwiftUI only, (on example to make a hole in view)

SwiftUI hole mask, reverse mask

func HoleShapeMask(in rect: CGRect) -> Path {
var shape = Rectangle().path(in: rect)
shape.addPath(Circle().path(in: rect))
return shape
}

struct TestInvertedMask: View {

let rect = CGRect(x: 0, y: 0, width: 300, height: 100)
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: rect.width, height: rect.height)
.mask(HoleShapeMask(in: rect).fill(style: FillStyle(eoFill: true)))
}
}

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;

Quartz2D: How to convert a clipping rect to an inverted mask at runtime?

Read about fill rules in the “Filling a Path” section of the Quartz 2D Programming Guide.

In your case, the easiest thing to do is use the even-odd fill rule. Create a path consisting of your small rectangle, and a much larger rectangle:

CGContextBeginPath(ctx);
CGContextAddRect(ctx, CGRectMake(25,25,50,50));
CGContextAddRect(ctx, CGRectInfinite);

Then, intersect this path into the clipping path using the even-odd fill rule:

CGContextEOClip(ctx);

CALayer create transparent round mask

Yeah, kCAFillRuleEvenOdd didn't do it's magic without adding a rect first, here is a working snippet though:

CAShapeLayer *shape = [CAShapeLayer layer];

shape.frame = self.bounds;

CGMutablePathRef pathRef = CGPathCreateMutable();
CGPathAddRect(pathRef, NULL, self.bounds);
CGPathAddEllipseInRect(pathRef, NULL, self.bounds);

shape.fillRule = kCAFillRuleEvenOdd;
shape.path = pathRef;

self.layer.mask = shape;

Mask a UIView with a cut-out circle

So, here’s what I did. I created a custom UIView subclass called BlackoutView, like so:

BlackoutView.h

#import <UIKit/UIKit.h>

@interface BlackoutView : UIView

@property (nonatomic, retain) UIColor *fillColor;
@property (nonatomic, retain) NSArray *framesToCutOut;

@end

BlackoutView.m

#import "BlackoutView.h"

@implementation BlackoutView

- (void)drawRect:(CGRect)rect
{
[self.fillColor setFill];
UIRectFill(rect);

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);

for (NSValue *value in self.framesToCutOut) {
CGRect pathRect = [value CGRectValue];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:pathRect];
[path fill];
}

CGContextSetBlendMode(context, kCGBlendModeNormal);
}

@end

I then instantiate it as normal, and set the properties to the colour of the mask I want to use, and the frames to be cut out of the mask:

[blackMask setFillColor:[UIColor colorWithWhite:0.0f alpha:0.8f]];
[blackMask setFramesToCutOut:@[[NSValue valueWithCGRect:buttonCircleA.frame],
[NSValue valueWithCGRect:buttonCircleB.frame]]];

This could be improved by allowing me to cut out other shapes besides ovals, but it’s fine for my purposes here and would be easily adapted to do so later. Hopefully this helps others!

Use Layer Mask to make parts of the UIView transparent?

Can you share your image?

EDIT:

After looking at the demo code. Your approach will not work as the same. His background is an image.

Try the following:

1- In your App Delegate make the window background colour blue.

self.window?.backgroundColor = UIColor.blueColor()

Replace your code with the following:

self.view.backgroundColor = UIColor.redColor()
let maskImage: UIImage = UIImage(named: "twitterIcon")!
mask = CALayer()
mask?.contents = maskImage.CGImage
mask?.contentsGravity = kCAGravityResizeAspect
mask?.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
mask?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
mask?.position = CGPoint(x: self.view.frame.size.width/2, y: self.view.frame.size.height/2)
self.view.layer.mask = mask!

UIView drawRect: Draw the inverted pixels, make a hole, a window, negative space

Add multiple subpaths to your context, and draw with mode kCGPathEOFill. The Quartz 2D Programming Guide explains in more detail.

// Outer subpath: the whole rect
CGContextAddRect(context, rrect);

// Inner subpath: the area inside the whole rect
CGContextMoveToPoint(context, minx, midy);
...
// Close the inner subpath
CGContextClosePath(context);

// Fill the path
CGContextDrawPath(context, kCGPathEOFill);


Related Topics



Leave a reply



Submit