From 8f26a18031c6b43598739e582afd7f6a2e290c12 Mon Sep 17 00:00:00 2001 From: MaraMincho <103064352+MaraMincho@users.noreply.github.com> Date: Sat, 18 Nov 2023 00:15:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/SceneDelegate.swift | 5 +- .../View/ExerciseCardCell.swift | 121 ++++++++++++++++++ ...erciseEnvironmentSetupViewController.swift | 98 ++++++++++++++ .../View/ExerciseSelectViewController.swift | 109 ++++++++++++++++ .../Sources/ConstraintsGuideLine.swift | 13 ++ 5 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseCardCell.swift create mode 100644 iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseEnvironmentSetupViewController.swift create mode 100644 iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseSelectViewController.swift create mode 100644 iOS/Projects/Shared/DesignSystem/Sources/ConstraintsGuideLine.swift diff --git a/iOS/Projects/App/Sources/Application/SceneDelegate.swift b/iOS/Projects/App/Sources/Application/SceneDelegate.swift index f925bca4..1b0fd5c8 100644 --- a/iOS/Projects/App/Sources/Application/SceneDelegate.swift +++ b/iOS/Projects/App/Sources/Application/SceneDelegate.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import RecordFeature import UIKit final class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -15,9 +16,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = scene as? UIWindowScene else { return } let navigationController = UINavigationController() window = UIWindow(windowScene: windowScene) - window?.rootViewController = navigationController - let coordinator = AppCoordinator(navigationController: navigationController) - coordinator.start() + window?.rootViewController = ExerciseEnvironmentSetupViewController() window?.makeKeyAndVisible() } } diff --git a/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseCardCell.swift b/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseCardCell.swift new file mode 100644 index 00000000..abcb6885 --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseCardCell.swift @@ -0,0 +1,121 @@ +// +// ExerciseCardCell.swift +// RecordFeature +// +// Created by MaraMincho on 11/16/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - ExerciseCardCell + +class ExerciseCardCell: UICollectionViewCell { + static let identifier = "ExerciseCardCell" + + override init(frame: CGRect) { + super.init(frame: frame) + shadowDecorate() + backgroundColor = .white + setupConstraints() + } + + override var isSelected: Bool { + didSet { + if isSelected { + makeSelectUI() + } else { + makeDeslectUI() + } + } + } + + private let exerciseIconDescriptionLagel: UILabel = { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .title3) + label.textAlignment = .center + label.text = "달리기에용" + label.contentMode = .scaleAspectFit + + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private let exerciseIcon: UIImageView = { + let config = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 120)) + let icon = UIImage(systemName: "figure.run", withConfiguration: config) + let imageView = UIImageView(image: icon) + imageView.contentMode = .scaleAspectFit + imageView.tintColor = DesignSystemColor.primaryText + + imageView.translatesAutoresizingMaskIntoConstraints = false + return imageView + }() + + required init?(coder: NSCoder) { + super.init(coder: coder) + } +} + +private extension ExerciseCardCell { + func setupConstraints() { + contentView.addSubview(exerciseIconDescriptionLagel) + exerciseIconDescriptionLagel.bottomAnchor + .constraint(equalTo: contentView.bottomAnchor, constant: -12).isActive = true + exerciseIconDescriptionLagel.leadingAnchor + .constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true + exerciseIconDescriptionLagel.trailingAnchor + .constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true + + contentView.addSubview(exerciseIcon) + exerciseIcon.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true + exerciseIcon.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true + exerciseIcon.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true + exerciseIcon.bottomAnchor.constraint(equalTo: exerciseIconDescriptionLagel.topAnchor, constant: -15).isActive = true + } + + func shadowDecorate() { + let radius: CGFloat = 10 + contentView.layer.cornerRadius = radius + contentView.layer.borderWidth = 1 + contentView.layer.borderColor = UIColor.clear.cgColor + contentView.layer.masksToBounds = true + + layer.shadowColor = UIColor.black.cgColor + layer.shadowOffset = CGSize(width: 0, height: 1.0) + layer.shadowRadius = 2.0 + layer.shadowOpacity = 0.5 + layer.masksToBounds = false + layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath + layer.cornerRadius = radius + } + + func makeSelectUI() { + exerciseIcon.tintColor = DesignSystemColor.main03 + exerciseIcon.makeShadow() + exerciseIconDescriptionLagel.textColor = DesignSystemColor.main03 + exerciseIconDescriptionLagel.font = .preferredFont(forTextStyle: .title3, with: .traitBold) + } + + func makeDeslectUI() { + exerciseIcon.tintColor = DesignSystemColor.primaryText + exerciseIcon.disableShadow() + exerciseIconDescriptionLagel.textColor = DesignSystemColor.primaryText + exerciseIconDescriptionLagel.font = .preferredFont(forTextStyle: .title3) + } +} + +private extension UIImageView { + func makeShadow() { + layer.shadowColor = UIColor.black.cgColor + layer.shadowOffset = CGSize(width: -2, height: 2) + layer.shadowRadius = 2.0 + layer.shadowOpacity = 0.3 + layer.masksToBounds = false + } + + func disableShadow() { + layer.shadowOpacity = 0 + } +} diff --git a/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseEnvironmentSetupViewController.swift b/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseEnvironmentSetupViewController.swift new file mode 100644 index 00000000..b279acc3 --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseEnvironmentSetupViewController.swift @@ -0,0 +1,98 @@ +// +// ExerciseEnvironmentSetupViewController.swift +// RecordFeature +// +// Created by MaraMincho on 11/15/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - ExerciseEnvironmentSetupViewController + +public final class ExerciseEnvironmentSetupViewController: UIViewController { + public init() { + super.init(nibName: nil, bundle: nil) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + + override public func viewDidLoad() { + super.viewDidLoad() + setup() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + insertTempSource() + } + + lazy var contentNAV: UINavigationController = { + let nav = UINavigationController(rootViewController: exerciseSelectView) + + return nav + }() + + private let exerciseSelectView = ExerciseSelectViewController() + + private let pageControl: GWPageControl = { + let pageControl = GWPageControl(count: Const.countOfPage) + + pageControl.translatesAutoresizingMaskIntoConstraints = false + return pageControl + }() + + var dataSource: UICollectionViewDiffableDataSource! + var exerciseCardCollectionView: UICollectionView! +} + +private extension ExerciseEnvironmentSetupViewController { + func setup() { + view.backgroundColor = .systemBackground + setupConstraints() + exerciseCardCollectionView = exerciseSelectView.exerciseCardCollectionView + + configureDataSource() + } + + func configureDataSource() { + dataSource = .init(collectionView: exerciseCardCollectionView, cellProvider: { collectionView, indexPath, _ in + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ExerciseCardCell.identifier, for: indexPath) + return cell + }) + } + + func insertTempSource() { + var snapshot = dataSource.snapshot() + snapshot.deleteAllItems() + snapshot.appendSections([0]) + snapshot.appendItems([.init(), .init(), .init(), .init(), .init()]) + + dataSource.apply(snapshot) + } + + func setupConstraints() { + let safeArea = view.safeAreaLayoutGuide + + view.addSubview(pageControl) + pageControl.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 10).isActive = true + pageControl.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 23).isActive = true + pageControl.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -23).isActive = true + pageControl.heightAnchor.constraint(equalToConstant: 30).isActive = true + + view.addSubview(contentNAV.view) + contentNAV.view.translatesAutoresizingMaskIntoConstraints = false + contentNAV.view.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true + contentNAV.view.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true + contentNAV.view.topAnchor.constraint(equalTo: pageControl.bottomAnchor).isActive = true + contentNAV.view.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true + } + + enum Const { + static let countOfPage = 2 + } +} diff --git a/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseSelectViewController.swift b/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseSelectViewController.swift new file mode 100644 index 00000000..efaf8798 --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/ExerciseSelectScene/View/ExerciseSelectViewController.swift @@ -0,0 +1,109 @@ +// +// ExerciseSelectViewController.swift +// RecordFeature +// +// Created by MaraMincho on 11/16/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - ExerciseSelectViewController + +final class ExerciseSelectViewController: UIViewController { + override init(nibName _: String?, bundle _: Bundle?) { + super.init(nibName: nil, bundle: nil) + } + + override func viewDidLoad() { + super.viewDidLoad() + setupConstraints() + navigationController?.setNavigationBarHidden(true, animated: false) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + private let exerciseSelectDescriptionLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .title1, with: .traitBold) + label.textAlignment = .left + label.text = "1. 운동을 선택하세요" + + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + lazy var exerciseCardCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionViewLayout()) + collectionView.register(ExerciseCardCell.self, forCellWithReuseIdentifier: ExerciseCardCell.identifier) + + collectionView.translatesAutoresizingMaskIntoConstraints = false + return collectionView + }() + + private let nextButton: UIButton = { + let button = UIButton() + button.configurationUpdateHandler = UIButton.Configuration.main(label: "다음") + + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() +} + +private extension ExerciseSelectViewController { + func makeCollectionViewLayout() -> UICollectionViewLayout { + let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1)) + + let item = NSCollectionLayoutItem(layoutSize: itemSize) + item.contentInsets = .init( + top: Const.CellInset, + leading: Const.CellInset, + bottom: Const.CellInset, + trailing: Const.CellInset + ) + + let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), + heightDimension: .fractionalWidth(0.55)) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + + let section = NSCollectionLayoutSection(group: group) + + return UICollectionViewCompositionalLayout(section: section) + } + + func setupConstraints() { + let safeArea = view.safeAreaLayoutGuide + + view.addSubview(exerciseSelectDescriptionLabel) + exerciseSelectDescriptionLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true + exerciseSelectDescriptionLabel.leadingAnchor + .constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true + exerciseSelectDescriptionLabel.trailingAnchor + .constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true + + view.addSubview(exerciseCardCollectionView) + exerciseCardCollectionView.topAnchor + .constraint(equalTo: exerciseSelectDescriptionLabel.bottomAnchor, constant: 12).isActive = true + exerciseCardCollectionView.leadingAnchor + .constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true + exerciseCardCollectionView.trailingAnchor + .constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true + exerciseCardCollectionView.bottomAnchor + .constraint(equalTo: view.bottomAnchor, constant: -15).isActive = true + + view.addSubview(nextButton) + nextButton.leadingAnchor + .constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true + nextButton.trailingAnchor + .constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true + nextButton.bottomAnchor + .constraint(equalTo: safeArea.bottomAnchor, constant: -28).isActive = true + } + + enum Const { + static let CellInset: CGFloat = 5 + } +} diff --git a/iOS/Projects/Shared/DesignSystem/Sources/ConstraintsGuideLine.swift b/iOS/Projects/Shared/DesignSystem/Sources/ConstraintsGuideLine.swift new file mode 100644 index 00000000..f9e1f63d --- /dev/null +++ b/iOS/Projects/Shared/DesignSystem/Sources/ConstraintsGuideLine.swift @@ -0,0 +1,13 @@ +// +// ConstraintsGuideLine.swift +// DesignSystem +// +// Created by MaraMincho on 11/18/23. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import Foundation + +public enum ConstraintsGuideLine { + public static let value: CGFloat = 23 +}