CAGradient layer not visible on view - ios

I try to add a CAGradient effect in a pie chart I make with Core Graphics. It turns out I am not able and certainly not understand very well how to apply it to a view that is part of a CGPath...
override func draw(_ rect: CGRect)
{
...
func arc(myRadius: CGFloat, myStartAngle: CGFloat, myEndAngle: CGFloat) {
// This is the pie chart segment
context?.setFillColor(phenotypeColor.cgColor)
context?.move(to: center)
context?.addArc(center: center, radius: finalRadius, startAngle: myStartAngle, endAngle: myEndAngle, clockwise: true)
context?.fillPath()
(...)
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.colors = [UIColor.red, UIColor.blue]
self.layer.addSublayer(gradientLayer)
}
I don't see any gradient on the screen (only the white background...).
Lastly, I also tried to clip the context juste after saving its state but I am not sure I clip anything since I don't see any gradient (either on the whole view or just on the segment). I read the documentation but it does not clear things up (in my case...).

cgColor was missing... Sorry for the inconvenience.
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.bounds
gradientLayer.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
self.layer.addSublayer(gradientLayer)

Related

Change Gradient Background of UIView Through Sublayer

I've got this function defined in an extension of UIView which I use to set the gradient background on a UIView:
func setGradientBackground(colorTop: UIColor, colorBottom: UIColor, withCornerRadius : CGFloat){
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorBottom.cgColor, colorTop.cgColor]
gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.locations = [0, 1]
gradientLayer.frame = bounds
gradientLayer.cornerRadius = withCornerRadius
layer.insertSublayer(gradientLayer, at: 0)
}
It is used as follows:
view.setGradientBackground(colorTop: UIColor.blue, colorBottom: UIColor.red, withCornerRadius: 10)
The problem is, if I have already called this function once on a view, the second time I call it, it does not do anything. I suspect this is caused by the subLayer persisting from the previous insertion.
I've tried adding:
if layer.sublayers?.count ?? 0 > 0 {
layer.sublayers?.remove(at: 0)
}
to the top of that function, but that causes by code to crash with a BAD_INSTRUCTION exception.
You can get the Gradient layer and can remove from view anywhere you want to ... Not just by adding it again you remove it..
if let gradientLayer = (yourView.layer.sublayers?.compactMap { $0 as? CAGradientLayer })?.first {
print("gradientLayer is presnet")
gradientLayer.removeFromSuperlayer()
} else {
print("its not added on layer yet")
}
The easiest option would be to check to see if the layer's sublayer is a gradient layer. This isn't perfectly safe because if you happen to have another gradient layer at index 0 of the sublayers, it will change the incorrect sublayer.
func setGradientBackground(colorTop: UIColor, colorBottom: UIColor, withCornerRadius cornerRadius: CGFloat) {
let gradientLayer = layer.sublayers?.first as? CAGradientLayer ?? CAGradientLayer()
// The rest of your existing layer customization code here
guard gradientLayer.superlayer != self else {
return
}
layer.sublayers?.insertSublayer(gradientLayer, at: 0)
}

Set gradient color on custom UIView boarder

I have a UIView that contains a 2 UILabels with a button inside and I would like to add a gradient color to its boarder. I have managed to add it and button has ended up moving outside the custom UIView with the custom UIView also shrinking all the way outside on smaller devices. How can I fix the UIView to remain the same size when I add a gradient color
Here is the initial UIView with two UILabels and a button inside with a normal border colour before
And here how it looks after applying a gradient color to it
Here is my code on how I apply the gradient.
#IBOutlet weak var customView: UIView!
let gradient = CAGradientLayer()
gradient.frame.size = self.customView.frame.size
gradient.colors = [UIColor.green.cgColor, UIColor.yellow.cgColor, UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0.1, y: 0.78)
gradient.endPoint = CGPoint(x: 1.0, y: 0.78)
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(rect: self.customView.bounds).cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 4
gradient.mask = shapeLayer
self.customView.layer.addSublayer(gradient)
Layers do not resize when the view resizes, so you want to create a custom view and override layoutSubviews().
Here's an example:
#IBDesignable
class GradBorderView: UIView {
var gradient = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
layer.addSublayer(gradient)
}
override func layoutSubviews() {
gradient.frame = bounds
gradient.colors = [UIColor.green.cgColor, UIColor.yellow.cgColor, UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0.1, y: 0.78)
gradient.endPoint = CGPoint(x: 1.0, y: 0.78)
let shapeLayer = CAShapeLayer()
shapeLayer.path = UIBezierPath(rect: bounds).cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = 4
gradient.mask = shapeLayer
}
}
Now, when your view changes size based on constraints and auto-layout, your gradient border will "auto-resize" correctly.
Also, by using #IBDesignable, you can see the results when laying out your views in Storyboard / Interface Builder.
Here's how it looks with the Grad Border View width set to 240:
and with the Grad Border View width set to 320:
Edit
If we want to use rounded corners, we can set the shape layer path to a rounded rect bezier path, and then also set the corner radius of the view's layer.
For example:
override func layoutSubviews() {
let cRadius: CGFloat = 8.0
let bWidth: CGFloat = 4.0
// layer border is centered on layer edge
let half: CGFloat = bWidth * 0.5
// make gradient frame size of view + half the border width
gradient.frame = bounds.insetBy(dx: -half, dy: -half)
gradient.colors = [UIColor.green.cgColor, UIColor.yellow.cgColor, UIColor.red.cgColor]
gradient.startPoint = CGPoint(x: 0.1, y: 0.78)
gradient.endPoint = CGPoint(x: 1.0, y: 0.78)
let shapeLayer = CAShapeLayer()
// make shapeLayer path the size of view OFFSET by half the border width
// with rounded corners
shapeLayer.path = UIBezierPath(roundedRect: bounds.offsetBy(dx: half, dy: half), cornerRadius: cRadius).cgPath
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.black.cgColor
shapeLayer.lineWidth = bWidth
gradient.mask = shapeLayer
// same corner radius as shapeLayer path
layer.cornerRadius = cRadius
}

Swift PageViewController gradient background

I have a PageViewController and need to set a gradient background to it.
When using gradient background in normal ViewControllers, I use the following:
let layer = self.layer as! CAGradientLayer
layer.colors = [FirstColor.cgColor, SecondColor.cgColor]
layer.locations = [0.5]
when I try to use self.view.layer inside my PageViewController I get an error:
Could not cast value of type 'CALayer' (0x104f57900) to 'CAGradientLayer'
Can someone help me setting the background of a page view controller to an gradient?
Thanks
Hi you can create a UIView extension and just call like this:
view.applyGradient(colours: [UIColor.red, UIColor.green])
here the extension code
extension UIView {
func applyGradient(colours: [UIColor]) -> Void {
clipsToBounds = true
let gradient: CAGradientLayer = CAGradientLayer()
gradient.frame = self.bounds
gradient.colors = colours.map { $0.cgColor }
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 0)
layer.insertSublayer(gradient, at: 0)
}
}
with this code you can apply to UIButtons, UILabels, anything that extends from UIView
hope it helps

How to add gradient to UIView as an extension

I am trying to add some gradient to a view in Xcode and for simplicity I tried to add my method as an extension to UIView:
extension UIView {
func applyGradient() {
let gradient = CAGradientLayer()
gradient.colors = [UIColor(hex: "F5FF8C").cgColor,
UIColor(hex: "F1F99D").cgColor,
UIColor(hex: "FDFFE0").cgColor]
gradient.locations = [0.0, 0.5, 1.0]
self.layer.insertSublayer(gradient, at: 0)
}
}
But apparently this doesn't work when I call it in my viewDidLoad:
self.myView.applyGradient()
Can someone point to me what am I doing wrong ?
In the question, you haven't set the frame at all. In the comment, your frame was not set correctly. This is the code that should work properly:
extension UIView {
func applyGradient() {
let gradient = CAGradientLayer()
gradient.colors = [UIColor.red.cgColor,
UIColor.green.cgColor,
UIColor.black.cgColor] // your colors go here
gradient.locations = [0.0, 0.5, 1.0]
gradient.frame = self.bounds
self.layer.insertSublayer(gradient, at: 0)
}
}
With your code:
With the modified code:
Explanation
gradient.frame.size = self.frame.size doesn't work, while gradient.frame = self.bounds does, because the frame attribute contains both the location and the size of the view, and even if you set the gradient's frame size, you do not specify the location of the gradient... So the gradient is never actually added to the view. By setting the frame attribute directly to the bounds of the view, you also add the location of the gradient in the view.

How to get a circle shape layer with one edge as a gradient and the other one as a full color

I have done this circle gradient layer:
What I would like to have is only one gradient (the one on the left) while the gradient on the bottom would be removed to show a clear separation between red and yellow.
As I will need to make an animation out of it (like a loading view), I thought about having an image in the background and having a circle shape layer with a color (like white) on top and change the stroke of this layer as I need.
Another solution I thought about was having tow circle shape layers, one with the gradient, the other one without.
But both those solutions feels more like a hack and I was wondering if there was a proper one using just
Here is the code I used:
fileprivate func createProgressLayer() {
let startAngle = CGFloat(M_PI_2)
let endAngle = CGFloat(M_PI * 2 + M_PI_2)
let centerPoint = CGPoint(x: frame.width / 2 , y: frame.height / 2)
progressLayer.path = UIBezierPath(arcCenter:centerPoint, radius: frame.width / 2 - 30.0, startAngle: startAngle, endAngle: endAngle, clockwise: true).cgPath
progressLayer.backgroundColor = UIColor.clear.cgColor
progressLayer.fillColor = nil
progressLayer.strokeColor = UIColor.black.cgColor
progressLayer.lineWidth = 4.0
progressLayer.strokeStart = 0.0
progressLayer.strokeEnd = 1.0
let gradientMaskLayer = gradientMask()
gradientMaskLayer.mask = progressLayer
layer.addSublayer(gradientMaskLayer)
}
fileprivate func gradientMask() -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.locations = [0.0, 0.1]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)
let arrayOfColors: [AnyObject] = [UIColor.red.cgColor, UIColor.yellow.cgColor]
gradientLayer.colors = arrayOfColors
return gradientLayer
}
Shape layers only support a single color.
In order to get the effect that you're after I think you're going to need to use a shape layer as a mask on your gradient like you're doing.
Having a second white shape layer on top of the gradient+mask layer and changing strokeStart and/or strokeEnd also sounds like a reasonable way to do what you're trying to do.
Doing tricks ("hacks" as you call them) with layers is quite common and a reasonable way to do what you're trying to do.

Resources