As an alternative of attempting to animate the gradient colours and areas, one other method is to rotate the CAGradientLayer
.
So, if we begin with a plain view:
we will add a CAGradientLayer
as a sublayer — right here, it has solely a single 1/2 alpha colour so we will simply see the framing:
As a result of we will probably be rotating that layer, we set that layer’s body to be bigger than the view so it’ll fully cowl it:
Subsequent, we set the gradient layer’s colours to clear, white, clear
:
and at last, apply a form layer masks to the view:
This is the way it appears to be like on a black background:
And listed here are some animated variations (too huge to embed right here): https://imgur.com/a/JXgyT7b
Right here is a few instance code for that:
// couple helpers for CGRect
extension CGRect {
public var heart: CGPoint { return CGPoint(x: midX, y: midY) }
public var diagonalExtent: CGFloat { return hypot(width, top) }
}
// instance view
class AnimatedGradientBorderedView: UIView {
public var lineWidth: CGFloat = 1.0 { didSet { mskLayer.lineWidth = lineWidth } }
public var cornerRadius: CGFloat = 16.0 { didSet { setNeedsLayout() } }
non-public let gradLayer = CAGradientLayer()
non-public let mskLayer = CAShapeLayer()
override init(body: CGRect) {
tremendous.init(body: body)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
tremendous.init(coder:aDecoder)
commonInit()
}
func commonInit() {
gradLayer.colours = [UIColor.clear.cgColor, UIColor.white.cgColor, UIColor.clear.cgColor]
layer.addSublayer(gradLayer)
mskLayer.fillColor = UIColor.clear.cgColor
// any opaque colour
mskLayer.strokeColor = UIColor.black.cgColor
mskLayer.lineWidth = lineWidth
}
override func layoutSubviews() {
tremendous.layoutSubviews()
// we'd like the gradient layer body to cowl the whole view
// when it rotates, so we make it a sq. with width/top
// equal to the diagonal dimension of self (plus just a bit "padding")
let diag: CGFloat = bounds.diagonalExtent + 2.0
gradLayer.body = bounds.insetBy(dx: -(diag - bounds.width) * 0.5, dy: -(diag - bounds.top) * 0.5)
// rounded-corners masks path, inset by lineWidth so it's fully inside self
mskLayer.path = UIBezierPath(roundedRect: bounds.insetBy(dx: lineWidth, dy: lineWidth), cornerRadius: cornerRadius).cgPath
layer.masks = mskLayer
doAnim()
}
func doAnim() {
// rotate the masks layer
let rotateAnimation = CABasicAnimation(keyPath: "rework.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = CGFloat(Double.pi * 2)
rotateAnimation.isRemovedOnCompletion = false
// alter rotation velocity as desired
rotateAnimation.length = 8.0
rotateAnimation.repeatCount=Float.infinity
gradLayer.add(rotateAnimation, forKey: nil)
}
}
// easy instance view controller
class MyViewController: UIViewController {
let testView = AnimatedGradientBorderedView()
override func viewDidLoad() {
tremendous.viewDidLoad()
testView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(testView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
testView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 34.0),
testView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -34.0),
testView.heightAnchor.constraint(equalToConstant: 492.0),
testView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
view.backgroundColor = .black
testView.backgroundColor = .clear
}
}