Skip to content

Commit b4cfe0a

Browse files
authored
Merge pull request #296 from boostcampwm-2024/task/hero_animation_update
task 히어로 애니메이션 수정 및 주석 작성
2 parents 0d022c5 + 3394400 commit b4cfe0a

File tree

2 files changed

+79
-53
lines changed

2 files changed

+79
-53
lines changed

Projects/Features/MainFeature/Sources/Utilities/CollectionViewCellTransitioning.swift

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3346
extension 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
107129
extension 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
}

Projects/Features/MainFeature/Sources/ViewControllers/BroadcastCollectionViewController.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ extension BroadcastCollectionViewController: UICollectionViewDelegate {
132132
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
133133
guard let channel = dataSource?.itemIdentifier(for: indexPath) else { return }
134134
guard let viewController = factory?.make(channelID: channel.id, title: channel.name, owner: channel.owner, description: channel.description) else { return }
135+
136+
/// 히어로 애니메이션 전환 적용
135137
viewController.modalPresentationStyle = .overFullScreen
136138
viewController.transitioningDelegate = transitioning
137139
present(viewController, animated: true)

0 commit comments

Comments
 (0)