问题
I want to replicate this in Swift.. reading further: How to draw a linear gradient arc with Qt QPainter?
I've figured out how to draw a gradient inside of a rectangular view:
override func drawRect(rect: CGRect) {
let currentContext = UIGraphicsGetCurrentContext()
CGContextSaveGState(currentContext)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let startColor = MyDisplay.displayColor(myMetric.currentValue)
let startColorComponents = CGColorGetComponents(startColor.CGColor)
let endColor = MyDisplay.gradientEndDisplayColor(myMetric.currentValue)
let endColorComponents = CGColorGetComponents(endColor.CGColor)
var colorComponents
= [startColorComponents[0], startColorComponents[1], startColorComponents[2], startColorComponents[3], endColorComponents[0], endColorComponents[1], endColorComponents[2], endColorComponents[3]]
var locations: [CGFloat] = [0.0, 1.0]
let gradient = CGGradientCreateWithColorComponents(colorSpace, &colorComponents, &locations, 2)
let startPoint = CGPointMake(0, 0)
let endPoint = CGPointMake(1, 8)
CGContextDrawLinearGradient(currentContext, gradient, startPoint, endPoint, CGGradientDrawingOptions.DrawsAfterEndLocation)
CGContextRestoreGState(currentContext)
}
and how to draw an arc
func drawArc(color: UIColor, x: CGFloat, y: CGFloat, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, lineWidth: CGFloat, clockwise: Int32) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, lineWidth)
CGContextSetStrokeColorWithColor(context,
color.CGColor)
CGContextAddArc(context, x, y, radius, startAngle, endAngle, clockwise)
CGContextStrokePath(context)
}
With the gradient, that took up the entire view. I was unsure how to limit the gradient to a particular area so I made a subclass of UIView
, modified the frame and used the gradient code (first snippet).
For arcs, I've used the second snippet. I'm thinking I can draw an arc and then draw a circle at the end of the arc to smooth out the arc with a rounded finish. How can I do this and draw a gradient over the arc shape?
Please ignore the purple mark. The first picture is the beginning of the gradient arc from my specification, the second is the end of the gradient arc, below and to the left of the beginning arc.
How can I draw this in Swift with Core Graphics?
回答1:
override func draw(_ rect: CGRect) {
//Add gradient layer
let gl = CAGradientLayer()
gl.frame = rect
gl.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
layer.addSublayer(gl)
//create mask in the shape of arc
let sl = CAShapeLayer()
sl.frame = rect
sl.lineWidth = 15.0
sl.strokeColor = UIColor.red.cgColor
let path = UIBezierPath()
path.addArc(withCenter: .zero, radius: 250.0, startAngle: 10.0.radians, endAngle: 60.0.radians, clockwise: true)
sl.fillColor = UIColor.clear.cgColor
sl.lineCap = kCALineCapRound
sl.path = path.cgPath
//Add mask to gradient layer
gl.mask = sl
}
Hope this helps!!
回答2:
Exact code for 2019 with true circular gradient and totally flexible end positions:
These days basically you simply have a gradient layer and mask for that layer.
You actually use a CAShapeLayer to make a mask. (Which I find confusing: it should be called something like "CAShapeToUseAsMaskLayer".)
In layout, you must resize the shape layer. And that's it.
So basically it's just this:
class SweetArc: UIView {
open override func layoutSubviews() {
super.layoutSubviews()
arcLayer.frame = bounds
gradientLayer.frame = bounds
}
private lazy var gradientLayer: CAGradientLayer = {
let l = CAGradientLayer()
...
l.mask = arcLayer
layer.addSublayer(l)
return l
}()
private lazy var arcLayer: CAShapeLayer = {
let l = CAShapeLayer()
...
return l
}()
}
That's the whole thing.
Note that the mask of the gradient layer is indeed set as the shape layer, which we have named "arcLayer".
A shape layer is used as a mask.
Here's the exact code for the two layers:
private lazy var gradientLayer: CAGradientLayer = {
// recall that .conic is finally available in 12 !
let l = CAGradientLayer()
l.type = .conic
l.colors = [
UIColor.yellow,
UIColor.red
].map{$0.cgColor}
l.locations = [0, 1]
l.startPoint = CGPoint(x: 0.5, y: 0.5)
l.endPoint = CGPoint(x: 0.5, y: 0)
l.mask = arcLayer
layer.addSublayer(l)
return l
}()
private lazy var arcLayer: CAShapeLayer = {
let l = CAShapeLayer()
l.path = arcPath
// to use a shape layer as a mask, you mask with white-on-clear:
l.fillColor = UIColor.clear.cgColor
l.strokeColor = UIColor.white.cgColor
l.lineWidth = lineThick
l.lineCap = CAShapeLayerLineCap.round
l.strokeStart = 0.4 // try values 0-1
l.strokeEnd = 0.5 // try values 0-1
return l
}()
Again, notice arcLayer is a shape layer you are using as a mask - you do NOT actually add it as a subview.
Finally, notice arcLayer uses a path arcPath
.
Construct the path perfectly so that strokeStart and strokeEnd work correctly.
You want to build the path perfectly so that it is very easy to set the stroke start and stroke end values, and they make sense perfectly and correctly run clockwise from the top from 0 to 1.
Here's the exact code for that:
private let lineThick: CGFloat = 10.0
private let outerGap: CGFloat = 10.0
private let beginFraction: CGFloat = 0 // 0 means from top; runs clockwise
private lazy var arcPath: CGPath = {
let b = beginFraction * .pi * 2.0
return UIBezierPath(
arcCenter: bounds.centerOfCGRect(),
radius: bounds.width / 2.0 - lineThick / 2.0 - outerGap,
startAngle: .pi * -0.5 + b,
endAngle: .pi * 1.5 + b,
clockwise: true
).cgPath
}()
You're done!
来源:https://stackoverflow.com/questions/37092248/drawing-a-gradient-color-in-an-arc-with-a-rounded-edge