There is a problem that my gradient animation is not working correctly. If you add more colors to the gradientColors array, it works more or less, but there is a flickering effect.
Tried solving the problem via CATransaction, but it did not solve the problem. When I add more than 2 colors to the array, the animation seems to work, but there is a flickering effect
there are more than two colors in the colors array
there are 2 colors in the array
Maybe you have a solution to the problem ? I would be grateful for your help:)
class ViewController: UIViewController {
private enum Color {
static var gradientColors: [UIColor] = [
UIColor(red: 255, green: 255, blue: 255, alpha: 1),
UIColor(red: 255, green: 255, blue: 255, alpha: 0)
]
}
@IBOutlet weak var gradientView: UIView! {
didSet {
gradientView.layer.cornerRadius = 16
}
}
private var timer: Timer?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
animateBorderGradietion()
}
func animateBorderGradietion() {
let shape1 = CAShapeLayer()
shape1.path = UIBezierPath(
roundedRect: gradientView.bounds.insetBy(dx: 1.0, dy: 1.0),
cornerRadius: gradientView.layer.cornerRadius
).cgPath
shape1.lineWidth = 1.0
shape1.strokeColor = UIColor.white.cgColor
shape1.fillColor = UIColor.clear.cgColor
let gradient1 = CAGradientLayer()
gradient1.frame = gradientView.bounds
gradient1.type = .conic
gradient1.colors = Color.gradientColors.map { $0.cgColor }
gradient1.locations = calculateGradientLocation()
gradient1.startPoint = CGPoint(x: 0.5, y: 0.5)
gradient1.endPoint = CGPoint(x: 1, y: 1)
gradient1.mask = shape1
gradientView.layer.addSublayer(gradient1)
let shape2 = CAShapeLayer()
shape2.path = UIBezierPath(
roundedRect: gradientView.bounds.insetBy(dx: 1.0, dy: 1.0),
cornerRadius: gradientView.layer.cornerRadius
).cgPath
shape2.lineWidth = 1.0
shape2.strokeColor = UIColor.white.cgColor
shape2.fillColor = UIColor.clear.cgColor
let gradient2 = CAGradientLayer()
gradient2.frame = gradientView.bounds
gradient2.type = .conic
gradient2.colors = Color.gradientColors.map { $0.cgColor }
gradient2.locations = calculateGradientLocation()
gradient2.startPoint = CGPoint(x: 1, y: 1)
gradient2.endPoint = CGPoint(x: 0.5, y: 0.5)
gradient2.mask = shape2
gradientView.layer.addSublayer(gradient2)
self.timer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
CATransaction.begin()
CATransaction.setAnimationDuration(0.2)
CATransaction.setCompletionBlock {
gradient1.removeAnimation(forKey: "myAnimation")
gradient2.removeAnimation(forKey: "myAnimation")
let previous = Color.gradientColors.map { $0.cgColor }
let last = Color.gradientColors.removeLast()
Color.gradientColors.insert(last, at: 0)
let lastColors = Color.gradientColors.map { $0.cgColor }
let colorsAnimation = CABasicAnimation(keyPath: "colors")
colorsAnimation.fromValue = previous
colorsAnimation.toValue = lastColors
colorsAnimation.repeatCount = 1
colorsAnimation.duration = 0.2
colorsAnimation.isRemovedOnCompletion = false
colorsAnimation.fillMode = .both
gradient1.colors = lastColors
gradient2.colors = lastColors
gradient1.add(colorsAnimation, forKey: "myAnimation")
gradient2.add(colorsAnimation, forKey: "myAnimation")
}
CATransaction.commit()
}
}
private func calculateGradientLocation() -> [NSNumber] {
return Array(stride(from: 0, to: Color.gradientColors.count, by: 1))
.map { NSNumber(value: Double($0) / Double(Color.gradientColors.count)) }
}
}
Instead of trying to animate the gradient colors and locations, another approach is to rotate the
CAGradientLayer.So, if we start with a plain view:
we can add a
CAGradientLayeras a sublayer -- here, it has only a single 1/2 alpha color so we can easily see the framing:Because we will be rotating that layer, we set that layer's frame to be larger than the view so it will completely cover it:
Next, we set the gradient layer's colors to
clear, white, clear:and finally, apply a shape layer mask to the view:
Here's how it looks on a black background:
And here are some animated versions (too big to embed here): https://imgur.com/a/JXgyT7b
Here is some example code for that: