Skip to content

Instantly share code, notes, and snippets.

@thekan23
Created October 13, 2018 13:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thekan23/d58da01138a80554a4e8399a75db8953 to your computer and use it in GitHub Desktop.
Save thekan23/d58da01138a80554a4e8399a75db8953 to your computer and use it in GitHub Desktop.
DisplayLink + BezierPath를 사용한 애니메이션
import Foundation
import UIKit
class ElasticView: UIView {
private let topControlPointView = UIView()
private let leftControlPointView = UIView()
private let rightControlPointView = UIView()
private let bottomControlPointView = UIView()
private let elasticShape = CAShapeLayer()
private lazy var displayLink: CADisplayLink = {
let displayLink = CADisplayLink(target: self, selector: #selector(updateLoop))
displayLink.add(to: .current, forMode: .commonModes)
return displayLink
}()
func animateControlPoints() {
let overshootAmount: CGFloat = 10 // control point가 움직일 거리
UIView.animate(withDuration: 5, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5, options: .curveLinear, animations: {
self.topControlPointView.center.y -= overshootAmount
self.leftControlPointView.center.x -= overshootAmount
self.rightControlPointView.center.x += overshootAmount
self.bottomControlPointView.center.y += overshootAmount
}) { _ in
UIView.animate(withDuration: 0.45, delay: 0.0, usingSpringWithDamping: 0.15, initialSpringVelocity: 5.5, options: .curveLinear, animations: {
self.positionControlPoints()
}) { _ in
self.stopUpdatesLoop()
}
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
startUpdatesLoop()
animateControlPoints()
}
private func startUpdatesLoop() {
displayLink.isPaused = false
}
private func stopUpdatesLoop() {
displayLink.isPaused = true
}
private var controlPointViews: [UIView] {
return [topControlPointView, leftControlPointView, rightControlPointView, bottomControlPointView]
}
override init(frame: CGRect) {
super.init(frame: frame)
setupComponents()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupComponents()
}
@objc private func updateLoop() {
elasticShape.path = bezierPathForControlPoints()
}
private func setupComponents() {
elasticShape.fillColor = backgroundColor?.cgColor
elasticShape.path = UIBezierPath(rect: bounds).cgPath
layer.addSublayer(elasticShape)
for pointView in controlPointViews {
addSubview(pointView)
pointView.frame = CGRect(x: 0, y: 0, width: 5, height: 5)
pointView.backgroundColor = .blue
}
positionControlPoints()
}
private func positionControlPoints() {
topControlPointView.center = CGPoint(x: bounds.midX, y: 0)
leftControlPointView.center = CGPoint(x: 0, y: bounds.midY)
rightControlPointView.center = CGPoint(x: bounds.maxX, y: bounds.midY)
bottomControlPointView.center = CGPoint(x: bounds.midX, y: bounds.maxY)
}
// control points를 애니메이팅 할 때 이 메서드를 다시 호출해야 한다
private func bezierPathForControlPoints() -> CGPath {
let path = UIBezierPath()
// presentation 프로퍼티를 사용한 이유는 animation 도중 view의 실시간 위치를 얻기 위함
let top = topControlPointView.layer.presentation()?.position
let left = leftControlPointView.layer.presentation()?.position
let right = rightControlPointView.layer.presentation()?.position
let bottom = bottomControlPointView.layer.presentation()?.position
let width = frame.width
let height = frame.height
print("top: \(top!), left: \(left!), right: \(right!), bottom: \(bottom!), width: \(width), height: \(height)")
// 직사각형의 모서리와 모서리에 곡선을 더하면서 path를 생성
path.move(to: .zero)
path.addQuadCurve(to: CGPoint(x: width, y: 0), controlPoint: top!) // 우상단
path.addQuadCurve(to: CGPoint(x: width, y: height), controlPoint: right!) // 우하단
path.addQuadCurve(to: CGPoint(x: 0, y: height), controlPoint: bottom!)
path.addQuadCurve(to: .zero, controlPoint: left!)
return path.cgPath
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment