Reputation: 889
In my scene I have 2 views: first holds CALayer instances (bars), another hold CAGradientLayer and placed over first one. Picture below describes current state.
But I need this gradient to be applied only to bars (CALayer) of the first view.
I haven't found any relevant information to my problem. Any help appreciated.
Upvotes: 1
Views: 1512
Reputation: 386068
You have to apply a mask to the gradient. There are various ways you could approach this problem.
You could create a CAShapeLayer
, set the shape layer's path
to the shape of the bars, and set the gradient layer's mask
to that shape layer.
Or you could get rid of the bar layer and instead use two gradient layers, one for the orange bars and the other for the gray bars. Put both gradient layers in a subview, side-by-side, and set the superview's layer mask to the shape layer. Here's how to do that.
You'll need two gradient layers and a shape layer:
@IBDesignable
class BarGraphView : UIView {
private let orangeGradientLayer = CAGradientLayer()
private let grayGradientLayer = CAGradientLayer()
private let maskLayer = CAShapeLayer()
You'll also need the bar width:
private let barWidth = CGFloat(9)
At initialization time, set up the gradients and add all the sublayers:
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
backgroundColor = .black
initGradientLayer(orangeGradientLayer, with: .orange)
initGradientLayer(grayGradientLayer, with: .gray)
maskLayer.strokeColor = nil
maskLayer.fillColor = UIColor.white.cgColor
layer.mask = maskLayer
}
private func initGradientLayer(_ gradientLayer: CAGradientLayer, with color: UIColor) {
gradientLayer.colors = [ color, color, color.withAlphaComponent(0.6), color ].map({ $0.cgColor })
gradientLayer.locations = [ 0.0, 0.5, 0.5, 1.0 ]
layer.addSublayer(gradientLayer)
}
At layout time, set the frames of the gradient layers and set the mask layer's path. This requires a little work because you don't want a bar to be half orange and half gray.
override func layoutSubviews() {
super.layoutSubviews()
let barCount = ceil(bounds.size.width / barWidth)
let orangeBarCount = floor(barCount / 2)
let grayBarCount = barCount - orangeBarCount
var grayFrame = bounds
grayFrame.size.width = grayBarCount * barWidth
grayFrame.origin.x = frame.maxX - grayFrame.size.width
grayGradientLayer.frame = grayFrame
var orangeFrame = bounds
orangeFrame.size.width -= grayFrame.size.width
orangeGradientLayer.frame = orangeFrame
maskLayer.frame = bounds
maskLayer.path = barPath()
}
private func barPath() -> CGPath {
var columnBounds = self.bounds
columnBounds.origin.x = columnBounds.maxX
columnBounds.size.width = barWidth
let path = CGMutablePath()
for datum in barData.reversed() {
columnBounds.origin.x -= barWidth
let barHeight = CGFloat(datum) * columnBounds.size.height
let barRect = columnBounds.insetBy(dx: 1, dy: (columnBounds.size.height - barHeight) / 2)
path.addRoundedRect(in: barRect, cornerWidth: 2, cornerHeight: 2)
}
return path
}
let barData: [Double] = {
let count = 100
return (0 ..< count).map({ 0.5 + (1 + sin(8.0 * .pi * Double($0) / Double(count))) / 4 })
}()
}
Result:
The BarGraphView
is transparent wherever there are no bars. If you want it on a dark background, put a dark view behind it, or make it a subview of a dark view:
Upvotes: 8