개요
MVVM 디자인 패턴을 이용하여 원피스 현상금 랭킹앱을 디자인.
현상금이 높은 순서대로 보여주어야 한다.
View구성은 CollectionView를 이용하였다.
애니메이션 기능을 추가하여 동적인 느낌을 추가하였다.
실행 동영상
MVVM 디자인 패턴
View = UI + ViewController
Model = 데이터
ViewModel = Model은 ViewModel를 거쳐서 접근 가능하여야 한다. 알고리즘적인 부분 수행
MVVM에 관한 구현 방법은 TableView로 구성한 1차 원피스 현상금 앱과 동일하다. (밑 링크 참고)
cau-meng2.tistory.com/101?category=833321
>> 이번에는 MVVM패턴은 그대로 내비두고 TableView가아닌 CollectionView로 재구성 하였으며,
이미지를 클릭하였을 때, segue에서 동적인 애니메이션까지 추가를 하였다.
ViewController
⭐️ CollectionView를 위한 프로토콜 : 컬렉션 뷰 셸 몇개? , 컬렉션 뷰 어떻게 보여줘? , 컬렉션 뷰 클릭하면 어떡해? 등등 여러가지 질문이 있지만, 기본적으로 '컬렉션 뷰 셸 몇개? , 컬렉션 뷰 어떻게 보여줘?'이 두가지는 꼭 답을 해주어야 함 => UICollectionViewDataSource 상속
우리는
클릭했을 때 할 일( => UITableViewDelegate 상속), cell의 사이즈를 일관적으로 적용해주기위한 일 (=>
UICollectionViewDelegateFlowLayout 상속) 이 있으므로 총 4가지 모두 답하기로 한다
결론적으로 ViewController는 UICollectionViewDataSource,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout 3가지를 상속 받는다.
- UICollectionViewDataSource
//UICollectionViewDataSource 프로토콜
//몇개를 보여줄까요?
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModel.numOfBountyInfoList
}
//UICollectionViewDataSource 프로토콜
//셸은 어떻게 표현할까요? 에 대한 답
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GridCell", for: indexPath) as? GridCell else {
return UICollectionViewCell()
}
let bountyInfo = viewModel.bountyInfo(at: indexPath.item)
cell.update(info: bountyInfo)
return cell
}
indexPath뒤에 원래 테이블 뷰에선 indexPath.row였는데, 컬렉션 뷰에선 indexPath.item으로 접근한다.
그 이외엔 이전 1차 앱과 다른점은 크게 없다.
- UICollectionViewDelegate
//UICollectionViewDelegate 프로토콜
//셸이 클릭되었을때 어쩔까요? 에 대한 답
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//뒷부분에 sender는 showDetail로 넘어가기 전에 넘겨주어야할 것을 prepare 로 넘겨준다.
//showDetail은 segue에 id임.
performSegue(withIdentifier: "showDetail", sender: indexPath.item)
}
이전 1차 앱과 다른점은 크게 없다.
- UICollectionViewDelegateFlowLayout
//UICollectionViewDelegateFlowLayout 프로토콜
//cell사이즈를 계산할꺼 - 다양한 디바이스에서 일관적인 디자인을 보여주기 위해 에 대한 답
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemSpacing: CGFloat = 10
let textAreaHeight: CGFloat = 65
let width: CGFloat = (collectionView.bounds.width - itemSpacing)/2
let height: CGFloat = width * 10/7 + textAreaHeight
return CGSize(width: width, height: height)
}
Collection 뷰에선 셸을 그리디 형태로 배열해야 한다.
UI적으로 잘 배열했다 하더라도 각각의 폰의 폭이 다르기 때문에, 폭이 작은 폰에선 테이블 뷰처럼 보이는 현상이 발생한다.
따라서, 직접 셸사이즈를 계산하기 위한 프로토콜을 추가한다.
+ 주의 : CollectionView(StoryBoard)에서 estimate size를 none 으로 해주어야한다. (직접 계산하겠다는 뜻)
collectionView.bounds.width 는 내장되어 있는 값들이다.
collectionView.bounds 는 경계값들의 정보가 저장되어있고
collectionView.bounds.width 하면 폭의 길이가 나오게 된다.
따라서 전체 폭에서 원하는 공간을 빼준뒤 2등분해주면 한줄에 셸을 2개 띄을수 있는 공간이 나오는 것이다.
Animation
동영상에서 보다싶이, 세그웨이에 애니메이션 기능들이 추가되어 있다.
애니메이션 기능은 크게 2가지로 나뉜다.
- property 기반 애니메이션 (이 프로젝트에 이용된 애니메이션)
- 레이아웃 기반 애니메이션
//UIViewController 상속하면 기본적으로 있는 함수
//이부분은 화면이 바로 보여지기 직전에 수행되는 것들
//수행되고 화면이 뜸.
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
prepareAnimation() //애니메이션 준비단계
}
//화면이 보여지고 실행되는 것
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showAnimation()
}
위의 두가지 메소드는 UIViewController상속 했을시 쓸 수 있는 함수로,
첫번째는, 화면이 보여지기 직전에 수행되는 함수이며
두번째는, 화면이 보여지고 실행되는 것이다.
이것을 이용하여 애니메이션을 구현 할 수 있는데 , 애니메이션에 관한 함수를 따로 prepareAnimation,showAnimation를 만들어 사용하였다.
prepareAnimation
private func prepareAnimation(){
// nameLabelCenterX.constant = view.bounds.width
// bountyLabelCenterX.constant = view.bounds.width
//3배로 키우고 180도 회전되어있음.
nameLabel.transform = CGAffineTransform(translationX: view.bounds.width, y: 0).scaledBy(x: 3, y: 3).rotated(by: 180)
bountyLabel.transform = CGAffineTransform(translationX: view.bounds.width, y: 0).scaledBy(x: 3, y: 3).rotated(by: 180)
//투명도
nameLabel.alpha = 0
bountyLabel.alpha = 0
}
CGAffineTransform(translationX: view.bounds.width, y: 0) 는 x, y 위치 설정을 말해준다.
.scaledBy는 x, y으로 몇 배를 늘릴 것인지 말해준다.
.rotated 몇 도 회전할 것인지를 말해준다.
CGAffineTransform struct는 이외에도 여러가지 기능을 제공하는데 자세한건 , 밑에 링크에서 보면된다. (한국어 번역은 아직 없더라 🥲)
developer.apple.com/documentation/coregraphics/cgaffinetransform
showAnimation
private func showAnimation(){
//레이아웃이 바뀌면 레이아웃팅을 다시 해야됨 그과정에 애니메이션으로 레이아웃팅한 것
//레이아웃에 관한 애니메이션
//이름이랑 현상금 따로 애니메이션 걸어줌.
UIView.animate(withDuration: 1, delay: 0, usingSpringWithDamping: 0.6,initialSpringVelocity: 2, options: .allowUserInteraction,
animations: {
//변하기 전의 모습으로는 identity로 접근이 가능함.
self.nameLabel.transform = CGAffineTransform.identity
self.nameLabel.alpha = 1
}, completion: nil)
UIView.animate(withDuration: 1, delay: 0.2, usingSpringWithDamping: 0.6,initialSpringVelocity: 2, options: .allowUserInteraction,
animations: {
//변하기 전의 모습으로는 identity로 접근이 가능함.
self.bountyLabel.transform = CGAffineTransform.identity
self.bountyLabel.alpha = 1
}, completion: nil)
UIView.transition(with: imgView, duration: 0.3, options: .transitionFlipFromLeft, animations: nil, completion: nil)
}
CGAffineTransform.identity는 변하기 전의 모습으로 접근 가능하게 해준다.
>> UIView.animate의 인자 설명 (이동관련)
withDuration : 몇초 동안?
delay : 몇초 뒤에 애니메이션이 나올거니?
usingSpringWithDamping: 마지막 탄성력 얼마나 작용?
initialSpringVelocity: 처음 속도?
animations: 어떤상태로 변하게 할꺼니? (함수형태)
completion: 끝나고 어떤 일이 벌어지니? (함수형태)
>>UIView.transition의 인자 설명 (제자리전환)
with : 어떤 뷰?
duration : 몇초동안 일어나길바라니?
options : 어떤 옵션을 줄거니? (.transitionFlipFromLeft는 왼쪽으로 카드 뒤집기)
'🌙 iOS 스터디 > Xcode' 카테고리의 다른 글
[xcode] Inpo.plist 파일 - 애플리케이션 정보 (0) | 2021.01.24 |
---|---|
[xcode] HeaderView있는 CollectionView 애플 뮤직 앱 (0) | 2021.01.20 |
[Xcode] MVVM을 이용하여 TableView로 구성한 원피스 현상금 앱 (0) | 2021.01.15 |
[Xcode] MVC 모델로 만든 계산기 앱 (Standford 대학 IOS강의) (0) | 2021.01.01 |
[Xcode] 손전등 앱 (Flash App) (0) | 2020.12.31 |