Skip to content

Instantly share code, notes, and snippets.

@kylehowells
Created February 25, 2023 23:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kylehowells/9681243715760e06ae276ad50f40789c to your computer and use it in GitHub Desktop.
Save kylehowells/9681243715760e06ae276ad50f40789c to your computer and use it in GitHub Desktop.
Animates a wave ripple along the top
import UIKit
// MARK: - WaveView
class WaveView: UIView {
private var shapeLayer: CAShapeLayer = CAShapeLayer()
// MARK: - Animation Properties
/// 0 - Static
/// 20 - Very fast
var speed: CGFloat = 10
/// Wave Height
var frequency: CGFloat = 6.0
/// Wave phase
private var phase: CGFloat = 0.0
/// Wave color
var preferredColor: UIColor = UIColor.cyan {
didSet {
self.shapeLayer.fillColor = self.preferredColor.cgColor
self.shapeLayer.strokeColor = self.preferredColor.cgColor
}
}
// MARK: - Start Animation
enum Direction {
case right
case left
}
func animationStart(direction: Direction, speed: Double) {
self.preferredColor = UIColor.cyan
self.layer.addSublayer(self.shapeLayer)
if direction == .right {
self.speed = -speed
} else {
self.speed = speed
}
self.startDisplayLink()
}
// MARK: - Display Link
private weak var displayLink: CADisplayLink?
private var startTime: CFTimeInterval = 0
private func startDisplayLink() {
self.startTime = CACurrentMediaTime()
self.displayLink?.invalidate()
let displayLink = CADisplayLink(target: self, selector:#selector(self.handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}
@objc private func handleDisplayLink(_ displayLink: CADisplayLink) {
self.phase = (CACurrentMediaTime() - self.startTime) * self.speed
self.updatePath()
}
private func stopDisplayLink() {
self.displayLink?.invalidate()
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
self.shapeLayer.frame = self.bounds
self.updatePath()
}
func updatePath() {
let path: UIBezierPath = UIBezierPath()
let width: CGFloat = self.bounds.width
let height: CGFloat = self.bounds.height
let mid: CGFloat = height * 0.25
let waveLength: CGFloat = width / self.frequency
let waveHeightCoef: CGFloat = self.frequency
path.move(to: CGPoint(x: 0, y: self.bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: mid))
for x in stride(from: 0, through: width, by: 1)
{
let actualX: CGFloat = x / waveLength
let sine: CGFloat = -sin((actualX + self.phase))
let y: CGFloat = waveHeightCoef * sine + mid
path.addLine(to: CGPoint(x: x, y: y))
}
path.addLine(to: CGPoint(x: CGFloat(width), y: self.bounds.maxY))
path.addLine(to: CGPoint(x: 0, y: self.bounds.maxY))
self.shapeLayer.path = path.cgPath
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment