diff --git a/Techl-Neo/App/SceneDelegate.swift b/Techl-Neo/App/SceneDelegate.swift index 3b41db3..7ce3cc3 100644 --- a/Techl-Neo/App/SceneDelegate.swift +++ b/Techl-Neo/App/SceneDelegate.swift @@ -18,7 +18,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). guard let windowScene = (scene as? UIWindowScene) else { return } - let vc = ViewController() // 여길 바꿔서 첫 화면 이동 + let vc = OnboardingVC() // 여길 바꿔서 첫 화면 이동 let window = UIWindow(windowScene: windowScene) window.rootViewController = vc self.window = window diff --git a/Techl-Neo/Presentations/Base/BaseVC.swift b/Techl-Neo/Presentations/Base/BaseVC.swift index c0f184c..702a942 100644 --- a/Techl-Neo/Presentations/Base/BaseVC.swift +++ b/Techl-Neo/Presentations/Base/BaseVC.swift @@ -14,7 +14,6 @@ class BaseVC: UIViewController { override func viewDidLoad() { super.viewDidLoad() - self.view.backgroundColor = .systemBackground configure() setNavigationBar() diff --git a/Techl-Neo/Presentations/Base/BaseView.swift b/Techl-Neo/Presentations/Base/BaseView.swift index 609c5e1..67232cc 100644 --- a/Techl-Neo/Presentations/Base/BaseView.swift +++ b/Techl-Neo/Presentations/Base/BaseView.swift @@ -16,7 +16,8 @@ class BaseView: UIView { /// - VC의 loadView() 메서드를 오버라이딩후 super 메서드를 호출하지 않고 view = 커스텀뷰()로 바꿔주어야 합니다. override init(frame: CGRect) { super.init(frame: frame) - + backgroundColor = .systemBackground + setupUI() setConstraints() } diff --git a/Techl-Neo/Presentations/Onboarding/OnboardingCollectionViewCell.swift b/Techl-Neo/Presentations/Onboarding/OnboardingCollectionViewCell.swift index 59dc1cd..613bba5 100644 --- a/Techl-Neo/Presentations/Onboarding/OnboardingCollectionViewCell.swift +++ b/Techl-Neo/Presentations/Onboarding/OnboardingCollectionViewCell.swift @@ -13,13 +13,58 @@ class OnboardingCollectionViewCell: UICollectionViewCell { static let identifier = "OnboardingCollectionViewCell" - weak var thumbnailImageView: UIImageView! - weak var titleLabel: UILabel! - weak var descriptionLabel: UILabel! + let thumbnailImageView = UIImageView() + let titleLabel = UILabel().then{ + $0.textAlignment = .center + $0.font = UIFont(name: "Apple SD Gothic Neo Bold", size: 28) + } + let descriptionLabel = UILabel().then{ + $0.numberOfLines = 2 + $0.textAlignment = .center + $0.font = UIFont(name: "Apple SD Gothic Neo SemiBold", size: 22) + } + + override init(frame: CGRect) { + super.init(frame: frame) + + setUI() + setConstraints() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } func configure(_ message: OnboardingMessage) { thumbnailImageView.image = UIImage(named: message.imageName) titleLabel.text = message.title descriptionLabel.text = message.description } + + func setUI(){ + contentView.addSubview(titleLabel) + contentView.addSubview(descriptionLabel) + contentView.addSubview(thumbnailImageView) + } + + func setConstraints(){ + titleLabel.snp.makeConstraints { make in + make.top.greaterThanOrEqualTo(contentView.snp.top).offset(24) + make.trailing.equalTo(contentView.snp.trailing).offset(-24) // x좌표 기준으로 -24가 되어야 하기 때문에 + make.leading.equalTo(contentView.snp.leading).offset(24) + + } + descriptionLabel.snp.makeConstraints { make in + make.top.equalTo(titleLabel.snp.bottom).offset(3) + make.trailing.equalTo(titleLabel.snp.trailing) + make.leading.equalTo(titleLabel.snp.leading) + } + + thumbnailImageView.snp.makeConstraints{ make in + make.trailing.equalTo(titleLabel.snp.trailing) + make.leading.equalTo(titleLabel.snp.leading) + make.top.equalTo(descriptionLabel.snp.bottom).offset(31) + make.height.equalTo(thumbnailImageView.snp.width).multipliedBy(1/1) //aspect ratio 1:1 + } + } } diff --git a/Techl-Neo/Presentations/Onboarding/OnboardingVC.swift b/Techl-Neo/Presentations/Onboarding/OnboardingVC.swift index 061f3a9..69e4be4 100644 --- a/Techl-Neo/Presentations/Onboarding/OnboardingVC.swift +++ b/Techl-Neo/Presentations/Onboarding/OnboardingVC.swift @@ -7,20 +7,148 @@ import UIKit +/// 베이스 뷰 컨트롤러 +/// - loadView() 메서드를 오버라이드한 후 view를 커스텀 뷰로 교체해줄 수 있다. class OnboardingVC: BaseVC { + //MARK: - Properties + + let onboardingView = OnboardingView() + + + var isFinished: Bool = false + + let messages: [OnboardingMessage] = OnboardingMessage.messages + + var currentIndex: Int = 0 { // skipButton isHidden 설정 변수 + didSet { + buttonDesignUI() + onboardingView.skipButton.isHidden = (currentIndex == messages.count - 1 ? true : false) + if currentIndex < messages.count - 1 { + isFinished = false + } + } + } + //MARK: - LifeCycle + override func loadView() { + view = onboardingView + } override func viewDidLoad() { super.viewDidLoad() } + + //MARK: - configure /// Delegate, Register, AddTarget 등 override func configure() { + onboardingView.onboardingCollectionView.delegate = self + onboardingView.onboardingCollectionView.dataSource = self + onboardingView.onboardingCollectionView.register(OnboardingCollectionViewCell.self, forCellWithReuseIdentifier: OnboardingCollectionViewCell.identifier) + onboardingView.skipButton.addTarget(self, action: #selector(skipButtonTapped(_:)), for: .touchUpInside) + onboardingView.nextButton.addTarget(self, action: #selector(nextButtonTapped(_:)), for: .touchUpInside) } + //MARK: - setNavigationBar + /// 네비게이션 바 설정 /// - 타이틀, 아이템 등 네비게이션과 관련된 설정을 합니다. override func setNavigationBar() { + } + + //MARK: - Helper + + @objc func nextButtonTapped(_ sender: UIButton!){ + let index = currentIndex == messages.count - 1 ? currentIndex : (currentIndex + 1) % 3 + currentIndex = index + + onboardingView.onboardingCollectionView.scrollToItem(at: [0, currentIndex], at: .right, animated: true) + + onboardingView.pageControl.currentPage = currentIndex + + if isFinished == true { + + print("skip") + } + + if onboardingView.nextButton.currentTitle == "완료" && currentIndex == messages.count - 1 { + // 완료일 때 화면 이동 + isFinished = true + } + + } + + @objc func skipButtonTapped(_ sender: UIButton!){ + print("skip") + } + + func buttonDesignUI() { + onboardingView.nextButton.titleLabel?.font = UIFont(name: "Apple SD Gothic Neo SemiBold", size: 14) + onboardingView.nextButton.layer.cornerRadius = 8 + + if currentIndex < messages.count - 1 { + onboardingView.skipButton.isHidden = false + onboardingView.nextButton.setTitle("다음", for: .normal) + onboardingView.nextButton.setImage(UIImage(systemName: "chevron.right"), for: .normal) + + } else { // 완료일 때 화면전환 + onboardingView.skipButton.isHidden = true + onboardingView.nextButton.setTitle("완료", for: .normal) + onboardingView.nextButton.setImage(UIImage(), for: .normal) + onboardingView.nextButton.layer.borderWidth = 0 + } } } + +//MARK: - extenstion + +extension OnboardingVC: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return messages.count + } + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: OnboardingCollectionViewCell.identifier, for: indexPath) as? OnboardingCollectionViewCell else { return UICollectionViewCell() } + + let message = messages[indexPath.item] + cell.configure(message) + + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let width: CGFloat = UIScreen.main.bounds.width + let height: CGFloat = collectionView.bounds.height + return CGSize(width: width, height: height) + } + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return .zero + } + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return .zero + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + return .zero + } +} + +extension OnboardingVC: UIScrollViewDelegate { + //현재 페이지가 어디에 위치해 있는지 Indexing 하는 메소드 + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + let index = Int(scrollView.contentOffset.x/scrollView.bounds.width) + onboardingView.pageControl.currentPage = index + currentIndex = index + + } + //carousel 적용 메소드 + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + let scrolledOffsetX = targetContentOffset.pointee.x + scrollView.contentInset.left + let cellWidth = UIScreen.main.bounds.width + let index = round(scrolledOffsetX / cellWidth) + targetContentOffset.pointee = CGPoint(x: index * cellWidth - scrollView.contentInset.left, y: scrollView.contentInset.top) + } +} + diff --git a/Techl-Neo/Presentations/Onboarding/OnboardingView.swift b/Techl-Neo/Presentations/Onboarding/OnboardingView.swift index e9409ac..02b5b22 100644 --- a/Techl-Neo/Presentations/Onboarding/OnboardingView.swift +++ b/Techl-Neo/Presentations/Onboarding/OnboardingView.swift @@ -9,42 +9,101 @@ import UIKit import SnapKit import Then + class OnboardingView: BaseView { + // MARK: - Properties + let identifier = "OnboardingView" + + let onboardingCollectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()).then { //CollectionView + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal //CollectionView scroll 방향을 수평으로 설정합니다. + $0.decelerationRate = .fast //사용자가 손가락을 뗀 후 감속 비율을 결정하는 부동 소수점 값입니다. -> 빠르게 설정 + $0.collectionViewLayout = layout + $0.backgroundColor = .clear //collectionView의 배경을 투명하게 만듭니다. + $0.showsHorizontalScrollIndicator = false //collectionView의 스크롤바가 보이지 않게 설정합니다. + } - var onboardingCollectionView: UICollectionView! - let pageControl: UIPageControl = UIPageControl().then { make in + let pageControl: UIPageControl = UIPageControl().then { + $0.currentPageIndicatorTintColor = UIColor.CustomColor.primaryColor + $0.tintColor = .darkGray + $0.isUserInteractionEnabled = false + $0.numberOfPages = OnboardingMessage.messages.count + $0.currentPage = 0 } + let skipButton: UIButton = UIButton().then { $0.isHidden = false $0.titleLabel?.font = UIFont(name: "Apple SD Gothic Neo SemiBold", size: 14) + $0.setTitle("건너뛰기", for: .normal) + $0.setTitleColor(.darkGray, for: .normal) + $0.backgroundColor = .clear } + let nextButton: UIButton = UIButton().then { + $0.setTitle("다음", for: .normal) + $0.setImage(UIImage(systemName: "chevron.right"), for: .normal) + $0.semanticContentAttribute = .forceRightToLeft //이미지를 텍스트 오른쪽으로 변경 $0.titleLabel?.font = UIFont(name: "Apple SD Gothic Neo SemiBold", size: 14) $0.layer.cornerRadius = 8 - $0.setImage(UIImage(systemName: "chevron.right"), for: .normal) + $0.backgroundColor = .CustomColor.primaryColor + $0.tintColor = .white } - - override init(frame: CGRect) { - super.init(frame: frame) + var stackView = UIStackView().then{ + $0.distribution = .fillProportionally + $0.spacing = 17 + $0.axis = .horizontal } - required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + // MARK: - setupUI /// UI 설정 /// - addSubviews를 하거나 뷰의 배경색을 바꾸는 등 설정을 합니다. override func setupUI() { - + self.addSubview(onboardingCollectionView) + self.addSubview(pageControl) + stackView.insertArrangedSubview(skipButton, at: 0) + stackView.insertArrangedSubview(nextButton, at: 1) + self.addSubview(stackView) + backgroundColor = .CustomColor.backgroundColor } + + // MARK: - setConstraints /// UI 제약조건 설정 /// - Anchor, Snapkit 등을 이용해 UI 컴포넌트의 위치, 크기 등 제약조건을 설정합니다. override func setConstraints() { + pageControl.snp.makeConstraints{ make in + make.top.equalTo(safeAreaLayoutGuide.snp.top).offset(24) + make.centerX.equalTo(safeAreaLayoutGuide.snp.centerX) + } onboardingCollectionView.snp.makeConstraints { make in - make.trailing.leading.equalToSuperview() + make.trailing.leading.equalTo(safeAreaLayoutGuide) + make.top.equalTo(pageControl.snp.bottom).offset(10) + make.height.equalTo(safeAreaLayoutGuide.snp.height).multipliedBy(0.669643) + } + + nextButton.snp.makeConstraints { make in + make.height.equalTo(stackView.snp.height) + } + + skipButton.snp.makeConstraints{ make in + make.width.equalTo(stackView.snp.width).multipliedBy(0.25) + } + + stackView.snp.makeConstraints { make in + make.leading.equalTo(safeAreaLayoutGuide.snp.leading).offset(24) + make.trailing.equalTo(safeAreaLayoutGuide.snp.trailing).offset(-24) + make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom).offset(-30) //바닥 보다 30 올라가야 하기 때문에 offset -30 + make.height.equalTo(50) } } + //MARK: - Helper + } + +