I'm trying to draw of my view with using bezierPath and my code is ?
class auctionCollectionView : UIView{
let gradient = CAGradientLayer()
private var shapeLayer: CALayer?
private func addShape() {
let shapeLayer = CAShapeLayer()
shapeLayer.path = mys2().cgPath
shapeLayer.strokeColor = UIColor.lightGray.cgColor
self.shapeLayer = shapeLayer
let colorTop = UIColor(hexFromString: "#101820").cgColor
let colorBottom = UIColor(hexFromString: "#101820").cgColor
let gradientLayer = CAGradientLayer()
gradientLayer.mask = shapeLayer
gradientLayer.colors = [colorTop, colorBottom]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.frame = mys2().bounds
self.layer.insertSublayer(gradientLayer, at:0)
}
override func draw(_ rect: CGRect) {
self.addShape()
}
func mys2() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: self.frame.height/4)) // start top left
path.addQuadCurve(to: CGPoint(x: self.frame.width , y:self.frame.height/4), controlPoint: CGPoint(x: self.frame.width/2 , y: 0 ))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height * 3/4))
path.addQuadCurve(to: CGPoint(x: 0, y: self.frame.height * 3/4), controlPoint: CGPoint(x: self.frame.width/2, y:self.frame.height / 2))
path.addLine(to: CGPoint(x: 0, y: self.frame.height/4))
path.close()
return path
}
}
My first QuadCurve looks ok but when I try to add this to from maxX (self.frame.width)
, maxY ( self.frame.height * 3/4)
to minX ( 0)
, maxY (self.frame.height * 3/4)
with CGPoint(x: self.frame.width/2, y:self.frame.height / 2)
it acts like adding line
What did I missing here ? Regards!
Your path seems to be OK but I am worried your problem is what you call when overriding draw
method.
You should either add layers once and manage them later. Or you should use draw rect to draw everything without using layers. In this case I suggest the second one. I am not exactly sure how your full implementation should be but from copying your path and seeing the gradient code it might be something similar to this:
@IBDesignable class DrawingView: UIView {
@IBInspectable var topColor: UIColor? = .red
@IBInspectable var bottomColor: UIColor? = .blue
private func mys2() -> UIBezierPath {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: self.frame.height/4)) // start top left
path.addQuadCurve(to: CGPoint(x: self.frame.width , y:self.frame.height/4), controlPoint: CGPoint(x: self.frame.width/2 , y: 0 ))
path.addLine(to: CGPoint(x: self.frame.width, y: self.frame.height * 3/4))
path.addQuadCurve(to: CGPoint(x: 0, y: self.frame.height * 3/4), controlPoint: CGPoint(x: self.frame.width/2, y:self.frame.height / 2))
path.addLine(to: CGPoint(x: 0, y: self.frame.height/4))
path.close()
return path
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext(), let topColor = topColor, let bottomColor = bottomColor else {
return
}
// Save because of clipping
context.saveGState()
// Add clipping from my path
mys2().addClip()
let locations: [CGFloat] = [0.0, 1.0]
let colors = [topColor.cgColor, bottomColor.cgColor]
if let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: colors as CFArray, locations: locations) {
context.drawLinearGradient(gradient, start: CGPoint.zero, end: CGPoint(x: 0.0, y: frame.size.height), options: CGGradientDrawingOptions(rawValue: 0))
}
context.restoreGState()
}
}
So instead of mask we can just use clipping which is kind of what mask does in the first place in layer. The gradient code is a little bit different but still similar enough.
Now note that this method will be called whenever the view needs to redraw. To force a redraw you need to call setNeedsDisplay
. So something like the following can be pretty common:
@IBInspectable var topColor: UIColor? = .red { didSet { refresh() } }
@IBInspectable var bottomColor: UIColor? = .blue { didSet { refresh() } }
override var frame: CGRect { didSet { refresh() } }
override func layoutSubviews() {
super.layoutSubviews()
refresh()
}
private func refresh() { setNeedsDisplay() }
So basically you force it to redraw when:
Note that calling setNeedsDisplay
will not force the reload instantly and call draw
method. It just marks the component dirty and will redraw it in the next loop. The result is that draw
will be called only once even if you call setNeedsDisplay
multiple times in the same loop (stack, method...). So there are no performance issues.
The same can be done in your case with using layers. You need a refresh
method whenever something changes. Then this method should either create sublayers or correct the sublayers frames, mask... And the draw
method should be removed. So either use one or the other approach but not both.