App Shop 프로젝트
소개
App Store 클론 프로젝트입니다.
기간
2021.10.02 ~ 10.06
기술
- Swift
- MVP Pattern
라이브러리
- SnapKit
깃허브
https://github.com/sanghee-dev/App-Shop
https://github.com/della-padula/YappUltraHardPractice/tree/main/Sanghee/Practice3
특징
- UICollectionView을 사용하였습니다.
- 커스텀 애니메이터를 구현하였습니다.
화면
메인 화면, 상세 화면
- 메인 화면은 스크롤이 가능하며 피드를 클릭시 상세 화면으로 이동합니다.
- 상세 화면에서는 클릭한 피드의 세부 내용을 확인할 수 있습니다.
애니메이션
- 메인 화면에서 상세 화면으로, 상세 화면에서 메인 화면으로 이동시 애니메이션으로 화면이 전환됩니다.
- 메인 화면에서 피드를 클릭하면 상세 화면으로 이동합니다. 상세 화면에서는 cancel 버튼을 클릭하거나 화면을 아래로 당기는 경우에 메인 화면으로 이동합니다.
- 메인 화면에서의 피드가 상세 화면의 위에 위치됩니다.
- 메인 화면에서의 뷰에 대한 피드의 y값을 이용해 다시 상세 화면에서 메인 화면으로 이동시에도 자연스럽게 애니메이션이 보여집니다.
코드 설명
더욱 자세한 코드는 깃허브에서 확인해주세요.
Model
MainUnit, DetailUnit
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
}
Function
PopAnimator
이 애니메이셔는 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)
})
}
}
}
MainViewController
cPointY
컬렉션뷰에서 셀을 클릭하면 해당 셀의 뷰에 대한 중심점 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
}
}
Presenter
MainPresenter
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(...)
]
}
}
Author And Source
이 문제에 관하여(App Shop 프로젝트), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@leeesangheee/App-Shop-프로젝트저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)