개발/IOS Swift

[해석] Introduction to Core Animation: Step-by-step Guide

doyou1 2021. 11. 26. 22:20
반응형

https://www.mobindustry.net/blog/introduction-to-core-animation-step-by-step-guide/

 

Introduction to Core Animation: Step-by-step Guide | Mobindustry

Animation is an important part of almost every user interface, both for mobile apps and web platforms. Developers implement various animated elements to make their apps more compelling. In this article we’ll tell you how to work with Core Animation, a gr

www.mobindustry.net

 

애니메이션은 모바일 앱과 웹 플랫폼 모두에서 거의 모든 사용자 인터페이스의 중요한 부분입니다. 개발자들은 그들의 앱을 더 매력적으로 만들기 위해 다양한 애니메이션 요소들을 구현합니다. 이 기사에서는 iOS용 그래픽 렌더링 프레임워크인 Core Animation을 사용하는 방법에 대해 설명하겠습니다.
참고: 이 튜토리얼에서는 사용자가 Swift 및 UIKit 프레임워크에 기본적으로 익숙하다고 가정합니다.

 

CALayer

지금쯤이면 이미 view가 무엇인지 알았을 거야. 뷰로 작업할 때마다 레이어로 간접적으로 작업할 수 있습니다. layer이란 무엇일까요? UIKit 프레임워크의 모든 뷰에는 레이어가 포함되어 있습니다. 이 정보를 보려면 고유한 UIView를 작성하고 layer property에 액세스할 수 있습니다.

 

let myView = UIView() myView.layer

 

UIView의 색상, 크기 및 기타 특성을 변경함으로써 UIView의 기본 layer를 변경하고 있습니다. 다른 말로 하자면, UIView는 screen에 애니메이션을 렌더링한다. 이 Class는 UIKit의 하위 레벨 추상화인 Core Animation 프레임워크에 의해 제시됩니다.

 

Core Animation은 우리가 OpenGL 기반의 그래픽 작업을 쉽게 하기 위해 만들어졌습니다. 코어 애니메이션을 사용하면 간단한 작업을 설명하는 데 필요한 코드 줄이 줄어듭니다. UIKit은 복잡한 UI와 애니메이션을 요구하지 않기 때문에 핵심 애니메이션 위에 있는 상위 수준의 프레임워크입니다. 이는 다음과 같은 계층(hierarchy)으로 설명될 수 있습니다.

 

 

CALayer에 대해 더 알아야 할 것이 있나요?

  • Layer는 모델 객체입니다. 데이터 속성을 제공하고 로직을 구현하지 않습니다.
  • Layer에는 컨텐츠가 화면에 표시되는 방식(경계선, 테두리 색상, 요소 위치, 그림자 등)에 영향을 미치는 일련의 속성을 포함하여 시각적으로 미리 정의된 특성이 있습니다.
  • 코어 애니메이션은 레이어 콘텐츠 캐싱을 최적화하고 GPU에서 직접 빠르게 드로잉할 수 있다.

 

Layer animation

Layer 애니메이션은 뷰 애니메이션과 동일한 방식으로 작동합니다. 특정 시간대에 대한 초기 값과 최종 값 사이의 속성을 애니메이션화하고 Core Animation이 중간 렌더링을 처리하도록 하면 됩니다. 그러나 Layer에는 UIKit 뷰보다 더 많은 애니메이션 특성이 있습니다. 따라서 맞춤형 효과를 개발할 때 유연성이 높아집니다.

 

Core Animation을 사용하여 간단한 애니메이션을 만들어 보겠습니다.

 

class ViewController: UIViewController {
	var myView: UIView!

	override func viewDidLoad() {
		super.viewDidLoad()
		let frame = CGRect(origin: CGPoint.zero, size: CGSize(width: 100, height: 100))
		myView = UIView(frame: frame)
		myView.backgroundColor = .black
		view.addSubview(myView)
	}

	override func viewDidAppear(_ animated: Bool) {
		super.viewDidAppear(animated)

		let animation = CABasicAnimation(keyPath: "position.x")
		animation.fromValue = CGPoint.zero
		animation.toValue = view.bounds.size.width
		animation.duration = 0.5
		animation.beginTime = 
 + 0.3
		animation.repeatCount = 4
		animation.autoreverses = true
		
		myView.layer.add(animation, forKey: nil)
	}
}

 

이 예에서는 viewDidLoad method을 사용하여 myView라는 이름으로 자체 UIView를 만들었습니다. viewDidAppear method으로 x축을 따라 이동하도록 myView를 설정하였습니다. 코어 애니메이션의 애니메이션 개체는 데이터 모델일 뿐입니다. 애니메이션을 작성하려면 먼저 모델의 인스턴스를 작성하고 그에 따라 모델의 속성을 설정하십시오. CABasicAnimation의 인스턴스는 잠재적 레이어의 애니메이션을 설명합니다. 지금 실행하거나 나중에 실행하거나 실행하지 않을 수 있습니다.

 

레이어에서 수행할 수 있는 애니메이션의 예입니다.

 

CABasicAnimation의 특성과 방법에 대해 자세히 알아보겠습니다.

 

1) CABasicAnimation 클래스의 인스턴스를 만들고 keyPath로 설정했습니다. keyPath는 우리 애니메이션의 목적을 정의합니다. 예를 들어, keyPath: "position.x"는 애니메이션이 값에 따라 X축을 따라 왼쪽 또는 오른쪽으로 이동한다는 의미입니다.

 

CABasicAnimation(keyPath: "position.x")

 

이 경우 keyPath는 CALayer 클래스의 다양한 속성을 설명합니다. 우리의 레이어가 다른 특성을 가지고 있기 때문에, 우리는 border, fill, position along different axis, radius, turn과 같은 것들을 변화시키고 애니메이션화할 수 있다. 예를 들어, 우리는 X축을 따라 위치를 바꿉니다.

 

2) 다음 속성은 애니메이션이 어느 위치에서 position.x 키를 따라 이동하기 시작하고 어느 지점에서 멈추는지 보여줍니다.

 

animation.fromValue = CGPoint.zero animation.toValue = view.bounds.size.width

 

3) 이전에 UIView.animation(...)으로 작업했다면 duration 속성에 익숙할 것입니다. duration은 애니메이션이 수행되는 시간을 나타냅니다. 위의 값은 우리의 레이어가 0.5초 안에 position.x를 따라 Value에서 point로 이동한다는 것을 보여준다.

 

animation.duration = 0.5

 

4) BeginTime 속성은 애니메이션의 시작을 설정합니다. CACurentMediaTime 기능은 애니메이션이 current time 값에서 시작되며 + 0.3은 시작 시간을 0.3초 지연시킵니다. 이 값을 5로 변경하고 프로젝트를 다시 시작하십시오. 그러면 애니메이션은 레이어에 추가한 후 5초 후에 시작됩니다.

 

animation.beginTime = CACurrentMediaTime() + 0.3

 

5) 애니메이션은 설정한 횟수만큼 반복됩니다.

 

animation.repeatCount = 4

 

6) autoreverses를 true로 설정하면 애니메이션이 끝나는 대로 반대로 수행됩니다. myView는 미리 정해진 궤적을 따라 이동한 후 되돌아갑니다. autoreverses 속성이 이 일을 담당합니다. 이 줄에 주석을 달 경우 autoreverses는 기본적으로 false이 됩니다. 이 경우 myView는 한 방향으로만 이동합니다.

 

animation.autoreverses = true

 

7) 이 코드는 myView 레이어에 애니메이션을 추가합니다. 이 코드를 삽입하면 애니메이션이 생동합니다.

 

myView.layer.add(animation, forKey: nil)

 

이 예에서는 간단한 애니메이션을 만들고 그 속성을 분석했습니다. 키를 position.y, border, backgroundColor 등으로 변경하여 다른 도면층의 특성을 사용하여 동일한 작업을 수행합니다. 키를 변경하는 동안 fromValue와 toValue도 변경해야 합니다.

 

Animation delegates

UIView 애니메이션은 애니메이션의 끝을 알려주는 closure가 있습니다. 또한 Core Animation에는 애니메이션의 시작과 끝을 알려주는 방법이 있습니다. 이 경우, closure 대신, 우리는 delegate와 함께 일하고 있습니다. Delegate는 CABasicAnimation의 속성이며 CAAnimationDelegate 프로토콜에 속합니다. animation.deligate = self라는 코드 줄을 추가하고 CAAnimationDelegate 메서드를 모두 구현해 보겠습니다.

 

extension ViewController: CAAnimationDelegate {
	func animationDidStart(_ anim: CAAnimation) {
		print("animation did start")
	}

	func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
		print("animation did finish")
	}
}

 

보시다시피 animationDidStart method는 애니메이션의 시작을 알려주고, animationDidStop method는 애니메이션이 끝났음을 알려준다.

 

Spring animation

이제 CABasicAnimation에 대해 살펴보았으므로 CABasicAnimation의 후속 제품인 CASpringAnimation으로 이동할 수 있습니다. CASpringAnimation은 개발자들이 Spring motion을 모방한 애니메이션 효과를 만들 수 있게 해준다. CASpringAnimation의 새로운 속성을 보기 위해 viewDidLoad 메소드의 코드를 아래에 제공된 코드로 대체하겠습니다.

 

let jump = CASpringAnimation(ketPath: "transform.scale")
jump.damping = 10
jump.mass = 1
jump.initialVelocity = 100
jump.stiffness = 1500.0

jump.fromValue = 1.0
jump.toValue = 2.0
jump.duration = jump.settlingDuration
myView.layer.add(jump, forKey: nil)

 

이것은 Spring-based 애니메이션을 제공합니다. keyPath를 transform.scale로 변경하여 myView의 크기를 1.0에서 2.0으로 변경하는 새로운 키로 스케일을 설정합니다.

 

다음과 같은 새 매개 변수도 볼 수 있습니다:

 

damping - damping 비율을 계산하는 데 사용됩니다.

mass(질량?) - 애니메이션 속성의 유효 mass. 0보다 커야 합니다.

stiffness(강성?) - 스프링 stiffness 계수. 값이 높을수록 움직이는 물체에 더 큰 힘을 주는 스프링이 강해집니다.

initialVelocity(초기 속도) - 중량에 적용되는 초기 impulse

jump.settlingDuration - 이 값은 애니메이션 duration(기간)을 설정합니다.

 

CAShapeLayer, CGPath, and UIBezierPath

CAShapeLayer는 다양한 모양(단순하고 복잡한)을 그리는 데 사용되는 CALayer 하위 클래스입니다.

CGPath는 그래픽 문맥에 그려야 하는 도형과 선에 대한 수학적 설명이다.

UIBezierPath는 Custom view에서 렌더링할 수 있는 직선 및 곡선 세그먼트로 구성된 Path입니다.

 

UIBezierPath를 사용하여 모든 모양 또는 선 세그먼트를 그릴 수 있습니다. 이 클래스에는 원, 반원, 사각형 등의 다양한 렌더링 방법이 있습니다. 점 A에서 점 B까지 선과 곡선을 추가하여 새 도형을 작성할 수도 있습니다.

 

UIBezierPath에는 shape의 수학적 설명을 담당하는 CGPath 속성이 포함되어 있습니다. CGPath는 shape에 대한 렌더링 방법을 제공하지 않으므로 UIBezierPath로 shape를 렌더링하고 속성을 CAShapeLayer에 할당합니다. 이렇게 하면 우리가 만든 모양이 화면에 나타납니다.

 

어떻게 작동하는지 보려면 새 ViewController를 만들고 ShapeLayer를 뷰에 추가해 보겠습니다. 이제 우리는 UIBezierPath로 화면 중앙에 drop-like 모양의 모양을 그릴 것입니다.

 

class ViewController: UIViewController {
	var shapeLayer: CAShapeLayer!

	override func vierDidLoad() {
		super.viewDidLoad()

		let shapeLayer = CAShapeLayer()
		view.layer.addSublayer(shapeLayer)

		self.shapeLayer = shapeLayer
	}

	override func viewDidAppear(_ animated: Bool) {
		super.viewDidAppear(animated)

		let path = UIBezierPath()
		path.move(to: view.center)
		path.addCurve(
		to: view.center, 
		controlPoint1: CGPoint(x: view.center.x + 150, y: view.center.y + 150),
		controlPoint2: CGPoint(x: view.center.x - 150, y: view.center.y + 150))

		path.lineWidth = 2

		shapeLayer.fillColor = UIColor.clear.cgColor
		shapeLayer.strokeColor = UIColor.blue.cgColor
		shapeLayer.path = path.cgPath
	}
}

 

UIBezierPath를 사용하여 drop-like 모양의 수학적 설명을 만들고 렌더링할 CAShapeLayer에 경로를 할당했습니다.

 

모양을 설명하는 주요 속성을 기록해 두십시오.

 

1) 먼저 모양을 렌더링하기 위한 시작점을 설명합니다. 이 경우 화면 중앙이 됩니다.

 

path.move(to: view.center)

 

2) addCurve method를 사용하면 원을 추가할 수 있습니다. 첫 번째 속성은 원의 끝점을 설명합니다. 이 예에서 원은 시작점과 끝점이 같기 때문에 닫힌 것처럼 보인다. controlPoint1 및 controlPoint2와 같은 속성은 원이 그려질 점을 설명합니다. 드롭 효과를 얻기 위해 CGPoint로 위에서 볼 수 있는 특정 여백을 설정했습니다.

 

path.addCurve(to: view.center,
           controlPoint1: CGPoint(x: view.center.x + 150, y: view.center.y + 150),
           controlPoint2: CGPoint(x: view.center.x - 150, y: view.center.y + 150))

 

Creating a pull-to-refresh gesture with Core Animation

풀 투 리프레시(Pull-to-Refresh)는 사용자가 더 많은 데이터를 검색하기 위해 목록을 풀다운할 수 있는 제스처입니다.

참고: 이 튜토리얼에서는 사용자가 이미 UIPanGestureRecognizer로 작업했다고 가정합니다. 만약 당신이 이 수업에 익숙하지 않다면, 당신은 여기에서 더 배울 수 있다.

 

class ViewController: UIViewController {
	@IBOutlet weak var activityIndicator: UIActivityIndicatorView!

	let shapeLayer = CAShapeLayer()

	override func viewDidLoad() {
		super.viewDidLoad()

		activityIndicator.isHidden = true

		let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gestureAction))
		view.addGestureRecognizer(gestureRecognizer)
		view.layer.addSublayer(shapeLayer)
	}

	override func viewDidAppear(_ animated: Bool) {
		super.viewDidAppear(animated)

		shapeLayer.fillColor = UIColor.black.cgColor
		shapeLayer.path = createPath(point: CGPoint(x: view.bounds.maxX / 2, y: view.bounds.minY))
	}

	@objc func gestureAction(_ sender: UIPanGestureRecognizer) {
		let point = sender.location(in: view)

		struct AnimationSetting {
			static var isAnimation = false
			static var isLoading = false
		}

		switch sender.state {
		case .began:
			AnimationSetting.isAnimation = point.y < 40 
			if AnimationSetting.isAnimation {
				shapeLayer.removeAllAnimations()
			}
		
		case .changed:
			guard AnimationSetting.isAnimation else { return }
			shapeLayer.path = createPath(point: point)

		case .ended, .failed, .cancelled:
			guard AnimationSetting.isAnimation else { return }
			animationStartingPosition(fromPoint: point)

		default:
			break			
		}
	}

	func createPath(point: CGPoint) -> CGPath {
		let bezierPath = UIBezierPath()
		
		let startPoint = CGPoint(x: 0, y: 0)
		let endPoint = CGPoint(x: view.bounds.maxX, y: view.bounds.minY)

		bezierPath.move(to: startPoint)
		bezierPath.addCurve(to: endPoint, controlPoint1: point, controlPoint2: point)
		bezierPath.close()

		return bezierPath.cgPath
	}

	func animationStartingPosition(fromPoint: CGPoint) {
		let animation = CASPringAnimation(keyPath: "path")
		animation.fromValue = createPath(point: fromPoint)
		animation.toValue = createPath(point: CGPoint(x: view.bounds.maxX / 2, y: view.bounds.minY))

		animation.damping = 10
		animation.initialVelocity = 20.0
		animation.mass = 2.0
		animation.stiffness = 1000.0

		animation.duration = animation.settlingDuration

		animation.delegate = self

		shapeLayer.add(animation, forKey: nil)
		shapeLayer.path = createPath(point: CGPoint(x: view.bounds.maxX / 2, y: view.bounds.minY))
	}
}

//MARK: - CAAnimationDelegate

extension ViewController: CAAnimationDelegate {
	func animationDidStart(_ anim: CAAnimation) {
		activityIndicator.isHidden = false
		activityIndicator.startAnimating()
	}

	func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
		shapeLayer.removeAllAnimations()
		
		activityIndicator.isHidden = true
		activityIndicator.stopAnimating()
	}
}

 

Exmaple of pull-to-refresh animation:

 

Conclusion(결론)

이 튜토리얼에서는 Core Animation 클래스를 사용하여 낮은 수준의 액세스를 통해 자신만의 애니메이션을 만드는 방법을 배웠습니다. 또한 레이어와 레이어의 주요 특성에 익숙해졌습니다. 이제 path key를 사용하여 애니메이션 도형을 렌더링하여 프로젝트의 UI에 효과를 추가할 수 있습니다.

 

반응형