๐ฌ ๋ค์ด๊ฐ๊ธฐ ์ ์(Prepare)
์๊ฐ๋ณด๋ค ์ฐ๋ฆฌ๋ ํ์ด์ง ๋๋ ํ๋ฉด์ ์์ฃผ ๋ง๋ฉ๋๋ค.
์ฑ์ ์จ๋ณด๋ฉ ํ๋ฉด๋ถํฐ ์์ํด, ์บ๋ฌ์
๋ทฐ(Carousel View), ์๋๋ก์ด๋์์๋ View Pager๋ผ๊ณ ๋ถ๋ฆฌ์ฐ๋ ํญ ๊ฐ ์ ํ๋๋ ํ๋ฉด ๋ฑ ์ ๋ง ๋ง์๋ฐ์! (์ฌ์ค ์์ํ๋ฉด์ ๋ค ์ฒจ๋ถํ๊ณ ์ถ์ง๋ง,,, ๊ท์ฐฎ์๊ฑด ์๋น๋ฐ..ใ
ใ
,,, ๊ผญ ์ฒจ๋ถํ๋๋ก ํ๊ฒ ์ต๋๋ค..)
์์์ ์ธ๊ธํ ํ๋ฉด์ ๊ตฌํํ๋๋ฐ์๋ ๊ทธ ์ข ๋ฅ๋งํผ ๋ค์ํ ๋ฐฉ๋ฒ๋ค์ด ์กด์ฌํฉ๋๋ค.
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด๋ค๊ฑฐ๋ ์คํฌ๋กค๋ทฐ๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ, ๊ทธ๋ฆฌ๊ณ ์ค๋ ์ ๊ฐ ์๊ฐํด๋๋ฆด ์ปฌ๋ ์ ๋ทฐ๋ฅผ ์ด์ฉํ๋ ๋ฐฉ๋ฒ์ด ์๊ฒ ๋ค์.
ํ์ํ ์ํฉ์ ๋ง๊ฒ ์ ์ ํ๊ฒ ์ฌ์ฉํ๋ฉด ๋๊ฒ ์ฃ ?
์ค๋ ์ ๊ฐ ์์๋ก ์ค๋ช ๋๋ฆด ํ๋ฉด์ ์ฑ์ ์ฌ์ฉ์๊ฐ ์ฒ์ ์์ํ๊ฒ ๋๋ฉด ๋ง๋๊ฒ ๋๋ ์จ๋ณด๋ฉ ํ๋ฉด(Onboarding View)์ ๋๋ค.
(๊ทธ๋์ ๋ ์ผ๋ฌ์คํธ ๋๋ฌด ์์์ง ์๋์...? ๋๋ฆฌ๋ฒ ๋์์ด๋ ๋๋ฐ.. ์ผ๋ฌ์คํธ ์ ์๊ถ์ ๋๋ฆฌ๋ฒ ํ์ ์์ต๋๋ค!)
๐ง ์ค์ผ์น(Sketch)
์ ์ฒด์ ์ธ ์ค์ผ์น๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
ํฐ ํ์ ์คํ ๋ฆฌ๋ณด๋ ๋ด์์ CollectionView์ PageControl ๊ทธ๋ฆฌ๊ณ Button์ ๋ฐฐ์นํด์ ์ก์๊ตฌ์.
์ ๋์์ธ ๊ฐ์ ๊ฒฝ์ฐ๋ CollectionViewCell Xib๋ฅผ ๋ง๋ค์ด์ ์งํํ์ต๋๋ค.
์ด๋ฒ์ ๋์์ธ ํ ๊ฒ์ Lottie ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํด์ ์ ๋๋ฉ์ด์ ํ์ผ์ ๋ฃ๊ฒ ๋์ด์
์ ๋๋ฉ์ด์ ์ด ๋ค์ด๊ฐ ์๋ฆฌ๋ฅผ UIView๋ก ์์ญ๋ง ์ก์์ฃผ์์ด์~
๐ง ํด๋ ๊ตฌ์กฐ(Foldering)
๐ง๐ป๐ป ๊ตฌํํ๊ธฐ (Development)
์ค์ ๋ถ
์ผ๋จ ์ปฌ๋ ์ ๋ทฐ๋ฅผ ๋ฐฐ์นํ๊ณ ๋ ๋ค์์ ์ ์ค์ ํด์ผํ๋ ๋ถ๋ถ์ด 2๊ตฐ๋ฐ ์ ๋ ์์ด์.
์ผ์ชฝ์์ ๋ณผ ์ ์๋ฏ์ด Paging Enabled์ ์ฒดํฌโ ๋ฅผ ๊ทธ๋ฆฌ๊ณ ์ค๋ฅธ์ชฝ์์ ๋ณผ ์ ์๋ฏ์ด Estimate Size๋ None์ผ๋ก ์ค์ ํด์ฃผ์ธ์.
๋ฑํ ์ด๋ ค์ด ๋ถ๋ถ์ ์์ด์. ์์๋๋ก ํ ๋ฒ ๊ฐ๋ณผ๊ฒ์.
์ฝ๋๋ถ
1. ๊ธฐ๋ณธ ์ธํ
- ํ๋กํผํฐ ์ธํ
- onboardingDate: ์จ๋ณด๋ฉ ๊ฐ ํ๋ฉด์ ๋ค์ด๊ฐ ๋ฐ์ดํฐ ๋ณ์ (animation, title, description)
- currentPage: ํ์ฌ ํ์ด์ง ์์น๋ฅผ ๋ํ๋ด๋ ๋ณ์
- UI, ์ปฌ๋ ์ ๋ทฐ, ๋๋ฏธ๋ฐ์ดํฐ ์ธํ
// MARK: - Properties
var onboardingData: [OnboardingDataModel] = []
var currentPage: Int = 0 {
didSet {
pageControl.currentPage = currentPage
if currentPage == onboardingData.count - 1 {
nextButton.setTitle("Start", for: .normal)
} else {
nextButton.setTitle("Next", for: .normal)
}
}
}
// MARK: - Custom Functions
extension OnboardingViewController {
private func setUI() {
nextButton.layer.cornerRadius = 10
pageControl.isUserInteractionEnabled = false
}
private func setCollectionView() {
onboardingCollectionView.delegate = self
onboardingCollectionView.dataSource = self
let onboardingNib = UINib(nibName: OnboardingCollectionViewCell.cellId, bundle: nil)
onboardingCollectionView.register(onboardingNib, forCellWithReuseIdentifier: OnboardingCollectionViewCell.cellId)
}
private func setOnboardingData() {
onboardingData.append(contentsOf: [
OnboardingDataModel(lottieName: "onboarding1_img",
title: "์ฌํ์ ๋ ๋๋ณผ๊น์?",
description: "์ฌํ์ ์์ฑํ๊ฑฐ๋ ์ฐธ์ฌ ์ฝ๋๋ฅผ ์
๋ ฅํ์ฌ\n์๋ก์ด ์ฌํ์ ์์ํ์ธ์"),
OnboardingDataModel(lottieName: "onboarding2_img",
title: "์ฐ๋ฆฌ๋ค์ด ์ฌํ์ ๋ง๋ค์ด์",
description: "์ ํ ํ
์คํธ๋ก ์ฌํ ์คํ์ผ์ ํ์
ํ๊ณ ,\n๋ณด๋์์ ์ํตํ๋ฉฐ ์๋ก๋ฅผ ์์๊ฐ ์ ์์ด์"),
OnboardingDataModel(lottieName: "onboarding3_img",
title: "ํจ๊ป ์ฌํ์ ๋ง๋ค์ด์",
description: "์ฌํ ์ ๋ณด๋ฅผ ์
๋ ฅํ๊ณ ์ค์๊ฐ์ผ๋ก ๊ณต์ ํ์ฌ\n์ผ์ ์ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ด์")
])
}
}
2. ์ปฌ๋ ์ ๋ทฐ ์ธํ
- ๊ทธ ๋ค์์ ์ฐ๋ฆฌ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ปฌ๋ ์ ๋ทฐ๋ฅผ ๋ค๋ฃจ๋ ๊ฒ์ฒ๋ผ Delegate, DataSource, FlowLayout์ ์ค์ ์ ํด์.
- ์คํฌ๋กค๋ทฐ Delegate ๊ด๋ จ ์ฝ๋๊ฐ ์๋๋ฐ ๋ค์์ ์ค๋ช ํ ๊ฒ์!
// MARK: - CollectionView Delegate, DataSource
extension OnboardingViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return onboardingData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = onboardingCollectionView.dequeueReusableCell(withReuseIdentifier: OnboardingCollectionViewCell.cellId, for: indexPath) as? OnboardingCollectionViewCell else { return UICollectionViewCell() }
cell.setOnboardingSlides(onboardingData[indexPath.row])
return cell
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let width = scrollView.frame.width
currentPage = Int(scrollView.contentOffset.x / width)
}
}
// MARK: - CollectionView Delegate Flow Layout
extension OnboardingViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
3. ์คํฌ๋กค์ ๋ฐ๋ฅธ ์จ๋ณด๋ฉ ํ์ด์ง ์ค์์ดํ
์์์ ๋ณด์๋ ์ฝ๋์ธ๋ฐ์. ์ ํ ๊ณต์๋ฌธ์๋ฅผ ๋ณด๋ฉด ์ปจํ ์ธ ๋ฅผ ์คํฌ๋กคํ ๋ ์ปจํ ์ธ ์คํ์ ์ ๋ณ๊ฒฝ ๋ด์ฉ์ ๋๋ฆฌ์์๊ฒ ์๋ฆฐ๋ค๋ผ๊ณ ์ค๋ช ํ๊ณ ์์ต๋๋ค. ๊ฐ๋จํ๊ฒ ๋ณด๋ฉด ์ปฌ๋ ์ ๋ทฐ๋ ์คํฌ๋กค๋ทฐ์ ์ผ์ข ์ด์์์? ์์ผ๋ก ์ค์์ดํ ํ ๋๋ง๋ค ์์น๋ฅผ ๊ฐ์ ธ์์ ํ์ด์ง์ ์ฒ๋ฆฌํด์ฃผ๊ธฐ ์ํจ์ ๋๋ค!
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let width = scrollView.frame.width
currentPage = Int(scrollView.contentOffset.x / width)
}
์ด 3๊ฐ์ง์ ์ปจํ ์ธ ๊ฐ ์์ผ๋๊น ์ปจํ ์ธ ์คํ์ ์ ์คํฌ๋กค๋ทฐ์ ์ ์ฒด ๋์ด๋ก ๋๋ ๋ณด๋ฉด
currentPage๋ ์ฝ๋์ ๋ฐ๋ผ์ 0, 1, 2๊ฐ ๋์ฌ ์ ์๊ฒ ๋ค์!
4. Next ๋ฒํผ์ ๋๋ฅผ๋๋ง๋ค ์ปจํ ์ธ ์ ํ
์์์ gif๋ฅผ ๋ณด์๋ฉด ์ฐ์ธก ์๋์ ๋ฒํผ์ ๋๋ฅผ๋๋ง๋ค ํ๋ฉด์ด ์ ํ๋๋ ๊ฒ์ ๋ณด์ค ์ ์์ด์.
์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์๋ฐ์!
๊ฐ์ฅ ๋ง์ง๋ง ์ปจํ ์ธ ์ผ๋๋ ๋์ค์ ํ๋ฉด์ ํ์ ํด์ผํ๋๊น ๋ถ๊ธฐ์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๊ณ ,
๊ทธ ์ ์ํฉ์์๋ currentPage๋ฅผ 1์ฉ ์ฆ๊ฐ์์ผ์ฃผ๊ณ ,
indexPath๋ฅผ ๊ตฌํด์ scrollToItem์ด๋ผ๋ ๋ฉ์๋๋ก ์ปฌ๋ ์ ๋ทฐ๋ฅผ ํ์ด์ง ํด์ฃผ๊ณ ์์ต๋๋ค.
(์ด ๋ถ๋ถ์ ๊ณต์๋ฌธ์์์ ๋ฉ์๋๋ฅผ ์ข ๋ ์ฐพ์๋ณด๋ฉด ์ข์ ๋ฏ ํ๋ค์..!, ๋ค ์ค๋ช ํ๊ธฐ์๋ ๋ด์ฉ์ด ๋๋ฌด ๊ธธใ ...)
// MARK: - Actions
@IBAction private func nextButtonTapped(_ sender: Any) {
if currentPage == onboardingData.count - 1 {
print("go to main")
} else {
currentPage += 1
let indexPath = IndexPath(item: currentPage, section: 0)
onboardingCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
5. ํ๋กํผํฐ ๊ด์ฐฐ์๋ฅผ ํ์ฉํด ๋ฒํผ ์ปจํ ์ธ ๋ณ๊ฒฝ
๋ค ๋ง์ง๋ง ๋ด์ฉ์ ๋๋ค! ์ฐ๋ฆฌ๋ ํ๋กํผํฐ ๊ด์ฐฐ์๋ฅผ ์ด์ฉํด์ ๋ณ๊ฒฝ๋๋ ๋ด์ฉ์ด ์์๋๋ง๋ค ์ด๋ค ์ก์ ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์ ๋ ์ต๊ทผ์ ํด๋น ๋ด์ฉ์ ์์ฃผ ์ฌ์ฉํ๊ณ ์๋๋ฐ์. ๊ธฐํ๊ฐ ๋๋ฉด ๋ด์ฉ์ ๋ฐ๋ก ์ ๋ฆฌํด๋ณด๊ฒ ์ต๋๋ค.
์ฐ์ ์ฝ๋๋ฅผ ๋ณด์๋ฉด์!
var currentPage: Int = 0 {
didSet {
pageControl.currentPage = currentPage
if currentPage == onboardingData.count - 1 {
nextButton.setTitle("Start", for: .normal)
} else {
nextButton.setTitle("Next", for: .normal)
}
}
}
์๋ ๊ฒ! currentPage์ ๊ฐ์ด ๋ฐ๋๋๋ง๋ค pageControl์ ํ์ฌ ํ์ด์ง ์์น๋ฅผ ๋ฐ๊ฟ์ฃผ๊ณ ,
๋ง์ง๋ง ์จ๋ณด๋ฉ ํ์ด์ง์ ๋๋ฌํ๋ฉด ๋ฒํผ์ ํ์ดํ์ Next์์ Start๋ก ๋ฐ๊ฟ์ฃผ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ(End)
์ฐ๋ค๋ณด๋, ์ด๋๋ง ๊ธ์ด ๋์ด ๋ฌ๋ค์..! ์ฌ์ค ๋ด์ฉ์ด ๋๋ฌด ๊ธธ์ด์ง ๊ฑฐ ๊ฐ์ ์์ฝํด์ ๋ด์ฉ์ ์ ๋ฆฌํ๋๋ฐ
ํ์ํ์ ๋ด์ฉ์ด๋ผ๋ฉด ์ฒ์ฒํ ๋ฐ๋ผํด๋ณด์๊ณ , ํน์๋ ๊ถ๊ธํ ์ ์ด๋ ์๋ชป๋ ๋ด์ฉ์ด ์๋ค๋ฉด ๋๊ธ ๋จ๊ฒจ์ฃผ์ธ์ :)
ํ์ด์ ๊ด๋ จํด์๋ ๋ ์ด๋ ค์ด ๋ด์ฉ๋ค๋ ๋ง์ด ์๋ค๊ณ ์๊ฐํ๋๋ฐ์.
๋์ค์ ๋ค๋ฅธ ๋ด์ฉ์ผ๋ก ๋ค์ ์ฐพ์์ค๊ฒ ์ต๋๋ค :) ๊ฐ์ฌํฉ๋๋ค~
์ ์ฒด ์ฝ๋๋ ์ ๊นํ๋ธ ๋งํฌ์ ์์ต๋๋ค. ์๋์ ์ฒจ๋ถํ๋๋ก ํ ๊ฒ์!
https://github.com/Taehyeon-Kim/iOS-Wiki/tree/master/OnboardingSample
'๐ iOS & Swift' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
iOS Issue(1) - Build input file cannot be found (0) | 2021.07.30 |
---|---|
[iOS] ๋๋ ๋ชจ๋ฅด๊ฒ ์ฌ์ฉํ๊ณ ์์๋ Generic (0) | 2021.07.29 |
[iOS] ๋ฐฐ์ด์ ๊ณ ์ฐจ ํจ์ - Higher Order Fuctions (0) | 2021.07.24 |
[Alamofire Mapper] URLRequestConvertible ์ฌ์ฉํด๋ณด๊ธฐ (0) | 2021.07.23 |
[iOS] UIKit์์ Preview ์ฌ์ฉํ๊ธฐ (3) | 2021.05.27 |