Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mask UIView with UIBezierPath - Stroke Only

Tags:

ios

swift

Update 02 has a working solution...

I am trying to use the stroke of a UIBezierPath as a mask on another UIView. There are many examples, but they all use the fill to clip views.

I'm trying to use only the stroke of the path, but it's not displaying correctly.

Below is what I currently have in Swift 3.x:

let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: 100, y: 100))
bezierPath.addLine(to: CGPoint(x: 200, y: 50))
bezierPath.addLine(to: CGPoint(x: 300, y: 100))
bezierPath.lineWidth = 5.0
bezierPath.stroke()

let gradient = Gradient(frame: self.bounds)
let mask = CAShapeLayer()

mask.path = bezierPath.cgPath
gradient.layer.mask = mask

self.addSubview(gradient)

But, the above code does this. I'm looking for only to use the stroke for the mask... This is what the code is currently doing

Currently doing..

This is the desired outcome.. Desired outcome

(has more points in this comp snapshot)

I realize that there might be a better way, open to any ideas or alternatives!


--Update 01--

My latest, but masks out everything:

let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: 100, y: 100))
bezierPath.addLine(to: CGPoint(x: 200, y: 50))
bezierPath.addLine(to: CGPoint(x: 300, y: 100))
bezierPath.lineWidth = 5.0
bezierPath.stroke()

let gradient = Gradient(frame: self.bounds)

UIGraphicsBeginImageContext(CGSize(width: gradient.bounds.width, height: gradient.bounds.height))
let context : CGContext = UIGraphicsGetCurrentContext()!
context.addPath(bezierPath.cgPath)

let image = UIGraphicsGetImageFromCurrentImageContext()
gradient.layer.mask?.contents = image?.cgImage

And.. got nowhere after trying to figure it out with CAShapeLayer:

let mask = CAShapeLayer()
mask.fillColor = UIColor.black.cgColor
mask.strokeColor = UIColor.black.cgColor
mask.fillRule = kCAFillModeBoth
mask.path = bezierPath.cgPath
gradient.layer.mask = mask
self.addSubview(gradient)

--Update 02 - Working Solution --

Thanks for everybody's help!

let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: 100, y: 300))
bezierPath.addLine(to: CGPoint(x: 200, y: 50))
bezierPath.addLine(to: CGPoint(x: 300, y: 200))
bezierPath.addLine(to: CGPoint(x: 400, y: 100))
bezierPath.addLine(to: CGPoint(x: 500, y: 200))

let gradient = Gradient(frame: self.bounds) // subclass of UIView

let mask = CAShapeLayer()
mask.fillColor = nil
mask.strokeColor = UIColor.black.cgColor
mask.path = bezierPath.cgPath
mask.lineWidth = 5.0
gradient.layer.mask = mask

self.addSubview(gradient)

And, the results are what I was wanting:

Expected Result

like image 574
August Avatar asked Oct 13 '17 01:10

August


1 Answers

A UIBezierPath has several properties that only matter when stroking the path, including lineWidth, lineCapStyle, lineJoinStyle, and miterLimit.

A CGPath has none of these properties.

Thus when you set mask.path = bezierPath.cgPath, none of the stroke properties you set on bezierPath carries over. You've extracted just the CGPath from the UIBezierPath and none of those other properties.

You need to set the stroking properties on the CAShapeLayer rather than on any path object. Thus:

mask.path = bezierPath.cgPath
mask.lineWidth = 5

You also need to set a stroke color, because the default stroke color is nil, which means don't stroke at all:

mask.strokeColor = UIColor.white.cgColor

And, because you don't want the shape layer to fill the path, you need to set the fill color to nil:

mask.fillColor = nil
like image 93
rob mayoff Avatar answered Oct 07 '22 18:10

rob mayoff