App Store 클론 프로젝트입니다.
2021.10.02 ~ 10.06
https://github.com/sanghee-dev/App-Shop
https://github.com/della-padula/YappUltraHardPractice/tree/main/Sanghee/Practice3
더욱 자세한 코드는 깃허브에서 확인해주세요.
MainUnit은 메인 화면의 단위 모델이다. DetailUnit은 디테일 정보들이다.
struct MainUnit {
let title: String
let subTitle: String
let emoji: String
let backgroundColor: UIColor
let detailUnits: [DetailUnit]
}
struct DetailUnit {
let title: String
let emoji: String
let paragraph: String
}
이 애니메이셔는 duration동안 애니메이션을 실행한다. padding값은 메인 화면에서 좌우 간격값이다. cPointY는 메인 화면에서 선택한 셀의 뷰에 대한 중심점의 Y 위치값이다. 메인 화면에서 디테일 화면으로 전환될 때는 animateTransition함수에서 toView 코드가 실행되며, 디테일 화면에서 메인 화면으로 전환될 때는 fromView 코드가 실행된다. fromView의 코드에서 디테일 화면에서 메인 화면으로 이동할때에는 cPointY값을 활용하여 메인 화면에서의 위치로 다시 이동시킨다.
class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
let duration: TimeInterval = 1.0
let padding: CGFloat = 16
var cPointY: CGFloat = 0
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)
let fromView = transitionContext.view(forKey: .from)
if let toView = toView {
containerView.addSubview(toView)
containerView.bringSubviewToFront(toView)
toView.clipsToBounds = true
toView.layer.cornerRadius = 12
toView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
UIView.animate(withDuration: duration,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.1,
animations: {
toView.transform = .identity
toView.frame = containerView.frame
},
completion: { _ in
transitionContext.completeTransition(true)
})
}
if let fromView = fromView {
containerView.addSubview(fromView)
containerView.bringSubviewToFront(fromView)
fromView.clipsToBounds = true
fromView.layer.cornerRadius = 12
fromView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
let width = containerView.frame.width - self.padding * 2
UIView.animate(withDuration: duration,
delay: 0,
usingSpringWithDamping: 0.6,
initialSpringVelocity: 0.1,
animations: {
fromView.transform = .identity
fromView.frame = CGRect(x: self.padding, y: self.cPointY - (width / 2), width: width, height: width)
},
completion: { _ in
transitionContext.completeTransition(true)
})
}
}
}
컬렉션뷰에서 셀을 클릭하면 해당 셀의 뷰에 대한 중심점 y위치값을 저장한다. getCollectionViewItemCPoint 함수는 컬렉션뷰의 index를 보내면 컬렉션뷰의 상위뷰에 대한 중심점을 반환한다. 이 반환된 중심점의 y값을 cPointY에 저장한다.
그리고 화면이 dismiss되었을 때 이 값을 위의 animator 코드의 cPointY 변수에 값을 저장한다.
class MainViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
private var cPointY: CGFloat = 0
...
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let detailVC = DetailViewController()
detailVC.mainUnit = mainUnits[indexPath.row]
detailVC.modalPresentationStyle = .overFullScreen
detailVC.transitioningDelegate = self
// 선택한 item y값 저장
cPointY = getCollectionViewItemCPoint(indexPath: indexPath).y
self.present(detailVC, animated: true, completion: nil)
}
// 선택한 item y값 얻기
private func getCollectionViewItemCPoint(indexPath: IndexPath) -> CGPoint {
let attributes = collectionView.layoutAttributesForItem(at: indexPath)
let cPoint = collectionView.convert(attributes?.center ?? CGPoint(), to: collectionView.superview)
return cPoint
}
}
extension MainViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return animator
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
animator.cPointY = cPointY
return animator
}
}
MainView에서 구현할 메소드가 포함된 Protocol을 선언한다. 그리고 MainViewPresenter에서 구현할 메소드가 포함된 Protocol을 선언한다. MainPresenter는 현재 날짜와 정보 데이터를 가져오는 역할을 수행한다.
protocol MainView: AnyObject {
func setHeader()
}
protocol MainViewPresenter {
func getMainUnits()
}
class MainPresenter: MainViewPresenter {
var mainUnits: [MainUnit] = []
var currentDateString: String = ""
init() {
getMainUnits()
getDateString()
}
func getDateString() {
...
}
func getMainUnits() {
mainUnits = [
MainUnit(...)
]
}
}