I have created a UIView subclass which has a gradient border and its background is yellow.
class GradientBorderView: UIView {
override func layoutSubviews() {
super.layoutSubviews()
addGradientBorder()
}
private func addGradientBorder() {
self.layer.cornerRadius = 24
self.backgroundColor = UIColor.yellow
// Remove any existing layers
self.layer.sublayers?.removeAll(where: { $0.name == "gradientBorder" })
// Create the gradient layer
let gradientLayer = CAGradientLayer()
gradientLayer.name = "gradientBorder"
gradientLayer.frame = bounds
// Define the gradient colors
gradientLayer.colors = [UIColor.red.withAlphaComponent(0.2).cgColor, UIColor.black.withAlphaComponent(0.2).cgColor]
// Create a shape layer that defines the rounded path and border width
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 4 // gradient border width
shapeLayer.fillColor = UIColor.clear.cgColor //fill area will be transparent during masking
shapeLayer.strokeColor = UIColor.black.cgColor //stroke area will be visible during masking
//Half of the line width is drawn inside and other half of the line width is drawn outside the rectangle.
//Thats why oringal reactangle was shrunk from all side by half of the line width
shapeLayer.path = UIBezierPath(roundedRect: bounds.insetBy(dx: 2, dy: 2),
cornerRadius: self.layer.cornerRadius).cgPath
// Mask the gradient layer with the shape layer, only affecting the border
gradientLayer.mask = shapeLayer
// Add the gradient layer as a sublayer
layer.addSublayer(gradientLayer)
}
}
I am not able to get rid of the extra yellow view part visible around the corners.
The goal is to smoothly align the view layer with the gradient layer around the corners.
Another approach ...
CAShapeLayer
.fillColor
and .strokeColor
of that layer to yellow (or whatever color you want to use).lineWidth
to the same line width used for the gradient borderThen, set the .path
of the base shape layer to the same path as the gradient layer.
For example:
class AnotherGradientBorderView: UIView {
// let's use self's layer as a CAShapeLayer
override class var layerClass: AnyClass { CAShapeLayer.self }
private var selfLayer: CAShapeLayer { layer as! CAShapeLayer }
// border gradient
private var borderGradientLayer: CAGradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
// background needs to be clear
self.backgroundColor = .clear
selfLayer.fillColor = UIColor.yellow.cgColor
selfLayer.strokeColor = UIColor.yellow.cgColor
selfLayer.lineWidth = 4
borderGradientLayer.colors = [UIColor.red.withAlphaComponent(0.2).cgColor, UIColor.black.withAlphaComponent(0.2).cgColor]
self.layer.addSublayer(borderGradientLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
borderGradientLayer.frame = bounds
// Create a shape layer that defines the rounded path and border width
let shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 4 // gradient border width
shapeLayer.fillColor = UIColor.clear.cgColor //fill area will be transparent during masking
shapeLayer.strokeColor = UIColor.black.cgColor //stroke area will be visible during masking
//Half of the line width is drawn inside and other half of the line width is drawn outside the rectangle.
//Thats why oringal reactangle was shrunk from all side by half of the line width
let pth = UIBezierPath(roundedRect: bounds.insetBy(dx: 2, dy: 2),
cornerRadius: 24.0).cgPath
shapeLayer.path = pth
// Mask the gradient layer with the shape layer, only affecting the border
borderGradientLayer.mask = shapeLayer
// set the path of self's layer (it is a CAShapeLayer) to
// match the path of the border layer
selfLayer.path = pth
}
}
Output: