[iOS] CABasicAnimation 활용 (2) - 로딩 애니메이션 (UIBezierPath, CAShapeLayer)
오늘은 저번 시간에 이어 CABasicAnimation을 활용해서 새로운 애니메이션을 만들어 보았어요..!!
카카오톡에서 더보기 탭의 여러 메뉴들을 클릭하고 데이터를 받아올 때, 로딩화면이 있더라구요..?
저는 항상 이런것도 디자이너한데 부탁해서 Air BnB-Lottie를 이용해서 구현했는데, CABasicAnimation을 사용하고 보니깐 이것도 할 수 있겠다 생각이 들어서 바로 만들어봤어요.
먼저 화면을 먼저 보여드리면 다음과 같아요.
요런화면 어떤 요청을 보낼 때 본적 있죠?
오늘은 저걸 만들어볼게요!!
구현 방법
이 화면에서는 구현해야 할 UI요소가 몇가지일까요..?
저는 크게 2가지로 구성을 했는데요.
- 진행 트랙을 보여주는 뒤의 Background Layer
- 화면이 돌아가며 진행을 보여주는 Indicator Layer
이렇게 크게 2가지에요.
Q. 어 왜..? 제일 뒤의 회색 화면은 말을 안해요..?
👉 바로 저 부분은 UIView의 backgroundColor와 UIView.layer를 이용해서 기본으로 구성했어요!
그럼 우선 기본 UI를 먼저 구성해볼게요!
처음으로 UIView을 Custom으로 생성해주세요.
// 이름은 자유롭게 해주세요!
class LoadingIndicator: UIView {
}
이제 다음으로 제일 뒤의 화면 배경을 먼저 만들어줄게요! (아직 Layer를 이용하는 것이 아닌 젤 뒤 배경만이에요!)
여기서는 CALayer를 이용하지 않고 UIView.backgroundColor, UIView.layer.borderRadius를 활용할거에요.
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .darkGray
clipsToBounds = true
layer.cornerRadius = frame.width/2
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
backgroundColor = .darkGray
clipsToBounds = true
layer.cornerRadius = frame.width/2
}
이렇게 하면 둥근 배경의 화면만 떴을거에요!
이제는 위에서 언급했던 2가지의 구성요소 있죠?
그 중 첫번째, 진행 트랙을 보여주는 backgroundLayer를 UIBezierPath, CAShapeLayer를 이용해서 생성해줄거에요.
private lazy var backgroundLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineWidth = 5
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineCap = .round
layer.frame = bounds
return layer
}()
private lazy var circlePath: UIBezierPath = {
let center = CGPoint(x: bounds.width/2, y: bounds.height/2)
let path = UIBezierPath(arcCenter: center,
radius: bounds.width/2*2/3,
startAngle: .pi/2,
endAngle: 2*.pi - .pi/2,
clockwise: true)
return path
}()
override func draw(_ rect: CGRect) {
backgroundLayer.path = circlePath.cgPath
layer.addSublayer(backgroundLayer)
}
draw를 사용해서 커스텀으로 UIView를 그려주었는데요.
개발자 문서에 보면 이 메소드를 이용해서 UIView의 도화지 삼아서 render 시킬 수 있다고 나와있어요!
나머지 그려주는 부분에는 특별한 것이 없어요.
만약 CAShapeLayer를 이용해서 그림을 그려주는 것이 이해가 되지 않으면 앞 글을 보고오면 도움이 될 것 같아요..!!
여기서 radius를 현재 뷰의 반지름으로 설정하지 않은 이유는 젤 위의 애니메이션을 보시면 아시겠죠..?!
실제 트랙으로 나타나는 뷰가 원래 UIView보다 더 작아요!
다음으로 마지막 요소인 Indicator Layer를 만들어볼게요.
여기선 아까 backgroundLayer에서 만들어준 circlePath를 활용할거에요.
단 다른 점은 stokeEnd를 이용해서 끝을 잘라줄거라는거~~
private lazy var indicatorLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.strokeColor = UIColor.white.cgColor
layer.fillColor = UIColor.clear.cgColor
layer.lineWidth = 5
layer.lineCap = .round
layer.frame = bounds
// 이 부분을 이용해서 끝을 잘라주었어요!
layer.strokeEnd = 0.35
return layer
}()
override func draw(_ rect: CGRect) {
// circlePath는 아까 위에서 만들었죠.
// 똑같이 사용하면 됩니다.
indicatorLayer.path = circlePath.cgPath
layer.addSublayer(indicatorLayer)
}
자 이렇게까지하면 이제 모든 UI요소들은 만들어주었어요.
마지막으로 이제 애니메이션만 추가해주면 되겠죠?
여기서 CABasicAnimation을 이용할거랍니다!
func startAnimation() {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0
rotationAnimation.toValue = 2*.pi
rotationAnimation.duration = 2
rotationAnimation.repeatCount = HUGE
indicatorLayer.add(rotationAnimation, forKey: "rotate")
}
func cancelAnimation() {
layer.sublayers?.forEach { layer in
layer.removeAllAnimations()
layer.removeFromSuperlayer()
}
}
keyPath에는 많은 요소들이 있었는데 중앙을 기준으로 둥글게 둥글게 돌려주면 되어서 transform.rotation을 활용하였습니다.
이렇게까지 해주고 ViewController에서 한 번 생성하고 startAnimation()까지 호출해볼게요!
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let loadingIndicator = LoadingIndicator(frame: CGRect(x: 100, y: 100, width: 200, height: 200))
loadingIndicator.startAnimation()
self.view.addSubview(loadingIndicator)
}
}
여기까지 해주시면 제일 위와 같은 완성된 화면을 보실 수 있을거에요!!
재밌어서 이것저것 만들어보고 있었는데, 다음에는 또 다른 걸 만들어서 와볼게요 ㅎㅎㅎ
그럼 오늘 포스팅은 여기까지 해볼게요! :D
궁금한 점이나 이런 것도 해보면 좋겠어요 등등 다양한 의견있으시면 댓글 달아주세요.
감사합니다!