Simply mask a UIView with a rectangle - ios

I want to know how to simply mask the visible area of a UIView of any kind. All the answers/tutorials I've read so far describe masking with an image, gradient or creating round corners which is way more advanced than what I am after.
Example: I have a UIView with the bounds (0, 0, 100, 100) and I want to cut away the right half of the view using a mask. Therefore my mask frame would be (0, 0, 50, 100).
Any idea how to do this simply? I don't want to override the drawrect method since this should be applicable to any UIView.
I've tried this but it just makes the whole view invisible.
CGRect mask = CGRectMake(0, 0, 50, 100);
UIView *maskView = [[UIView alloc] initWithFrame:mask];
viewToMask.layer.mask = maskView.layer;

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;

Thanks for answers guys.
In case someone can't find suitable answer on SO for this question for hours, like i just did, i've assembled a working gist in Swift 2.2 for masking/clipping UIView with CGRect/UIBezierPath:
https://gist.github.com/Flar49/7e977e81f1d2827f5fcd5c6c6a3c3d94
extension UIView {
func mask(withRect rect: CGRect, inverse: Bool = false) {
let path = UIBezierPath(rect: rect)
let maskLayer = CAShapeLayer()
if inverse {
path.append(UIBezierPath(rect: self.bounds))
maskLayer.fillRule = kCAFillRuleEvenOdd
}
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}
func mask(withPath path: UIBezierPath, inverse: Bool = false) {
let path = path
let maskLayer = CAShapeLayer()
if inverse {
path.append(UIBezierPath(rect: self.bounds))
maskLayer.fillRule = kCAFillRuleEvenOdd
}
maskLayer.path = path.cgPath
self.layer.mask = maskLayer
}
}
Usage:
let viewSize = targetView.bounds.size
let rect = CGRect(x: 20, y: 20, width: viewSize.width - 20*2, height: viewSize.height - 20*2)
// Cuts rectangle inside view, leaving 20pt borders around
targetView.mask(withRect: rect, inverse: true)
// Cuts 20pt borders around the view, keeping part inside rect intact
targetView.mask(withRect: rect)
Hope it will save someone some time in the future :)

No need any mask at all.
Just put it into a wrapper view with the smaller frame, and set clipsToBounds.
wrapper.clipsToBounds = true

Very simple example in a Swift ViewController, based on the accepted answer:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let redView = UIView(frame: view.bounds)
view.addSubview(redView)
view.backgroundColor = UIColor.green
redView.backgroundColor = UIColor.red
mask(redView, maskRect: CGRect(x: 50, y: 50, width: 50, height: 50))
}
func mask(_ viewToMask: UIView, maskRect: CGRect) {
let maskLayer = CAShapeLayer()
let path = CGPath(rect: maskRect, transform: nil)
maskLayer.path = path
// Set the mask of the view.
viewToMask.layer.mask = maskLayer
}
}
Output

An optional layer whose alpha channel is used as a mask to select
between the layer's background and the result of compositing the
layer's contents with its filtered background.
#property(retain) CALayer *mask
The correct way to do what you want is to create the maskView of the same frame (0, 0, 100, 100) as the viewToMask which layer you want to mask. Then you need to set the clearColor for the path you want to make invisible (this will block the view interaction over the path so be careful with the view hierarchy).

Swift 5 , thanks #Dan Rosenstark
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let red = UIView(frame: view.bounds)
view.addSubview(red)
view.backgroundColor = UIColor.cyan
red.backgroundColor = UIColor.red
red.mask(CGRect(x: 50, y: 50, width: 50, height: 50))
}
}
extension UIView{
func mask(_ rect: CGRect){
let mask = CAShapeLayer()
let path = CGPath(rect: rect, transform: nil)
mask.path = path
// Set the mask of the view.
layer.mask = mask
}
}

Setting MaskToBounds is not enough, for example, in scenario where you have UIImageView that is positioned inside RelativeLayout. In case that you put your UIImageView to be near top edge of the layout and then you rotate your UIImageView, it will go over layout and it won't be cropped. If you set that flag to true, it will be cropped yes, but it will also be cropped if UIImageView is on the center of layout, and that is not what you want.
So, determinating maskRect is the right approach.

Related

Invert simple UIView mask (cut hole instead of clip to circle) [duplicate]

This question already has answers here:
How can I 'cut' a transparent hole in a UIImage?
(4 answers)
Closed 1 year ago.
I'm trying to avoid CAShapeLayer because I would need to mess with CABasicAnimation, not UIView.animate. So instead, I'm just using UIView's mask property to mask views. Here's my code currently:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imageView = UIImageView(frame: CGRect(x: 50, y: 50, width: 200, height: 300))
imageView.image = UIImage(named: "TestImage")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
view.addSubview(imageView)
let maskView = UIView(frame: CGRect(x: 100, y: 100, width: 80, height: 80))
maskView.backgroundColor = UIColor.blue /// ensure opaque
maskView.layer.cornerRadius = 10
imageView.mask = maskView /// set the mask
}
}
Without imageView.mask = maskView
With imageView.mask = maskView
It makes a portion of the image view visible. However, this is what I want:
Instead of making part of the image view visible, how can I cut a hole in it?
You can create an image view and set that as your mask. Note that this does not lend itself to animation. If you want to animate the mask to different shapes, you should add a mask to your view's CALayer and use CALayerAnimation, as you mention. It's not that bad.
Below I outline how to generate an image with a transparent part (a hole) that you can use as a mask in an image view. If your goal is to animate the size, shape, or position of the hole, however, this won't work. You'd have to regenerate the mask image for every frame, which would be really slow.
Here's how you would get the effect your are after for static views using an image view as a mask:
Use UIGraphicsBeginImageContextWithOptions() UIGraphicsImageRenderer to create an image that is opaque for most of your image, and has a transparent "hole" where you want a hole.
Then install that image in your image view, and make that image view your mask.
The code to create a mostly opaque image with a transparent rounded rect "hole" might look like this:
/**
Function to create a UIImage that is mostly opaque, with a transparent rounded rect "knockout" in it. Such an image might be used ask a mask
for another view, where the transparent "knockout" appears as a hole in the view that is being masked.
- Parameter size: The size of the image to create
- Parameter transparentRect: The (rounded )rectangle to make transparent in the middle of the image.
- Parameter cornerRadius: The corner radius ot use in the transparent rectangle. Pass 0 to make the rectangle square-cornered.
*/
func imageWithTransparentRoundedRect(size: CGSize, transparentRect: CGRect, cornerRadius: CGFloat) -> UIImage? {
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { (context) in
let frame = CGRect(origin: .zero, size: size)
UIColor.white.setFill()
context.fill(frame)
let roundedRect = UIBezierPath(roundedRect: transparentRect, cornerRadius: cornerRadius)
context.cgContext.setFillColor(UIColor.clear.cgColor)
context.cgContext.setBlendMode(.clear)
roundedRect.fill()
}
return image
}
And a viewDidLoad method that installs a UIImageView with a mask image view with a hole in it might look like this:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .cyan
let size = CGSize(width: 200, height: 300)
let origin = CGPoint(x: 50, y: 50)
let frame = CGRect(origin: origin, size: size)
let imageView = UIImageView(frame: frame)
imageView.image = UIImage(named: "TestImage")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
view.addSubview(imageView)
imageView.layer.borderWidth = 2
//Create a mask image view the same size as the (image) view we will be masking
let maskView = UIImageView(frame: imageView.bounds)
//Build an opaque UIImage with a transparent "knockout" rounded rect inside it.
let transparentRect = CGRect(x: 100, y: 100, width: 80, height: 80)
let maskImage = imageWithTransparentRoundedRect(size: size, transparentRect: transparentRect, cornerRadius: 20)
//Install the image with the "hole" into the mask image view
maskView.image = maskImage
//Make the maskView the ImageView's mask
imageView.mask = maskView /// set the mask
}
}
I created a sample project using the code above. You can download it from Github here:
https://github.com/DuncanMC/UIImageMask.git
I just updated the project to also show how to do the same thing using a CAShapeLayer as a mask on the image view's layer. Doing it that way, it's possible to animate changes to the mask layer's path.
The new version has a segmented control that lets you pick whether to mask the image view using a UIImage in the view's mask property, or via a CAShapeLayer used as a mask on the image view's layer.
For the CAShapeLayer version, the mask layer's path is a rectangle the size of the whole image view, with a second, smaller rounded rectangle drawn inside it. The winding rule on the shape layer is then set to the "even/odd" rule, meaning that if you have to cross an even number of shape boundaries to get to a point, it is considered outside the shape. That enables you to create hollow shapes like we need here.
When you select the layer mask option, it enables an animation button that animates random changes to the "cutout" transparent rectangle in the mask.
The function that creates the mask path looks like this:
func maskPath(transparentRect: CGRect, cornerRadius: CGFloat) -> UIBezierPath {
let fullRect = UIBezierPath(rect: maskLayer.frame)
let roundedRect = UIBezierPath(roundedRect: transparentRect, cornerRadius: cornerRadius)
fullRect.append(roundedRect)
return fullRect
}
And the function that does the animation looks like this:
#IBAction func handleAnimateButton(_ sender: Any) {
//Create a CABasicAnimation that will change the path of our maskLayer
//Use the keypath "path". That tells the animation object what property we are animating
let animation = CABasicAnimation(keyPath: "path")
animation.autoreverses = true //Make the animation reverse back to the oringinal position once it's done
//Use ease-in, ease-out timing, which looks smooth
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.duration = 0.3 //Make each step in the animation last 0.3 seconds.
let transparentRect: CGRect
//Randomly either animate the transparent rect to a different shape or shift it
if Bool.random() {
//Make the transparent rect taller and skinnier
transparentRect = self.transparentRect.inset(by: UIEdgeInsets(top: -20, left: 20, bottom: -20, right: 20))
} else {
//Shift the transparent rect to by a random amount that still says inside the image view's bounds.
transparentRect = self.transparentRect.offsetBy(dx: CGFloat.random(in: -100...20), dy: CGFloat.random(in: -100...100))
}
let cornerRadius: CGFloat = CGFloat.random(in: 0...30)
//install the new path as the animation's `toValue`. If we dont specify a `fromValue` the animation will start from the current path.
animation.toValue = maskPath(transparentRect: transparentRect, cornerRadius: cornerRadius).cgPath
//add the animation to the maskLayer. Since the animation's `keyPath` is "path",
//it will animate the layer's "path" property to the "toValue"
maskLayer.add(animation, forKey: nil)
//Since we don't actually change the path on the mask layer, the mask will revert to it's original path once the animation completes.
}
The results (using my own sample image) look like this:
A sample of the CALayer based mask animation looks like this:

Cut transparent hole in UIView using UIImageView

I'm trying to create the image below for my QR camera view.
I have an image for the camera overlay (which is the square in the middle of the screen).
Ive looked at similar topics on stackoverflow and found out how to cut out the camera overlay image from the black background (0.75% transparent) so that it leaves the empty space in the middle, however I'm having some real issues with its placement, and I cant find out what is behaving so weirdly.
Here is the code that I use to create the background black image and also to cut out the square in the center:
// Create a view filling the screen.
let backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
// Set a semi-transparent, black background.
backgroundView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.75)
// Create the initial layer from the view bounds.
let maskLayer = CAShapeLayer()
maskLayer.frame = backgroundView.bounds
maskLayer.fillColor = UIColor.black.cgColor
// Create the path.
let path = UIBezierPath(rect: backgroundView.bounds)
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
// Append the overlay image to the path so that it is subtracted.
path.append(UIBezierPath(rect: camOverlayImageView.frame))
maskLayer.path = path.cgPath
// Set the mask of the view.
backgroundView.layer.mask = maskLayer
// Add the view so it is visible.
self.view.addSubview(backgroundView)
The camOverlayImageView is created in storyboard and all I'm using on it are constraints to center it both vertically and horizontally to the superview like this:
However, when I do this, this is what I'm getting on the device:
If anyone might know what might be causing this or how to fix it, it would be greatly appreciated as I can't seem to find it.
I can of course manually move the frame and offset it like this but that isn't the correct way to do this:
let overlayFrame = camOverlayImageView.frame
// Append the overlay image to the path so that it is subtracted.
path.append(UIBezierPath(rect: CGRect(
x: overlayFrame.origin.x + 18,
y: overlayFrame.origin.y - 6,
width: overlayFrame.size.width,
height: overlayFrame.size.height))
)
maskLayer.path = path.cgPath
Instead of doing what I was previously doing:
path.append(UIBezierPath(rect: camOverlayImageView.frame))
maskLayer.path = path.cgPath
Most likely...
You are creating your mask too early - before auto-layout has sized / positioned the views.
Try it like this:
class HoleInViewController: UIViewController {
#IBOutlet var camOverlayImageView: UIView!
let backgroundView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor.black.withAlphaComponent(0.75)
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(backgroundView)
NSLayoutConstraint.activate([
backgroundView.topAnchor.constraint(equalTo: view.topAnchor),
backgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
backgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
backgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Create the initial layer from the view bounds.
let maskLayer = CAShapeLayer()
maskLayer.frame = backgroundView.bounds
maskLayer.fillColor = UIColor.black.cgColor
// Create the path.
let path = UIBezierPath(rect: backgroundView.bounds)
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
// Append the overlay image to the path so that it is subtracted.
path.append(UIBezierPath(rect: camOverlayImageView.frame))
maskLayer.path = path.cgPath
// Set the mask of the view.
backgroundView.layer.mask = maskLayer
}
}

How to cut portion of CALayer in iOS Swift? [duplicate]

This question already has answers here:
How can I 'cut' a transparent hole in a UIImage?
(4 answers)
Closed 3 years ago.
I am trying to create a QR Reader. For that I am showing a rectOfInterest with some CALayer for visual representation. I want to show a box with some border at the corners and black background with some opacity so hide the other view from the AVCaptureVideoPreviewLayer. What I have achieved till now looks like this:
As you can see the CALayer is there but I want to cut the box portion of the layer so that that blackish thing does not come there. The code I am using to do this is like below:
func createTransparentLayer()->CALayer{
let shape = CALayer()
shape.frame = self.scanView.layer.bounds
shape.backgroundColor = UIColor.black.cgColor
shape.opacity = 0.7
return shape
}
I looked into other questions for this, seems like you have the mask the layer with the cut portion. So I subclassed the CALayer and cleared the context in drawInContext and set the mask property of the super layer to this. After that I get nothing. Everything is invisible there. What is wrong in this?
The code I tried is this:
class TransparentLayer: CALayer {
override func draw(in ctx: CGContext) {
self.backgroundColor = UIColor.black.cgColor
self.opacity = 0.7
self.isOpaque = true
ctx.clear(CGRect(x: superlayer!.frame.size.width / 2 - 100, y: superlayer!.frame.size.height / 2 - 100, width: 200, height: 200))
}
}
then set the mask property like this:
override func viewDidLayoutSubviews() {
self.rectOfInterest = CGRect(x: self.scanView.layer.frame.size.width / 2 - 100, y: self.scanView.layer.frame.size.height / 2 - 100, width: 200, height: 200)
scanView.rectOfInterest = self.rectOfInterest
let shapeLayer = self.createFrame()
scanView.doInitialSetup()
self.scanView.layer.mask = self.createTransparentLayer()
self.scanView.layer.addSublayer(shapeLayer)
}
here the shapeLayer is the bordered corner in the screenshot. How can I achieve this?
I have added view in my view controller which is centre align vertically and horizontally. Also fixed height and width to 200. Then created extension of UIView and added following code:
extension UIView {
func strokeBorder() {
self.backgroundColor = .clear
self.clipsToBounds = true
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = UIBezierPath(rect: self.bounds).cgPath
self.layer.mask = maskLayer
let line = NSNumber(value: Float(self.bounds.width / 2))
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.white.cgColor
borderLayer.lineDashPattern = [line]
borderLayer.lineDashPhase = self.bounds.width / 4
borderLayer.lineWidth = 10
borderLayer.frame = self.bounds
self.layer.addSublayer(borderLayer)
}
}
Use:
By using outlet of view and call method to set border as you have request.
self.scanView.strokeBorder()
To clear the backgroundColor respective to mask view, I have added following code to clear it.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.scanView.strokeBorder()
self.backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
// Draw a graphics with a mostly solid alpha channel
// and a square of "clear" alpha in there.
UIGraphicsBeginImageContext(self.backgroundView.bounds.size)
let cgContext = UIGraphicsGetCurrentContext()
cgContext?.setFillColor(UIColor.white.cgColor)
cgContext?.fill(self.backgroundView.bounds)
cgContext?.clear(CGRect(x:self.scanView.frame.origin.x, y:self.scanView.frame.origin.y, width: self.scanView.frame.width, height: self.scanView.frame.height))
let maskImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// Set the content of the mask view so that it uses our
// alpha channel image
let maskView = UIView(frame: self.backgroundView.bounds)
maskView.layer.contents = maskImage?.cgImage
self.backgroundView.mask = maskView
}
Output:
I'm not using camera in background.

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!
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
}
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.
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.

CAShapeLayer cutout in UIView

My goal is to create a pretty opaque background with a clear rounded rect inset
let shape = CGRect(x: 0, y: 0, width: 200, height: 200)
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: shape, cornerRadius: 16).cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.fillRule = kCAFillRuleEvenOdd
let background = UIView()
background.backgroundColor = UIColor.black.withAlphaComponent(0.8)
view.addSubview(background)
constrain(background) {
$0.edges == $0.superview!.edges
}
background.layer.addSublayer(maskLayer)
// background.layer.mask = maskLayer
When I uncomment background.layer.mask = maskLayer, the view is totally clear. When I have it commented out, I see the semi-opaque background color but no mask cutout
Any idea what I'm doing wrong here? Thanks!
Here is a playground that I think implements your intended effect (minus .edges and constrain - that appear to be your own extensions).
a) used a red background instead of black with alpha (just to make the effect stand out)
b) set maskLayer.backgroundColor instead of maskLayer.fillColor
Adding the maskLayer as another layer to uiview is superfluous (as indicated by #Ken) but seems to do no harm.
import UIKit
import PlaygroundSupport
let bounds = CGRect(x: 0, y: 0, width: 400, height: 200)
let uiview = UIView(frame: bounds)
uiview.backgroundColor = UIColor.red.withAlphaComponent(0.8)
PlaygroundPage.current.liveView = uiview
let shape = CGRect(x: 100, y: 50, width: 200, height: 100)
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: shape, cornerRadius: 16).cgPath
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.fillRule = kCAFillRuleEvenOdd
uiview.layer.mask = maskLayer
image of clear inset
On the other hand, if you intended a picture frame / colored-envelope-with-transparent-window effect like below, then see gist
image of picture frame
Your mask Layer has no size.
Add this line maskLayer.frame = shape
and you don't need background.layer.addSublayer(maskLayer)

Resources