@@ -18,135 +18,155 @@ final class CollectionViewCellTransitioning: NSObject {
1818 let shrinkDuration : Double = 0.2
1919
2020 private let blurEffectView = UIVisualEffectView ( )
21- private let dimmingView = UIView ( )
2221 private let backgroundView = UIView ( )
2322
2423 override init ( ) {
2524 super. init ( )
26- blurEffectView. effect = UIBlurEffect ( style: . light)
27- dimmingView. backgroundColor = . black
25+ blurEffectView. effect = UIBlurEffect ( style: . dark)
2826 backgroundView. backgroundColor = . black
2927 }
3028}
3129
30+ // MARK: - UIViewControllerTransitioningDelegate
31+
32+ /// transition 속성을 변경하고 UIViewControllerAnimatedTransitioning를 채택한 자기 자신을 반환합니다.
33+ extension CollectionViewCellTransitioning : UIViewControllerTransitioningDelegate {
34+ func animationController( forPresented presented: UIViewController , presenting: UIViewController , source: UIViewController ) -> ( any UIViewControllerAnimatedTransitioning ) ? {
35+ transition = . present
36+ return self
37+ }
38+
39+ func animationController( forDismissed dismissed: UIViewController ) -> ( any UIViewControllerAnimatedTransitioning ) ? {
40+ transition = . dismiss
41+ return self
42+ }
43+ }
44+
3245// MARK: - UIViewControllerAnimatedTransitioning
3346extension CollectionViewCellTransitioning : UIViewControllerAnimatedTransitioning {
3447 func transitionDuration( using transitionContext: ( any UIViewControllerContextTransitioning ) ? ) -> TimeInterval {
3548 transitionDuration
3649 }
3750
51+ /// 실제 Transition 구현 부분
3852 func animateTransition( using transitionContext: any UIViewControllerContextTransitioning ) {
53+ /// 전환에 관련된 뷰를 사용합니다.
3954 let containerView = transitionContext. containerView
55+ /// 1. 깔끔한 애니메이션을 위해 transition되는 containerView의 모든 요소를 제거합니다.
4056 containerView. subviews. forEach { $0. removeFromSuperview ( ) }
4157
42- addBackgroundViews ( to: containerView)
58+ /// 2. 전환에 사용되는 뷰에 blurEffectView 추가합니다.
59+ blurEffectView. frame = containerView. frame
60+ blurEffectView. alpha = transition. next. blurAlpha
61+ containerView. addSubview ( blurEffectView)
4362
4463 let fromView = transitionContext. viewController ( forKey: . from)
4564 let toView = transitionContext. viewController ( forKey: . to)
4665
4766 var thumbnailView : ThumbnailView ?
67+ /// 3. Present: 시작하는 뷰의 썸네일을 가져옵니다. (시작 좌표 및 복사를 위해)
4868 if let navigationController = ( fromView as? UINavigationController ) , let viewController = ( navigationController. topViewController as? BroadcastCollectionViewController ) {
4969 thumbnailView = viewController. selectedThumbnailView
5070 }
5171
72+ /// 3. Dismiss: 목적지 뷰의 썸네일을 가져옵니다. (도착 좌표 및 복사를 위해)
5273 if let navigationController = ( toView as? UINavigationController ) , let viewController = ( navigationController. topViewController as? BroadcastCollectionViewController ) {
5374 thumbnailView = viewController. selectedThumbnailView
5475 }
5576
77+ /// 4. 썸네일 뷰를 복사하고 절대 프레임을 가져옵니다.
5678 guard let thumbnailView else { return }
5779 let thumbnailViewCopy = copy ( of: thumbnailView)
58- containerView. addSubview ( thumbnailViewCopy)
59- thumbnailView. isHidden = true
60-
6180 let absoluteFrame = thumbnailView. convert ( thumbnailView. frame, to: nil )
62- thumbnailViewCopy. frame = absoluteFrame
63- thumbnailViewCopy. layoutIfNeeded ( )
6481
65- backgroundView. frame = transition == . present ? thumbnailViewCopy. imageView. frame : containerView. frame
82+ /// 애니메이션의 디테일을 위한 설정
83+ backgroundView. frame = transition == . present ? absoluteFrame : containerView. frame
6684 backgroundView. layer. cornerRadius = transition. cornerRadius
6785 thumbnailViewCopy. insertSubview ( backgroundView, aboveSubview: thumbnailViewCopy. shadowView)
6886
87+ /// 5. 기존 썸네일을 숨기고 복사한 썸네일을 containerView에 추가합니다.
88+ containerView. addSubview ( thumbnailViewCopy)
89+ thumbnailView. isHidden = true
90+
91+ /// 6. Present: 애니메이션, 작았다 커지면서 위로 이동합니다.
6992 if transition == . present, let toView {
93+ /// 6-1. 썸네일은 원래 위치에서 시작
94+ thumbnailViewCopy. frame = absoluteFrame
95+ /// 6-2. containerView에 띄워줄 뷰 추가 후 숨김
7096 containerView. addSubview ( toView. view)
7197 toView. view. isHidden = true
98+ /// 6-3. 애니메이션
7299 moveAndConvert ( thumbnailView: thumbnailViewCopy, containerView: containerView, to: absoluteFrame) {
100+ /// 6-4. 띄워줄 뷰 표시
73101 toView. view. isHidden = false
102+ /// 6-5. 썸네일 복사 뷰 제거, 썸네일 뷰 원상복구 (표시)
74103 thumbnailViewCopy. removeFromSuperview ( )
75104 thumbnailView. isHidden = false
105+ /// 6-6. 애니메이션 종료 알림
76106 transitionContext. completeTransition ( true )
77107 }
78108 }
79109
110+ /// 6. Dismiss: 위에서 셀의 위치로 돌아오면서 작아집니다.
80111 if transition == . dismiss, let fromView {
112+ /// 6-1. 시작 뷰 숨김
81113 fromView. view. isHidden = true
82-
114+ /// 6-2. 썸네일 복사 뷰 위치를 시작하는 플레이어 위치부터 시작
83115 thumbnailViewCopy. frame = CGRect ( x: 0 , y: fromView. view. layoutMargins. top, width: containerView. frame. width, height: containerView. frame. width * 0.5625 )
84-
116+ /// 6-3. 애니메이션
85117 moveAndConvert ( thumbnailView: thumbnailViewCopy, containerView: containerView, to: absoluteFrame) {
118+ /// 6-4. 썸네일 복사 뷰 제거, 썸네일 뷰 원상복구 (표시)
119+ thumbnailViewCopy. removeFromSuperview ( )
86120 thumbnailView. isHidden = false
121+ /// 6-6. 애니메이션 종료 알림
87122 transitionContext. completeTransition ( true )
88123 }
89124 }
90125 }
91126}
92127
93- // MARK: - UIViewControllerTransitioningDelegate
94- extension CollectionViewCellTransitioning : UIViewControllerTransitioningDelegate {
95- func animationController( forPresented presented: UIViewController , presenting: UIViewController , source: UIViewController ) -> ( any UIViewControllerAnimatedTransitioning ) ? {
96- transition = . present
97- return self
98- }
99-
100- func animationController( forDismissed dismissed: UIViewController ) -> ( any UIViewControllerAnimatedTransitioning ) ? {
101- transition = . dismiss
102- return self
103- }
104- }
105-
106128// MARK: - Methods
107129extension CollectionViewCellTransitioning {
108- private func addBackgroundViews( to containerView: UIView ) {
109- blurEffectView. frame = containerView. frame
110- blurEffectView. alpha = transition. next. blurAlpha
111- containerView. addSubview ( blurEffectView)
112-
113- dimmingView. frame = containerView. frame
114- dimmingView. alpha = transition. next. dimmingAlpha
115- containerView. addSubview ( dimmingView)
116- }
117-
118130 private func copy( of thumbnailView: ThumbnailView ) -> ThumbnailView {
119131 let thumbnailViewCopy = ThumbnailView ( with: thumbnailView. size)
120132 thumbnailViewCopy. configure ( with: thumbnailView. imageView. image)
121133 return thumbnailViewCopy
122134 }
123135
136+ /// Present 때 사용되는 애니메이션
137+ /// 썸네일을 잠깐 축소했다가 확대시키면서 애니메이션이 진행됩니다.
124138 private func makeShrinkAnimator( of thumbnailView: ThumbnailView ) -> UIViewPropertyAnimator {
125139 UIViewPropertyAnimator ( duration: shrinkDuration, curve: . linear) {
126140 thumbnailView. transform = CGAffineTransform ( scaleX: 0.95 , y: 0.95 )
127- self . dimmingView. alpha = 0.8
128141 }
129142 }
130143
131- private func makeExpandContractAnimator( of thumbnailView: ThumbnailView , in containerView: UIView , to frame: CGRect ) -> UIViewPropertyAnimator {
144+ /// Present, Dismiss 때 사용되는 애니메이션
145+ private func makeScaleAndPositionAnimator( of thumbnailView: ThumbnailView , in containerView: UIView , to frame: CGRect ) -> UIViewPropertyAnimator {
146+ /// 애니메이션 설정
132147 let springTiming = UISpringTimingParameters ( dampingRatio: 0.75 , initialVelocity: CGVector ( dx: 0 , dy: 2 ) )
133148 let animator = UIViewPropertyAnimator ( duration: transitionDuration - shrinkDuration, timingParameters: springTiming)
134149
135150 animator. addAnimations {
136- thumbnailView. transform = . identity
137-
138- if self . transition == . present {
151+ switch self . transition {
152+ case . present:
153+ /// 작아졌던 썸네일 뷰 원상태로
154+ thumbnailView. transform = . identity
155+ /// 썸네일 뷰 Style, Layout 업데이트
139156 thumbnailView. updateStyles ( for: . present)
140157 thumbnailView. updateLayouts ( for: . present)
158+ /// 썸네일 뷰 플레이어뷰 위치로 이동 및 확대
141159 thumbnailView. frame = CGRect ( x: 0 , y: containerView. layoutMargins. top, width: containerView. frame. width, height: containerView. frame. width * 0.5625 )
142- } else {
160+
161+ case . dismiss:
162+ /// 썸네일 뷰 Style, Layout 업데이트
143163 thumbnailView. updateStyles ( for: . dismiss)
144164 thumbnailView. updateLayouts ( for: . dismiss)
165+ /// 인자로 받은 frame위치로 이동 (기존 썸네일 뷰의 절대 프레임)
145166 thumbnailView. frame = frame
146167 }
147168
148169 self . blurEffectView. alpha = self . transition. blurAlpha
149- self . dimmingView. alpha = self . transition. dimmingAlpha
150170
151171 self . backgroundView. layer. cornerRadius = self . transition. next. cornerRadius
152172 self . backgroundView. frame = containerView. frame
@@ -161,22 +181,26 @@ extension CollectionViewCellTransitioning {
161181
162182 private func moveAndConvert( thumbnailView: ThumbnailView , containerView: UIView , to frame: CGRect , completion: @escaping ( ) -> Void ) {
163183 let shrinkAnimator = makeShrinkAnimator ( of: thumbnailView)
164- let expandContractAnimator = makeExpandContractAnimator ( of: thumbnailView, in: containerView, to: frame)
165-
166- expandContractAnimator. addCompletion { _ in
167- completion ( )
168- }
184+ let scaleAndPositionAnimator = makeScaleAndPositionAnimator ( of: thumbnailView, in: containerView, to: frame)
169185
170- if transition == . present {
186+ switch transition {
187+ case . present:
188+ shrinkAnimator. startAnimation ( )
189+
190+ /// 축소 애니메이션 종료 후 확대 애니메이션
171191 shrinkAnimator. addCompletion { _ in
172- thumbnailView. layoutIfNeeded ( )
173- expandContractAnimator. startAnimation ( )
192+ scaleAndPositionAnimator. startAnimation ( )
174193 }
175194
176- shrinkAnimator . startAnimation ( )
177- } else {
195+ case . dismiss :
196+ /// 썸네일 뷰의 위치를 먼저 잡고 축소 애니메이션
178197 thumbnailView. layoutIfNeeded ( )
179- expandContractAnimator. startAnimation ( )
198+ scaleAndPositionAnimator. startAnimation ( )
199+ }
200+
201+ /// 크기, 위치 애니메이션 종료 후
202+ scaleAndPositionAnimator. addCompletion { _ in
203+ completion ( )
180204 }
181205 }
182206}
0 commit comments