Using an UIView as a Mask in another UIView on Swift - swift

I am developing an App on xCode with Swift. My screen has an Image of an animal (outlet "backImage"), over this image I have a view (outlet "coverView"), color black, which covers all the screen. So so far you can't see the animal image, because the "coverView" is in front of it. Then I have another view (outlet "maskView") which is smaller and it's over the big view "coverView". What I want is to use this "maskView" as mask and therefor see the "backImage" through it, like a window.
Is there anyone out there able to figure this out?
Here is my screen, I want to see the woman character behind the big gray view through the smaller white view:

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)

Thank you, #Alexandre Lara! You did it!
Here goes the solution:
#IBOutlet weak var windowView: UIView!
#IBOutlet weak var bigCoverView: UIView!
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
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let rectangularHole = windowView.frame.integral
// Set the mask in the created view
setMask(with: rectangularHole, in: bigCoverView!)
}

Related

does anybody understand this paradox with swift frames?

In UIKIT I have two uiview's main view and uiview installed with storyboard at top with high in 1/3 of main View.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var TopView: UIView!
#IBOutlet weak var MiddleView: UIView!
#IBOutlet weak var BottomView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let t = Vvp(inView: TopView)
TopView.addSubview(t)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: TopView.frame.maxX, y: 0))
bezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.lineWidth = 1.0
TopView.layer.addSublayer(shapeLayer)
}
}
second view:
func Vvp(inView: UIView)-> UIView {
let viewWithBeizer = UIView(frame: inView.frame)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: inView.frame.maxX, y: 0))
bezierPath.close()
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
// apply other properties related to the path
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 1.0
viewWithBeizer.layer.addSublayer(shapeLayer)
return viewWithBeizer
}
both views work with the same frame, at storyboard all borders are at zero
why lines are not the same?
The problem has nothing to do with where the lines are being drawn...
The issue is that you are referring to frame when you should be using bounds, and you're setting the frames before auto-layout has configured your views.
Based on your screen-shots, you are laying out your views in Storyboard based on an iPhone model with a Notch... so, in viewDidLoad() your TopView has the frame that was set in Storyboard.
This is how it looks using an iPhone 13 Pro in Storyboard:
As you can see, even though the yellow TopView is constrained to the top of the safe area, its Y position is 44. So, your code in your func Vvp(inView: UIView) is setting the Frame Y-position to 44, instead of Zero.
If you add these 4 lines at the end of viewDidLoad():
TopView.layer.addSublayer(shapeLayer)
// move t (from Vvp(inView: TopView))
// 40-pts to the right
t.frame.origin.x += 40.0
// give it an orange background color
t.backgroundColor = .orange
// allow it to show outside the bounds of TopView
TopView.clipsToBounds = false
// bring TopView to the front of the view hierarchy
view.bringSubviewToFront(TopView)
The output on an iPad Touch 7th Gen looks like this:
as you can see, TopView's subview (the orange view) is much larger than TopView, and is showing up where you told it to: 44-pts from the top of TopView.
To use the code the way you've written it, you need to call that func - along with the shapeLayer code for TopView - later in the controller's lifecycle... such as in viewDidLayoutSubviews(). If you do that, though, you need to remember it will be called multiple times (any time the main view changes, such as on device rotation), so you'll want to make sure you don't repeatedly add new subviews and layers.
Here's a quick modification of your code:
class ViewController: UIViewController {
#IBOutlet weak var TopView: UIView!
#IBOutlet weak var MiddleView: UIView!
#IBOutlet weak var BottomView: UIView!
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if TopView.subviews.count == 0 {
// we haven't added the subview or shape layer,
// so let's do that here
let t = Vvp(inView: TopView)
TopView.addSubview(t)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: TopView.frame.maxX, y: 0))
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.red.cgColor
shapeLayer.lineWidth = 1.0
TopView.layer.addSublayer(shapeLayer)
}
}
func Vvp(inView: UIView)-> UIView {
let viewWithBeizer = UIView(frame: inView.bounds)
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: inView.bounds.maxX, y: 0))
let shapeLayer = CAShapeLayer()
shapeLayer.path = bezierPath.cgPath
// apply other properties related to the path
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.fillColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 1.0
viewWithBeizer.layer.addSublayer(shapeLayer)
return viewWithBeizer
}
}
Result (blue line is not visible, because we've added the red line on top of it):
A better approach, though, is to A) use auto-layout constraints, and B) handle your shapeLayer logic inside a custom UIView subclass -- but that's another topic.
I think that this is a bug with iPod touch 7' emulator - with another emulators code works well. Below you can see the code from my question where I add 2px to red line.

swift ImageView with low alpha and subImageView transparent showing parent view but with 1 alpha

I'm having trouble setting my subImageViews to show parent ImageView with alpha 1 in their area, but all around I need to have alpha 0.25. Do you have any ideas?
I've tried to do it through View, now ImageView but can't find a solution.
16 squares in the middle need to show a clear image, after pressing the button "Start" they would crop their area and send it to other VC.
Thanks for help
So I've been playing around a bit and got solution (also thanks to #iOSDev link to article).
If anyone will need similar kind of thing, here it is :
#IBOutlet weak var alphaView: UIView!
#IBOutlet weak var pickedImage: UIImageView!
#IBOutlet weak var bigStackView: UIStackView!
override func viewWillAppear(_ animated: Bool) {
pickedImage.image = imagePicked
// Set white background color with custom alpha
alphaView.backgroundColor = UIColor(white: 255/255, alpha: 0.85)
// Create the initial layer from the stackView bounds.
let maskLayer = CAShapeLayer()
maskLayer.frame = alphaView.bounds
// Create the frame to cover whole stackView
let rect = CGRect(
x: bigStackView.frame.minX,
y: bigStackView.frame.minY,
width: bigStackView.bounds.width,
height: bigStackView.bounds.height)
// Create the path
let path = UIBezierPath(rect: alphaView.bounds)
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
// Append the framer to the path so that it is subtracted
path.append(UIBezierPath(rect: rect))
maskLayer.path = path.cgPath
// Set the mask of the alphaview
alphaView.layer.mask = maskLayer
}
pickedImage is background image view which is covered by aplhaView used only to make it foggy and bigStackView is 16 square in middle to determinate position of wanted square. Might aswell use explicit CGPoint & CGSize Value.
Screenshot from simulator

Can CAShapeLayer shapes center itself in a subView?

I created an arbitrary view
let middleView = UIView(
frame: CGRect(x: 0.0,
y: view.frame.height/4,
width: view.frame.width,
height: view.frame.height/4))
middleView.backgroundColor = UIColor.blue
view.addSubview(middleView)
Then I created a circle using UIBezierPath; however when I set the position to middleView.center, the circle is far off to the bottom of the view. Can you set the position in the center of a subview?
let shapeLayer = CAShapeLayer()
let circlePath = UIBezierPath(
arcCenter: .zero,
radius: 100,
startAngle: CGFloat(0).toRadians(),
endAngle: CGFloat(360).toRadians(),
clockwise: true)
shapeLayer.path = circlePath.cgPath
shapeLayer.strokeColor = UIColor.purple.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.position = middleView.center
middleView.layer.addSublayer(shapeLayer)
How do I center this circle in that view?
You have two problems.
First, you are setting shapeLayer.position = middleView.center. The center of a view is is the superview's geometry. In other words, middleView.center is relative to view, not to middleView. But then you're adding shapeLayer as a sublayer of middleView.layer, which means shapeLayer needs a position that is in middleView's geometry, not in view's geometry. You need to set shapeLayer.position to the center of middleView.bounds:
shapeLayer.position = CGPoint(x: middleView.bounds.midX, y: middleView.bounds.midY)
Second, you didn't say where you're doing all this. My guess is you're doing it in viewDidLoad. But that is too early. In viewDidLoad, the views loaded from the storyboard still have the frames they were given in the storyboard, and haven't been laid out for the current device's screen size yet. So it's a bad idea to look at frame (or bounds or center) in viewDidLoad if you don't do something to make sure that things will be laid out correctly during the layout phase. Usually you do this by setting the autoresizingMask or creating constraints. Example:
let middleView = UIView(
frame: CGRect(x: 0.0,
y: view.frame.height/4,
width: view.frame.width,
height: view.frame.height/4))
middleView.backgroundColor = UIColor.blue
middleView.autoresizingMask = [.flexibleWidth, .flexibleHeight, .flexibleTopMargin, .flexibleBottomMargin]
view.addSubview(middleView)
However, shapeLayer doesn't belong to a view, so it doesn't have an autoresizingMask and can't be constrained. You have to lay it out in code. You could do that, but it's better to just use a view to manage the shape layer. That way, you can use autoresizingMask or constraints to control the layout of the shape, and you can set it up in viewDidLoad.
let circleView = CircleView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
circleView.center = CGPoint(x: middleView.bounds.midX, y: middleView.bounds.midY)
circleView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin, .flexibleBottomMargin]
circleView.shapeLayer.strokeColor = UIColor.purple.cgColor
circleView.shapeLayer.fillColor = nil
middleView.addSubview(circleView)
...
class CircleView: UIView {
override class var layerClass: AnyClass { return CAShapeLayer.self }
var shapeLayer: CAShapeLayer { return layer as! CAShapeLayer }
override func layoutSubviews() {
super.layoutSubviews()
shapeLayer.path = UIBezierPath(ovalIn: bounds).cgPath
}
}
Result:
And after rotating to landscape:

UIBezierPath: How to add a border around a view with rounded corners?

I am using UIBezierPath to have my imageview have round corners but I also want to add a border to the imageview. Keep in mind the top is a uiimage and the bottom is a label.
Currently using this code produces:
let rectShape = CAShapeLayer()
rectShape.bounds = myCell2.NewFeedImageView.frame
rectShape.position = myCell2.NewFeedImageView.center
rectShape.path = UIBezierPath(roundedRect: myCell2.NewFeedImageView.bounds,
byRoundingCorners: .TopRight | .TopLeft,
cornerRadii: CGSize(width: 25, height: 25)).CGPath
myCell2.NewFeedImageView.layer.mask = rectShape
I want to add a green border to that but I cant use
myCell2.NewFeedImageView.layer.borderWidth = 8
myCell2.NewFeedImageView.layer.borderColor = UIColor.greenColor().CGColor
because it cuts off the top left and top right corner of the border as seen in this image:
Is there a way too add in a border with UIBezierPath along with my current code?
You can reuse the UIBezierPath path and add a shape layer to the view. Here is an example inside a view controller.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create a view with red background for demonstration
let v = UIView(frame: CGRectMake(0, 0, 100, 100))
v.center = view.center
v.backgroundColor = UIColor.redColor()
view.addSubview(v)
// Add rounded corners
let maskLayer = CAShapeLayer()
maskLayer.frame = v.bounds
maskLayer.path = UIBezierPath(roundedRect: v.bounds, byRoundingCorners: .TopRight | .TopLeft, cornerRadii: CGSize(width: 25, height: 25)).CGPath
v.layer.mask = maskLayer
// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path // Reuse the Bezier path
borderLayer.fillColor = UIColor.clearColor().CGColor
borderLayer.strokeColor = UIColor.greenColor().CGColor
borderLayer.lineWidth = 5
borderLayer.frame = v.bounds
v.layer.addSublayer(borderLayer)
}
}
The end result looks like this.
Note that this only works as expected when the view's size is fixed. When the view can resize, you will need to create a custom view class and resize the layers in layoutSubviews.
As it says above:
It is not easy to do this perfectly.
Here's a drop-in solution.
This
correctly addresses the issue that you are drawing HALF OF THE BORDER LINE
is totally usable in autolayout
completely re-works itself when the size of the view changes or animates
is totally IBDesignable - you can see it in realtime in your storyboard
for 2019 ...
#IBDesignable
class RoundedCornersAndTrueBorder: UIView {
#IBInspectable var cornerRadius: CGFloat = 10 {
didSet { setup() }
}
#IBInspectable var borderColor: UIColor = UIColor.black {
didSet { setup() }
}
#IBInspectable var trueBorderWidth: CGFloat = 2.0 {
didSet { setup() }
}
override func layoutSubviews() {
setup()
}
var border:CAShapeLayer? = nil
func setup() {
// make a path with round corners
let path = UIBezierPath(
roundedRect: self.bounds, cornerRadius:cornerRadius)
// note that it is >exactly< the size of the whole view
// mask the whole view to that shape
// note that you will ALSO be masking the border we'll draw below
let mask = CAShapeLayer()
mask.path = path.cgPath
self.layer.mask = mask
// add another layer, which will be the border as such
if (border == nil) {
border = CAShapeLayer()
self.layer.addSublayer(border!)
}
// IN SOME APPROACHES YOU would INSET THE FRAME
// of the border-drawing layer by the width of the border
// border.frame = bounds.insetBy(dx: borderWidth, dy: borderWidth)
// so that when you draw the line, ALL of the WIDTH of the line
// DOES fall within the actual mask.
// here, we will draw the border-line LITERALLY ON THE EDGE
// of the path. that means >HALF< THE LINE will be INSIDE
// the path and HALF THE LINE WILL BE OUTSIDE the path
border!.frame = bounds
let pathUsingCorrectInsetIfAny =
UIBezierPath(roundedRect: border!.bounds, cornerRadius:cornerRadius)
border!.path = pathUsingCorrectInsetIfAny.cgPath
border!.fillColor = UIColor.clear.cgColor
// the following is not what you want:
// it results in "half-missing corners"
// (note however, sometimes you do use this approach):
//border.borderColor = borderColor.cgColor
//border.borderWidth = borderWidth
// this approach will indeed be "inside" the path:
border!.strokeColor = borderColor.cgColor
border!.lineWidth = trueBorderWidth * 2.0
// HALF THE LINE will be INSIDE the path and HALF THE LINE
// WILL BE OUTSIDE the path. so MAKE IT >>TWICE AS THICK<<
// as requested by the consumer class.
}
}
So that's it.
Beginner help for the question in the comments ...
Make a "new Swift file" called "Fattie.swift". (Note, funnily enough it actually makes no difference what you call it. If you are at the stage of "don't know how to make a new file" seek basic Xcode tutorials.)
Put all of the above code in the file
You've just added a class "RoundedCornersAndTrueBorder" to your project.
On your story board. Add an ordinary UIView to your scene. In fact, make it actually any size/shape whatsoever, anything you prefer.
Look at the Identity Inspector. (If you do not know what that is, seek basic tutorials.) Simply change the class to "RoundedCornersAndTrueBorder". (Once you start typing "Roun...", it will guess which class you mean.
You're done - run the project.
Note that you have to, of course, add complete and correct constraints to the UIView, just as with absolutely anything you do in Xcode. Enjoy!
Similar solutions:
https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
https://stackoverflow.com/a/41553784/294884 - two-corner problem
https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers
And please also see the alternate answer below! :)
Absolutely perfect 2019 solution
Without further ado, here's exactly how you do this.
Don't actually use the "basic" layer that comes with the view
Make a new layer only for the image. You can now mask this (circularly) without affecting the next layer
Make a new layer for the border as such. It will safely not be masked by the picture layer.
The key facts are
With a CALayer, you can indeed apply a .mask and it only affects that layer
When drawing a circle (or indeed any border), have to attend very carefully to the fact that you only get "half the width" - in short never crop using the same path you draw with.
Notice the original cat image is exactly as wide as the horizontal yellow arrow. You have to be careful to paint the image so that the whole image appears in the roundel, which is smaller than the overall custom control.
So, setup in the usual way
import UIKit
#IBDesignable class GreenCirclePerson: UIView {
#IBInspectable var borderColor: UIColor = UIColor.black { didSet { setup() } }
#IBInspectable var trueBorderThickness: CGFloat = 2.0 { didSet { setup() } }
#IBInspectable var trueGapThickness: CGFloat = 2.0 { didSet { setup() } }
#IBInspectable var picture: UIImage? = nil { didSet { setup() } }
override func layoutSubviews() { setup() }
var imageLayer: CALayer? = nil
var border: CAShapeLayer? = nil
func setup() {
if (imageLayer == nil) {
imageLayer = CALayer()
self.layer.addSublayer(imageLayer!)
}
if (border == nil) {
border = CAShapeLayer()
self.layer.addSublayer(border!)
}
Now carefully make the layer for the circularly-cropped image:
// the ultimate size of our custom control:
let box = self.bounds.aspectFit()
let totalInsetOnAnyOneSide = trueBorderThickness + trueGapThickness
let boxInWhichImageSits = box.inset(by:
UIEdgeInsets(top: totalInsetOnAnyOneSide, left: totalInsetOnAnyOneSide,
bottom: totalInsetOnAnyOneSide, right: totalInsetOnAnyOneSide))
// just a note. that version of inset#by is much clearer than the
// confusing dx/dy variant, so best to use that one
imageLayer!.frame = boxInWhichImageSits
imageLayer!.contents = picture?.cgImage
imageLayer?.contentsGravity = .resizeAspectFill
let halfImageSize = boxInWhichImageSits.width / 2.0
let maskPath = UIBezierPath(roundedRect: imageLayer!.bounds,
cornerRadius:halfImageSize)
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
imageLayer!.mask = maskLayer
Next as a completely separate layer, draw the border as you wish:
// now create the border
border!.frame = bounds
// To draw the border, you must inset it by half the width of the border,
// otherwise you'll be drawing only half the border. (Indeed, as an additional
// subtle problem you are clipping rather than rendering the outside edge.)
let halfWidth = trueBorderThickness / 2.0
let borderCenterlineBox = box.inset(by:
UIEdgeInsets(top: halfWidth, left: halfWidth,
bottom: halfWidth, right: halfWidth))
let halfBorderBoxSize = borderCenterlineBox.width / 2.0
let borderPath = UIBezierPath(roundedRect: borderCenterlineBox,
cornerRadius:halfBorderBoxSize)
border!.path = borderPath.cgPath
border!.fillColor = UIColor.clear.cgColor
border!.strokeColor = borderColor.cgColor
border!.lineWidth = trueBorderThickness
}
}
Everything works perfectly as in iOS standard controls:
Everything which is invisible is invisible; you can see-through the overall custom control to any material behind, there are no "half thickness" problems or missing image material, you can set the custom control background color in the usual way, etc etc. The inspector controls all work properly. (Phew!)
Similar solutions:
https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
https://stackoverflow.com/a/41553784/294884 - two-corner problem
https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers
**use this extension for round borders and corner**
extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
func roundCornersWithBorder(corners: UIRectCorner, radius: CGFloat) {
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
maskLayer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: radius, height: radius)).cgPath
layer.mask = maskLayer
// Add border
let borderLayer = CAShapeLayer()
borderLayer.path = maskLayer.path // Reuse the Bezier path
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor(red:3/255, green:33/255, blue:70/255, alpha: 0.15).cgColor
borderLayer.lineWidth = 2
borderLayer.frame = bounds
layer.addSublayer(borderLayer)
}
}
Use like this
myView.roundCornersWithBorder(corners: [.topLeft, .topRight], radius: 8.0)
myView.roundCorners(corners: [.topLeft, .topRight], radius: 8.0)
There sure is! Every view has a layer property (which you know from giving your layer rounded corners). Another two properties on layer are borderColor and borderWidth. Just by setting those you can add a border to your view! (The border will follow the rounded corners.) Be sure to use UIColor.CGColor for borderColor as a plain UIColor won't match the type.

Swift - How to create a view with a shape cropped in it

I'm trying to achieve the result shown in the image using swift 1.2 and xcode 6.
Basically I want to create a view with a shape cut in it to be able to see the the view below to make a tutorial for my app.
I know how to create a circular shape but i don't know how to cut it out in a view.
I need a complete example on how to do it please.
Thanks in advance
Even though there is an answer, i'd like to share my way:
// Let's say that you have an outlet to the image view called imageView
// Create the white view
let whiteView = UIView(frame: imageView.bounds)
let maskLayer = CAShapeLayer() //create the mask layer
// Set the radius to 1/3 of the screen width
let radius : CGFloat = imageView.bounds.width/3
// Create a path with the rectangle in it.
let path = UIBezierPath(rect: imageView.bounds)
// Put a circle path in the middle
path.addArcWithCenter(imageView.center, radius: radius, startAngle: 0.0, endAngle: CGFloat(2*M_PI), clockwise: true)
// Give the mask layer the path you just draw
maskLayer.path = path.CGPath
// Fill rule set to exclude intersected paths
maskLayer.fillRule = kCAFillRuleEvenOdd
// By now the mask is a rectangle with a circle cut out of it. Set the mask to the view and clip.
whiteView.layer.mask = maskLayer
whiteView.clipsToBounds = true
whiteView.alpha = 0.8
whiteView.backgroundColor = UIColor.whiteColor()
//If you are in a VC add to the VC's view (over the image)
view.addSubview(whiteView)
// Annnnnd you're done.
//assume you create a UIImageView and content image before execute this code
let sampleMask = UIView()
sampleMask.frame = self.view.frame
sampleMask.backgroundColor = UIColor.black.withAlphaComponent(0.6)
//assume you work in UIViewcontroller
self.view.addSubview(sampleMask)
let maskLayer = CALayer()
maskLayer.frame = sampleMask.bounds
let circleLayer = CAShapeLayer()
//assume the circle's radius is 150
circleLayer.frame = CGRect(x:0 , y:0,width: sampleMask.frame.size.width,height: sampleMask.frame.size.height)
let finalPath = UIBezierPath(roundedRect: CGRect(x:0 , y:0,width: sampleMask.frame.size.width,height: sampleMask.frame.size.height), cornerRadius: 0)
let circlePath = UIBezierPath(ovalIn: CGRect(x:sampleMask.center.x - 150, y:sampleMask.center.y - 150, width: 300, height: 300))
finalPath.append(circlePath.reversing())
circleLayer.path = finalPath.cgPath
circleLayer.borderColor = UIColor.white.withAlphaComponent(1).cgColor
circleLayer.borderWidth = 1
maskLayer.addSublayer(circleLayer)
sampleMask.layer.mask = maskLayer
Here is sample code for how you can make a circle Mask for a UIView:
let sampleView = UIView(frame: UIScreen.mainScreen().bounds)
let maskLayer = CALayer()
maskLayer.frame = sampleView.bounds
let circleLayer = CAShapeLayer()
//assume the circle's radius is 100
circleLayer.frame = CGRectMake(sampleView.center.x - 100, sampleView.center.y - 100, 200, 200)
let circlePath = UIBezierPath(ovalInRect: CGRectMake(0, 0, 200, 200))
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = UIColor.blackColor().CGColor
maskLayer.addSublayer(circleLayer)
sampleView.layer.mask = maskLayer
Here is what I made in the playground:
The easiest way to do this would be to create a png image with partly transparent white around the outside and a clear circle in the middle. Then stack 2 image views on top of each other, with the masking image on top, and set its "opaque" flag to false.
You could also do this by creating a CAShapeLayer and set it up to use a translucent white color, then install a shape that is the square with the hole cut out of it shape. You'd install that shape layer on top of your image view's layer.
The most general-purpose way to do that would be to create a custom subclass of UIImageView and have the init method of your subclass create and install the shape layer. I just created a gist yesterday that illustrated creating a custom subclass of UIImageView. Here is the link: ImageViewWithGradient gist
That gist creates a gradient layer. It would be a simple matter to adapt it to create a shape layer instead, and if you modified the layoutSubviews method you could make it adapt the view and path if the image view gets resized.
EDIT:
Ok, I took the extra step of creating a playground that creates a cropping image view. You can find that at ImageViewWithMask on github
The resulting image for my playground looks like this:
class MakeTransparentHoleOnOverlayView: UIView {
#IBOutlet weak var transparentHoleView: UIView!
// MARK: - Drawing
override func draw(_ rect: CGRect) {
super.draw(rect)
if self.transparentHoleView != nil {
// Ensures to use the current background color to set the filling color
self.backgroundColor?.setFill()
UIRectFill(rect)
let layer = CAShapeLayer()
let path = CGMutablePath()
// Make hole in view's overlay
// NOTE: Here, instead of using the transparentHoleView UIView we could use a specific CFRect location instead...
path.addRect(transparentHoleView.frame)
path.addRect(bounds)
layer.path = path
layer.fillRule = kCAFillRuleEvenOdd
self.layer.mask = layer
}
}
override func layoutSubviews () {
super.layoutSubviews()
}
// MARK: - Initialization
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}