8강 정리
깨달은 것
1. Dynamic Animator
-> Dynamic Animator에게 뷰를 넘겨주면 뷰의 권한이 아예 애니메이터에게 넘어간다.
-> 뷰 계층에는 계속 남아 있지만, 사용자가 제어 불가능 && 오토레이아웃도 적용 X
-> animator가 running이라면 함수를 호출하여 적용해야하고, stop되면 사용자가 제어 가능해진다.
-> animator가 아이템을 놓는다면 오토레이아웃이 바로 적용된다.
-> bound는 유지한 채, transform과 (frame의) center로 직접 값을 움직인다.
-> 즉, 애니메이션 도중에 계속 값이 변화하고, 실제 적용된다.
2. UIView.animate
-> Dynamic Animator과 다르게 여전히 제어권은 사용자에게 있다.
-> 다만, 현재 뷰를 연속적으로 변화시키면서 변화된 값을 바탕으로 매번 화면에 드로잉을 하는게 아니라
-> 현재 뷰는 이미 값을 변경 완료시켜놓고, 다른 ??이가 뷰를 따로 생성해서 변화되어가는 과정을 그려주는 듯 하다.
-> 애니메이션 중간에 속성값을 변경해봤는데, 변경 완료된 뷰를 기준으로 애니메이션을 생성하는 것 같다.(그러니깐 끝점을 향해? 역추적?)
-> 뷰의 변화되가는 모습을 그리는 ??가 private라서 현재 드로잉 위치를 꺼내 쓸 수 없고, 일시정지, 재생 등등의 기능이 어렵다고 한다.
-> frame을 자유자재로 움직이고 나서 오토레이아웃을 적용하고 싶다면 setNeedsUpdateConstraints()을 호출하면 된다.
3. UIViewPropertyAnimator.running...
-> animate 상위 버전인듯하고, 아마 animate는 depreciate될 것 같다.
-> 애니메이션 옵션에 .repeat가 안먹히는데, UIViewPropertyAnimator에 직접 반복 횟수를 알려주는 함수를 호출해야한다.
-> animate과 다르게 뷰를 그려주는 ??이를 알 수 있어서, 애니메이션 중간에 이어그린다던지, 일시정지, 재생 등등의 다양한 기능을 재공한다고 한다.
= 정리하고 보니 조금 이해되는 듯 싶다...
학습 내용
애니메이션의 종류
애니메이션의 종류는 크게 UIView 애니메이션, UIViewController 애니메이션이 있습니다.
이번 강의에선 UIView 애니메이션에 대해서만 다룹니다.(복습 시에 UIViewController 애니메이션을 보시기 바랍니다.)
들어가기에 앞서.. CGAffineTransform
UIView에 transform이라는 행렬이 있습니다.
초기값이 identity이며, 이 행렬을 통해 scale, rotate, inverse, translate 등을 구현할 수 있습니다.
행렬은 CGAffineTransform.identity를 통해 얻을 수 있고, 다양한 행렬 또한 적용할 수 있습니다.
UIView 애니메이션
UIView 애니메이션을 구현하는 방법에는 UIKit 또는 Core Animation을 이용하는 방법 2가지가 있습니다.
애플은 UIKit을 사용할 것을 권장하고 있고, 이 강의 또한 UIKit을 이용한 간단한 방법만을 소개하고 있습니다.
Core Animation을 이용한다면, CALayer와 setNeedsDisplay 함수를 통해 애니메이션을 구현할 수 있습니다.
UIKit을 이용한 UIView 애니메이션에는 크게 4가지 종류가 있습니다. (아래의 순으로 발달해왔습니다.)
1. (iOS 2) beginAnimations, commitAnimation <- depreciate 되었습니다.
2. (iOS 4) closure 형태인 animate, transition 메서드
3. UIDynamicAnimator 클래스를 이용한 애니메이션
4. (iOS 10) UIViewPropertyAmimation
입니다.
UIKit 레벨의 애니메이션 함수들이 마련되어 있으므로, 덕분에 많은 양의 코드로 Core Animation을 구현할 필요가 없어졌습니다.
UIView의 animate 함수
UIView의 클래스 메서드로 animate 함수가 있습니다.
사용은 UIView.animate(withDuration: TimeInterval, delay: TimeInterval, options: UIView.AnimationOptions, animations: () -> Void, completion: ((Bool) -> Void)?)으로 사용합니다.
UIView.AnimationOptions에 다양한 값들이 있으며 단, transition 옵션은 animate에서 무시됩니다.(애니메이션 옵션은 함수에서 적합한 옵션만 실행됩니다.)
애니메이션 효과를 줄 수 있는 속성은 위와 같고, animations 인자 안에서 해당 속성들을 바꾼다면 duration에 걸쳐 바뀌게 됩니다.
물론 인터페이스에 관련된 작업이므로 main 쓰레드에서 동작합니다.(신기하게도 위의 속성 값들만 인지해서 드로잉한다고 합니다.)
animations으로 넘겨준 클로저는 곧바로 실행되므로, 비록 애니메이션이 진행중일지라도 속성 값들은 전부 바뀌고 있는 상태의 값이 아닌 클로저에서 설정한 값으로 설정됩니다.
UIView의 transition 함수
UIView의 클래스 메서드로 transition 함수가 있습니다.
두 가지의 함수가 있는데 UIView.transition(from: UIView, to: UIView, ...)와 UIView.transition(with: UIView, ...) 입니다.
전자는 viewController에서 from의 뷰의 자리를 to의 뷰로 바꿔넣습니다.
후자는 뷰의 자리는 유지한 채로 새로운 뷰를 다른 버퍼에 미리 그려놓은 뒤 transtion합니다.
UIViewPropertyAnimator
기존에 UIView.animate는 시작값과 종료값 밖에 가져올 수 없다는 단점이 있었는데, UIViewPropertyAnimator 클래스가 생겨난 덕분에 애니메이션의 다양한 상태를 제어할 수 있게 되었습니다.
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: TimeInterval, delay: TimeInterval, options: UIViewAnimationOptions, animations: () -> Void, completion: ((position: UIViewAnimatingPosition) -> Void)? = nil) 함수를 통해 애니메이션을 구현할 수 있고, animate와 다르게 complete에 position이라는 인수가 추가되었습니다.
position은 .start, .current, .end를 알 수 있습니다. (정방향으로 시작해서 끝났다면 .end를, 역방향을 그리고 끝났다면 .start를, 방해받고 중간에 끝났다면 .current를 받습니다.)
animate와 마찬가지로 동일한 속성을 변경하는 애니메이터가 있다면 늦게 온 애니메이터가 이기게 됩니다.(중단된 애니메이터는 .current를 받습니다.) -> 즉 서로 다른 속성들을 가지고 있던 애니메이터는 겹치는 속성을 가진 다른 애니메이터가 온 직후 completion을 수행하며 이때 .current인자를 받으며 끝내게 됩니다.
animations의 클로저로 변경한 속성값들은 바로 적용이 됩니다.
시각적인 뷰는 아직 움직이는 것처럼 보이지만 코드로 값을 확인해보면 이미 애니메이션이 완료된 속성값으로 적용이 되어 있습니다.
-> 드로잉을 외부(이 부분에선 공부가 필요할 것 같습니다.)에 맡기는 형태이기때문에 값들은 바로 바뀔지 몰라도, 드로잉을 외부에서 천천히 그려주고 있는 상황입니다.
Dynamic Animation
애니메이션에 물리 엔진을 도입하는 방법입니다.
UIDynamicAnimation 클래스를 활용하고, 이를 위해서 수행해야 할 것은 총 세단계 입니다.
1. 인스턴스를 만듭니다. var animator = UIDynamicAnimator(referenceView: UIView)
-> 여기서 referenceView는 아이템들을 모두 포함할 수 있는 상위 뷰라면 어느 것을 써도 상관 없습니다.
-> 뷰 컨트롤러의 뷰를 사용하면 안전하지만, 객체 지향을 준수하는 것이 좋으므로 최대한 모두 포함하면서 최소 상위 뷰를 쓰길 권장합니다.
-> 이 객체를 중심으로 다이나믹 애니메이션을 구현합니다.
2. 동작을 만듭니다. let gravity = UIGravityBehavior() -> animator.addBehavior(gravity)
3. 동작을 받게되는 항목을 만들어야 합니다. let item1: UIDynamicItem = -> gravity.addItem(item1)
UIView는 UIDynamicItem 프로토콜을 채택했기에 다이나믹 아이템으로 사용 가능합니다.
여기서 bounds는 읽기 전용인데, 실제로 애니메이터는 bounds를 변경하지 않고 center와 transform을 가지고 변경합니다.
다이나믹 애니메이터에게 아이템을 추가했다면, 소유권은 강제적으로 애니메이터에게 넘어가므로 뷰의 값들을 바꿀 수 없습니다.
따라서, 애니메이터에서 아이템을 제거하거나, updateItemUsingCurrentState함수를 불러서 뷰의 값을 설정해야 합니다.
Behaviors
애니메이터에 동작만 추가하면 안되고, 동작의 속성을 설정해주어야 합니다.
각각 동작들의 속성은 다 달라서 위의 사진처럼 해당하는 변수들을 설정해주어야 합니다.
한 가지 주의할 점은 UICollisionBehavior에서 경계선의 확인은 한 프레임당 한번씩 체크하게 되며, 이것보다 더 빠르게 애니메이션이 움직인다면, 경계선 밖으로 튀어 나가게 되고 다시는 돌아 올 수 없는 미지의 공간으로 가버립니다.
경계선 안에 있는 걸 무조건적으로 보장하지 않습니다.
또한, CollisionBehavior는 델리게이트 메서드가 있어서 충돌이 발생했을 때, 특정 액션을 취할 수 있습니다.
UIPushBehavior은 지속적이거나 일시적인 힘으로 하나 이상의 다이나믹 항목에 힘을 가하게 될때 사용하게 됩니다.
단, .instantaneous이면 한 번 수행하고, 계속 애니메이터에 의미없이 남아있기 때문에 지워주는 것이 좋습니다.
UIDynamicItemBehavior
애니메이터의 behavior입니다.(item이 아닙니다)
전체 아이템들의 속성을 설정하거나 값을 얻을 수 있습니다.
UIDynamicBehavior
UIDynamicBehavior은 모든 behavior들의 슈퍼클래스입니다.
모든 동작을 모아서 하나의 동작으로 만들 때 사용됩니다.(중력 + 마찰력 같은 상황)
여러분들만의 행동의 조합으로 하위 클래스를 만들 수도 있습니다.
모든 behavior들은 action이라는 함수를 가지고 있으며, 구현하면 해당 behavior의 동작이 일어날 때마다 action이 수행됩니다.
Stasis(정지)