Update 02有一个可行的解决方案......
我正在尝试使用UIBezierPath的笔划作为另一个UIView的掩码。有很多例子,但它们都使用填充来剪辑视图。
我正在尝试仅使用路径的笔划,但它无法正确显示。
以下是我目前在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)
但是,上面的代码就是这样做的。我正在寻找只使用笔画作为面具......这就是代码目前正在做的事情
(此comp快照中有更多分数)
我意识到可能有更好的方式,对任何想法或替代方案持开放态度!
- 更新01 -
我最新的,但掩盖了一切:
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
并且在尝试使用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)
- 更新02 - 工作解决方案 -
感谢大家的帮助!
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)
而且,结果是我想要的:
答案 0 :(得分:5)
UIBezierPath
有多个属性,只有在抚摸路径时才有用,包括lineWidth
,lineCapStyle
,lineJoinStyle
和miterLimit
。
CGPath
没有这些属性。
因此,当您设置mask.path = bezierPath.cgPath
时,您在bezierPath
上设置的所有笔触属性都不会延续。您只从CGPath
中提取了UIBezierPath
,而没有提取其他任何属性。
您需要在CAShapeLayer
而不是任何路径对象上设置描边属性。因此:
mask.path = bezierPath.cgPath
mask.lineWidth = 5
您还需要设置笔触颜色,因为默认笔触颜色为nil,这意味着根本不要描边:
mask.strokeColor = UIColor.white.cgColor
并且,因为不希望形状图层填充路径,您需要将填充颜色设置为nil:
mask.fillColor = nil
答案 1 :(得分:2)
确切的操作方法
override func layoutSubviews() {
setup()
super.layoutSubviews()
}
private lazy var layerToUseAsAMask: CAShapeLayer = {
let l = CAShapeLayer()
// Recall that often you'd just draw the path here, but, somewhat confusingly
// we will draw the path in layout, since, we do need to know the sizes to
// draw the path
l.fillColor = UIColor.clear.cgColor
l.strokeColor = UIColor.white.cgColor
l.lineWidth = thick
l.lineCap = .round
// So, the bezier path is going to be on this layer. recall that (confusingly)
// it's the >layer<, not the path, which has the line qualities. The bezier
// path is a 100% platonic path!
// Recall too that overall, >all< of this is merely the CAShapeLayer, which
// will be >used as a mask< on our actual layer of interest ("ourLayer")
return l
}()
private lazy var ourLayer: CAGradientLayer = {
let l = CAGradientLayer()
layer.addSublayer(l)
l.frame = self.bounds
l.startPoint = CGPoint(x: 0.5, y: 0)
l.endPoint = CGPoint(x: 0.5, y: 1)
l.colors = [topColor.cgColor, bottomColor.cgColor]
// Recall that to round an image layer (or gradient later) you use the .mask
// facility (>not< a path)
l.mask = layerToUseAsAMask
// Recall too that, somewhat bizarrely really, changes at layout to a path
// push up to a layer (in our case our maskToUseAsMask); and indeed changes
// to a .mask facility (in our case, the one on our main layer) push up
// to a layer (ie, in our case to our main layer).
return l
}()
override func layoutSubviews() {
common()
super.layoutSubviews()
}
func common() {
ourLayer.frame = bounds
layerToUseAsAMask.frame = bounds
// Recall as explained above, every single time we layout, you have to
// actually draw the path again. It is "pushed up" to maskToUseAsMask
// and ultimately to the .mask on our ourLayer.
let pBar = UIBezierPath()
pBar.move(to: ...)
pBar.addLine(to: ...)
maskToUseAsMask.path = pBar.cgPath
layer.shadowOffset = CGSize(width: 5, height: 5)
layer.shadowColor = UIColor.green.cgColor
layer.shadowOpacity = 0.20
layer.shadowRadius = 2
}
在iOS中,Apple已将面具命名为..一层!因此,“ CAShapeLayer”可以是一层(图像的一层),或者在完全不同的用法中可以是...蒙版。
let someMaskIAmAMaking = CAMask() ... no!
let someMaskIAmAMaking = CAShapeLayer() ... strange but true. Bad Apple.
请始终记住,在iOS中,“ CAMask”不存在-您对“ CAMask”和“ CALayer”都使用“ CALayer”。
要成形已添加的自定义图层(渐变色,猫的照片或其他任何形状),您需要使用蒙版。那是面具。. 不是路径 。
我们将要成形一个图层,因此我们将使用蒙版。 (这意味着-WTF-CAShapeLayer。)
let ourMask = CAShapeLayer( ...
因此。
回想一下,通常您只是在这里画出了一条路,但是有些混乱 我们将在布局中绘制路径,因为我们确实需要知道 绘制路径。
因此,贝塞尔曲线路径将在此层上。回想一下(令人困惑) 是> layer <,而不是具有线条质量的路径。贝塞尔曲线 路径是100%柏拉图式的路径!
也请回想一下,总体而言,所有这一切仅仅是CAShapeLayer, 将>用作我们的实际关注图层(“ ourLayer”)上的遮罩
回想一下,要使图像层(或以后的渐变)变圆,请使用.mask 设施(不是路径)
也请回想一下,实际上有点奇怪,它在布局上更改为路径 向上推一层(在我们的例子中是maskToUseAsMask);确实发生了变化 到一个.mask设施(在我们的示例中,是我们ourLayer上的一个)上推 一层(例如,在我们的OurLayer中)。
回想一下,如上所述,我们每次布局时,都必须 真正地画出了道路。它被“推”到maskToUseAsMask 并最终到达ourLayer上的.mask。
所以
layer.shadowOpacity = 0.20 etc ...
回想一下,“视图上”的阴影是视图“主层和基本层”“ .layer”的内置阴影。您只需将.layer的.shadowOpacity设置为一个值即可将其打开。
这与在添加的其他图层上添加阴影 完全不同。要在添加的其他某些层上添加阴影 ,例如catLayer,请使用catLayer.path。 (该过程完全不同,在这里举例说明:https://stackoverflow.com/a/57465440/294884)
答案 2 :(得分:1)
解决方案是将bezierPath绘制到图像中,然后使用生成的图像作为蒙版。您的代码的问题是,当您将bezier路径添加到上下文时,您永远不会描述()它。我想向您推荐这个快速函数,它可以将一系列路径绘制到图像中:Create an UIImage from Uibezierpath Array in Swift
您可以通过将图像显示到UIImageView来正确确认图像。确认后,应该很容易使用掩码:iOS layer mask from UIImage/CGImage?
干杯!
答案 3 :(得分:1)
您可以直接在Core Graphics中执行此操作,而无需使用CALayer
类,这在绘制图像时非常有用。
您需要先将贝塞尔曲线路径转换为本身的“描边”版本,然后使用渐变剪切并填充生成的路径。
我使用以下包装函数:
extension UIBezierPath {
// Return a path that represents the outline of the receiver when stroked
public func strokedPath() -> UIBezierPath?
{
guard let ctx = UIGraphicsGetCurrentContext() else { return nil }
ctx.addPath(self.cgPath)
ctx.setLineWidth(self.lineWidth)
ctx.replacePathWithStrokedPath()
guard let path = ctx.path else { return nil }
return UIBezierPath(cgPath: path)
}
/// Fills the receiving bezier path with a gradient
public func fillWithLinearGradient(start:CGPoint, end:CGPoint, colors:[UIColor], fractions:[CGFloat]? = nil)
{
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
defer { ctx.restoreGState() } // clean up graphics state changes when the method returns
self.addClip() // use the path as the clipping region
let cgColors = colors.map({ $0.cgColor })
let locations = (fractions?.count == colors.count) ? fractions : nil
guard let gradient = CGGradient(colorsSpace: nil, colors: cgColors as CFArray, locations: locations) else { return }
ctx.drawLinearGradient(gradient, start: start, end: end, options: [])
}
}
用法:
myBezierPath.strokedPath()?.fillWithLinearGradient(start: /* ... */)