iOS7부터 블록 기반의 애니매이션 API 등장
// 기존 API - 현재는 deprecated
class func beginAnimations(_ animationID: String?,
context: UnsafeMutableRawPointer?)
class func commitAnimations()
// 블록 기반 API
class func animate(withDuration duration: TimeInterval,
delay: TimeInterval,
options: UIView.AnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil)
// 애니매이션 비활성화가 필요할 때 구역을 만들어 사용하던 것
class func setAnimationsEnabled(Bool)
// 블록 기반 API
class func performWithoutAnimation(_ actionsWithoutAnimation: () -> Void)
class func animate(withDuration: TimeInterval,
delay: TimeInterval,
usingSpringWithDamping: CGFloat,
initialSpringVelocity: CGFloat,
options: UIView.AnimationOptions,
animations: () -> Void,
completion: ((Bool) -> Void)?)
키-프레임 애니매이션
class func animateKeyframes(withDuration duration: TimeInterval,
delay: TimeInterval,
options: UIView.KeyframeAnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil)
// 키프레임 애니매이션 내부에서 호출
class func addKeyframe(withRelativeStartTime frameStartTime: Double,
relativeDuration frameDuration: Double,
animations: @escaping () -> Void)
스냅샷
func snapshotView(afterScreenUpdates: Bool) -> UIView?
func resizableSnapshotView(from: CGRect, afterScreenUpdates: Bool, withCapInsets: UIEdgeInsets) -> UIView?
UIKit Dynamics
커스텀 가능한 부분
프레젠테이션, 디스미스
let vc: UIViewController = ...
vc.modalPresentationStyle = .custom
vc.transitioningDelegate = ...
self.present(vc, animated: true, completion: nil)
UITabbarController
// TabbarController의 인터페이스
var selectedViewController: UIViewController?
var selectedIndex: Int
// 예제
let secondTab = 1
self.delegate = tabBarControllerDelegate
self.selectedIndex = secondTab
UINavigationController이 UICollectionViewController를 만났을 때
let layout1: UICollectionViewLayout
let layout2: UICollectionViewLayout
let layout3: UICollectionViewLayout
let cvc1: UICollectionViewController = UICollectionViewController(collectionViewLayout: layout1)
let cvc2: UICollectionViewController = UICollectionViewController(collectionViewLayout: layout2)
let cvc3: UICollectionViewController = UICollectionViewController(collectionViewLayout: layout3)
navigationController.pushViewController(cvc1, animated: true)
cvc2.useLayoutToLayoutNavigationTransitions = true
cvc3.useLayoutToLayoutNavigationTransitions = true
navigationController.pushViewController(cvc2, animated: true)
navigationController.pushViewController(cvc3, animated: true)
navigationController.popViewController(animated: true)
트랜지션이 뭔가?: 부모뷰의 자식뷰를 가진 뷰컨트롤러에, 자식 뷰컨트롤러를 붙이면서 부모 뷰컨에 붙어 있던 뷰를 자식 뷰컨의 뷰로 교체하는 것
트랜지션 과정에서 컨테이너 뷰 안에서 기존뷰를 없애고 새로운 뷰를 추가하는 과정이 애니매이션으로 진행됨 → 순간적으로 두 개가 동시에 존재하게 되는 구간이 있다.
전체 과정
UIViewControllerContextTransitioning: 트랜지션 과정에서의 핵심 객체를 위한 프로토콜
// 직접 만들거나 채택하는 프로토콜은 아니다.
// 트랜지션 관련 정보를 얻거나, 트랜지션 상태를 시스템에 전달하는 역할을 한다.
protocol UIViewControllerContextTransitioning: NSObjectProtocol {
var containerView: UIView { get }
func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?
func view(forKey key: UITransitionContextViewKey) -> UIView?
func initialFrame(for vc: UIViewController) -> CGRect
func finalFrame(for vc: UIViewController) -> CGRect
func completeTransition(_ didComplete: Bool) // Animation의 completion 블록에서 호출해야 한다. 트랜지션 cancel시에는 false로 인자를 넘긴다.
}
// 애니매이션 트랜지션을 위한 프로토콜. 애니매이터 객체에 사용한다.
// 인터렉션을 위해서는 UIViewControllerInteractiveTransitioning 프로토콜과 함께 써야 한다.
protocol UIViewControllerAnimatedTransitioning: NSObjectProtocol {
// 해당 트랜지션의 소요 시간을 반환한다.
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
// 애니메이션을 수행한다.
// 인터렉티브한데 퍼센트 기반이 아닌 경우에만 해당 구현을 빈 채로 둘 수 있다.
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)```swift
// 직접 만들거나 채택하는 프로토콜은 아니다.
// 트랜지션 관련 정보를 얻거나, 트랜지션 상태를 시스템에 전달하는 역할을 한다.
protocol UIViewControllerContextTransitioning: NSObjectProtocol {
var containerView: UIView { get }
func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController?
func view(forKey key: UITransitionContextViewKey) -> UIView?
func initialFrame(for vc: UIViewController) -> CGRect
func finalFrame(for vc: UIViewController) -> CGRect
func completeTransition(_ didComplete: Bool) // Animation의 completion 블록에서 호출해야 한다. 트랜지션 cancel시에는 false로 인자를 넘긴다.
}
애니매이션 단계 중간에 낄 수 있는 delegate
애니매이션 컨트롤러는 UIViewControllerAnimatedTransitioning을 채택해야 한다.
인터렉션 컨트롤러는 UIViewControllerInteractiveTransitioning 프로토콜을 채택해야 한다.
시스템에서 넘겨주는 객체는 UIViewControllerContextTransitioning 프로토콜을 채택한 채로 들어온다.
커스텀 프레젠테이션
TransitionDelegate 설정
// UINavigationControllerDelegate와 UITabBarControllerDelegate에도 유사 메소드가 있다.
protocol UIViewControllerTransitioningDelegate {
optional func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning?
optional func animationController(forDismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
optional func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
optional func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
// 이건 다음 버전에 추가
optional func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController?
}
present
delegate 메소드 호출 → 여기서 애니매이터 객체를 만들어서 넘긴다.
func animationController(forPresented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
transitionDuration을 호출해서 시간을 얻어낸다.
animateTransition 메소드를 호출한다.
// pseudo code
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let inView = transitionContext.containerView
let toView = transitionContext.viewController(forKey: .to)!.view
let fromView = transitionContext.viewController(forKey: .from)!.view
let size = toEndFrame.size
if self.isPresentation {
inview.addSubView(toView)
} else {
inview.insertSubView(toView, belowSubview: fromView)
}
UIView.animate(withDuration: self.transitionDuration, animation: {
if self.isPresentation {
toView.center = newCenter
toView.bounds = newBounds
} else {
// ...
}
}, completion: { finished in
transitionContext.completeTransition(finished)
}
}
context를 통해서 completeTransition을 호출한다.
iOS 7.0부터 모든 UINavigationController는 팝 제스쳐 지원
커스텀 트랜지션에 대한 상호작용 지원 → UIViewControllerInteractiveTransitioning
protocol UIViewControllerInteractiveTransitioning: NSObjectProtocol {
// animateTransition 대신 호출됨
// 이 안에서 Animator를 호출하게 된다.
func startInteractiveTransition(_ transitionContext: UIViewControllerContextTransitioning)
optional var completionCurve: UIView.AnimationCurve { get }
optional var completionSpeed: CGFloat { get }
}
인터렉티브 프레젠테이션 단계
CollectionView 에 대한 특별한 레이아웃 지원: UICollectionViewTransitionLayout
트랜지션할 때 주의점
UIViewControllerTransitionCoordinator