From 2d386458ad783b4ad0c1c18985604bb47ad66611 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Thu, 16 Nov 2023 14:08:08 +0900 Subject: [PATCH 01/10] =?UTF-8?q?fix:=20tuist=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resources와 Tests 폴더 경로 문제 --- iOS/Projects/Features/Record/Tests/Test.swift | 8 ++++++++ iOS/Projects/Shared/TNCocoaCombine/Project.swift | 1 + 2 files changed, 9 insertions(+) create mode 100644 iOS/Projects/Features/Record/Tests/Test.swift diff --git a/iOS/Projects/Features/Record/Tests/Test.swift b/iOS/Projects/Features/Record/Tests/Test.swift new file mode 100644 index 00000000..436ae42a --- /dev/null +++ b/iOS/Projects/Features/Record/Tests/Test.swift @@ -0,0 +1,8 @@ +// +// Test.swift +// ProjectDescriptionHelpers +// +// Created by 안종표 on 2023/11/16. +// + +import Foundation diff --git a/iOS/Projects/Shared/TNCocoaCombine/Project.swift b/iOS/Projects/Shared/TNCocoaCombine/Project.swift index 40e52f03..6b9dc297 100644 --- a/iOS/Projects/Shared/TNCocoaCombine/Project.swift +++ b/iOS/Projects/Shared/TNCocoaCombine/Project.swift @@ -4,5 +4,6 @@ import ProjectDescriptionHelpers let project = Project.makeModule( name: "TNCocoaCombine", product: .framework, + resources: nil, isTestable: false ) From e6afaa04548ffb992dd6a0736725b7286d429997 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Fri, 17 Nov 2023 18:46:39 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EC=9A=B4=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=EB=A1=9D=EC=9D=B4=20=EC=9E=88=EB=8A=94=20=EA=B2=BD=EC=9A=B0?= =?UTF-8?q?=EC=9D=98=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...WorkoutInformationCollectionViewCell.swift | 92 ++++++++++ .../RecordScene/RecordViewController.swift | 165 ++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/RecordViewController.swift diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift new file mode 100644 index 00000000..17a132ea --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift @@ -0,0 +1,92 @@ +// +// WorkoutInformationCollectionViewCell.swift +// RecordFeature +// +// Created by 안종표 on 2023/11/16. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - WorkoutInformationCollectionViewCell + +final class WorkoutInformationCollectionViewCell: UICollectionViewCell { + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.spacing = 6 + stackView.alignment = .center + stackView.distribution = .fillEqually + stackView.axis = .vertical + return stackView + }() + + private let sportLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body) + label.text = "운동: 사이클" + return label + }() + + private let timeLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body) + label.text = "시간: 07:00~08:00" + return label + }() + + private let distanceLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body) + label.text = "거리: 12.43km" + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("xib파일을 사용하지않습니다.") + } + + func configure(workoutInformation: WorkoutInformation) { + configureUI() + sportLabel.text = "\(workoutInformation.sport)" + timeLabel.text = "\(workoutInformation.time)" + distanceLabel.text = "\(workoutInformation.distance)" + } +} + +private extension WorkoutInformationCollectionViewCell { + func configureUI() { + contentView.backgroundColor = DesignSystemColor.gray01 + + [sportLabel, timeLabel, distanceLabel].forEach { + stackView.addArrangedSubview($0) + } + contentView.addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), + stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: Metrics.leadingTrailingpadding), + stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -Metrics.leadingTrailingpadding), + ]) + } +} + +// MARK: - WorkoutInformation + +struct WorkoutInformation { + let sport: String + let time: String + let distance: String +} + +// MARK: - Metrics + +private enum Metrics { + static let leadingTrailingpadding: CGFloat = 10 + static let topBottomPadding: CGFloat = 47 +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/RecordViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/RecordViewController.swift new file mode 100644 index 00000000..71d7b4b7 --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/RecordViewController.swift @@ -0,0 +1,165 @@ +// +// RecordViewController.swift +// ProjectDescriptionHelpers +// +// Created by 안종표 on 2023/11/16. +// + +import DesignSystem +import UIKit + +// MARK: - RecordViewController + +public final class RecordViewController: UIViewController { + private var workoutInforamtionDataSource: WorkoutInformationDataSource? + + private let todayLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = "오늘\n ??월 ??일 ?요일" + label.numberOfLines = 0 + label.font = .preferredFont(forTextStyle: .title1, with: .traitBold) + return label + }() + + private lazy var workoutInformationCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .horizontal + flowLayout.minimumLineSpacing = 6 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.showsHorizontalScrollIndicator = false + return collectionView + }() + + private let recordButton: UIButton = { + var configuration = UIButton.Configuration.mainEnabled(title: "기록하러가기") + configuration.font = .preferredFont(forTextStyle: .headline, with: .traitBold) + let button = UIButton(configuration: configuration) + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + override public func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + configureUI() + configureDataSource() + // ViewModel 생성 전, 앱이 돌아가는지 확인하기 위한 간단한 예제 + let items: [WorkoutInformationItem] = [ + .init(sport: "수영", time: "08:00~09:00", distance: "12.12Km"), + .init(sport: "수영", time: "08:00~09:00", distance: "12.12Km"), + .init(sport: "수영", time: "08:00~09:00", distance: "12.12Km"), + .init(sport: "수영", time: "08:00~09:00", distance: "12.12Km"), + .init(sport: "수영", time: "08:00~09:00", distance: "12.12Km"), + ] + configureSnapShot(items: items) + } +} + +// MARK: UI + +private extension RecordViewController { + func configureUI() { + let safeArea = view.safeAreaLayoutGuide + + view.addSubview(todayLabel) + NSLayoutConstraint.activate([ + todayLabel.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: Metrics.topInterval), + todayLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), + todayLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + ]) + + view.addSubview(workoutInformationCollectionView) + NSLayoutConstraint.activate([ + workoutInformationCollectionView.topAnchor.constraint(equalTo: todayLabel.bottomAnchor, constant: Metrics.componentInterval), + workoutInformationCollectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), + workoutInformationCollectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + ]) + + view.addSubview(recordButton) + NSLayoutConstraint.activate([ + recordButton.topAnchor.constraint(equalTo: workoutInformationCollectionView.bottomAnchor, constant: Metrics.componentInterval), + recordButton.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), + recordButton.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + recordButton.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -Metrics.bottomInterval), + ]) + } + + func configureDataSource() { + let cellRegistration = WorkoutInformationCellRegistration { cell, _, item in + cell.configure(workoutInformation: + WorkoutInformation( + sport: item.sport, + time: item.time, + distance: item.distance + ) + ) + } + + workoutInforamtionDataSource = WorkoutInformationDataSource( + collectionView: workoutInformationCollectionView, + cellProvider: { collectionView, indexPath, itemIdentifier in + + collectionView.dequeueConfiguredReusableCell( + using: cellRegistration, + for: indexPath, + item: itemIdentifier + ) + } + ) + } + + func configureSnapShot(items: [WorkoutInformationItem]) { + var snapShot = WorkoutInformationSnapShot() + snapShot.appendSections([.workoutList]) + snapShot.appendItems(items) + workoutInforamtionDataSource?.apply(snapShot) + } +} + +// MARK: UICollectionViewDelegateFlowLayout + +extension RecordViewController: UICollectionViewDelegateFlowLayout { + public func collectionView( + _: UICollectionView, + layout _: UICollectionViewLayout, + sizeForItemAt _: IndexPath + ) -> CGSize { + return CGSize(width: view.frame.width / Metrics.itemWidthRatio, height: workoutInformationCollectionView.frame.height / Metrics.itemHeightRatio) + } +} + +// MARK: - Metrics + +private enum Metrics { + static let componentInterval: CGFloat = 24 + static let topInterval: CGFloat = 92 + static let bottomInterval: CGFloat = 215 + static let itemWidthRatio: CGFloat = 2.45 + static let itemHeightRatio: CGFloat = 1.5 +} + +// MARK: RecordViewController DiffableDataSource + +private extension RecordViewController { + typealias WorkoutInformationCellRegistration = UICollectionView.CellRegistration + typealias WorkoutInformationDataSource = UICollectionViewDiffableDataSource + typealias WorkoutInformationSnapShot = NSDiffableDataSourceSnapshot +} + +// MARK: - Section + +private enum Section { + case workoutList +} + +// MARK: - WorkoutInformationItem + +private struct WorkoutInformationItem: Identifiable, Hashable { + let id = UUID() + let sport: String + let time: String + let distance: String +} From 7736dbd79d6ad55ccb7701b6b854a92b359a139d Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Fri, 17 Nov 2023 21:22:29 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EA=B8=B0=EB=A1=9D=EC=9D=B4=20?= =?UTF-8?q?=EC=97=86=EC=9D=84=20=EB=95=8C=20=EB=82=98=ED=83=80=EB=82=B4?= =?UTF-8?q?=EB=8A=94=20View=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...WorkoutInformationCollectionViewCell.swift | 16 +++--- .../RecordScene/View/NoRecordsView.swift | 54 +++++++++++++++++++ 2 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/View/NoRecordsView.swift diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift index 17a132ea..ae1d70da 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/WorkoutInformationCollectionViewCell.swift @@ -49,7 +49,7 @@ final class WorkoutInformationCollectionViewCell: UICollectionViewCell { @available(*, unavailable) required init?(coder _: NSCoder) { - fatalError("xib파일을 사용하지않습니다.") + fatalError("No Xib") } func configure(workoutInformation: WorkoutInformation) { @@ -76,6 +76,13 @@ private extension WorkoutInformationCollectionViewCell { } } +// MARK: - Metrics + +private enum Metrics { + static let leadingTrailingpadding: CGFloat = 10 + static let topBottomPadding: CGFloat = 47 +} + // MARK: - WorkoutInformation struct WorkoutInformation { @@ -83,10 +90,3 @@ struct WorkoutInformation { let time: String let distance: String } - -// MARK: - Metrics - -private enum Metrics { - static let leadingTrailingpadding: CGFloat = 10 - static let topBottomPadding: CGFloat = 47 -} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/View/NoRecordsView.swift b/iOS/Projects/Features/Record/Sources/RecordScene/View/NoRecordsView.swift new file mode 100644 index 00000000..4eb77c5a --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/View/NoRecordsView.swift @@ -0,0 +1,54 @@ +// +// NoRecordsView.swift +// RecordFeature +// +// Created by 안종표 on 2023/11/17. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - NoRecordsView + +final class NoRecordsView: UIView { + private let noRecordLabel: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.text = "아직 기록이 없습니다\n기록하러 가볼까요?" + label.numberOfLines = 0 + label.font = .preferredFont(forTextStyle: .title3) + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + configureUI() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("No Xib") + } +} + +private extension NoRecordsView { + func configureUI() { + backgroundColor = DesignSystemColor.gray01 + + addSubview(noRecordLabel) + NSLayoutConstraint.activate([ + noRecordLabel.topAnchor.constraint(equalTo: topAnchor, constant: Metrics.topBottomPadding), + noRecordLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Metrics.leadingTrailing), + noRecordLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -Metrics.leadingTrailing), + noRecordLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Metrics.topBottomPadding), + ]) + } +} + +// MARK: - Metrics + +private enum Metrics { + static let topBottomPadding: CGFloat = 35 + static let leadingTrailing: CGFloat = 60 +} From e6a05ff51f25fc6dc2e2b744076cf90ee2ddf810 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 18 Nov 2023 14:14:46 +0900 Subject: [PATCH 04/10] =?UTF-8?q?refactor:=20RecordViewController=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordListViewController.swift} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename iOS/Projects/Features/Record/Sources/RecordScene/{RecordViewController.swift => ViewController/RecordListViewController.swift} (95%) diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/RecordViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift similarity index 95% rename from iOS/Projects/Features/Record/Sources/RecordScene/RecordViewController.swift rename to iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift index 71d7b4b7..115f72de 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/RecordViewController.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift @@ -1,5 +1,5 @@ // -// RecordViewController.swift +// RecordListViewController.swift // ProjectDescriptionHelpers // // Created by 안종표 on 2023/11/16. @@ -8,9 +8,9 @@ import DesignSystem import UIKit -// MARK: - RecordViewController +// MARK: - RecordListViewController -public final class RecordViewController: UIViewController { +public final class RecordListViewController: UIViewController { private var workoutInforamtionDataSource: WorkoutInformationDataSource? private let todayLabel: UILabel = { @@ -60,7 +60,7 @@ public final class RecordViewController: UIViewController { // MARK: UI -private extension RecordViewController { +private extension RecordListViewController { func configureUI() { let safeArea = view.safeAreaLayoutGuide @@ -121,7 +121,7 @@ private extension RecordViewController { // MARK: UICollectionViewDelegateFlowLayout -extension RecordViewController: UICollectionViewDelegateFlowLayout { +extension RecordListViewController: UICollectionViewDelegateFlowLayout { public func collectionView( _: UICollectionView, layout _: UICollectionViewLayout, @@ -143,7 +143,7 @@ private enum Metrics { // MARK: RecordViewController DiffableDataSource -private extension RecordViewController { +private extension RecordListViewController { typealias WorkoutInformationCellRegistration = UICollectionView.CellRegistration typealias WorkoutInformationDataSource = UICollectionViewDiffableDataSource typealias WorkoutInformationSnapShot = NSDiffableDataSourceSnapshot From 2f4a69851bd639ec8a5f296cab3083dca5bcaad4 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 18 Nov 2023 14:27:20 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20RecordContainerViewController=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecordContainerViewController.swift | 41 +++++++++++++++++++ .../RecordListViewController.swift | 4 +- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift new file mode 100644 index 00000000..024c6f8f --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift @@ -0,0 +1,41 @@ +// +// RecordContainerViewController.swift +// RecordFeature +// +// Created by 안종표 on 2023/11/18. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import UIKit + +// MARK: - RecordContainerViewController + +public final class RecordContainerViewController: UIViewController { + override public func viewDidLoad() { + super.viewDidLoad() + configureUI() + } +} + +private extension RecordContainerViewController { + func configureUI() { + let safeArea = view.safeAreaLayoutGuide + + let recordListViewController = RecordListViewController() + guard let recordView = recordListViewController.view else { return } + recordView.translatesAutoresizingMaskIntoConstraints = false + add(child: recordListViewController) + NSLayoutConstraint.activate([ + recordView.topAnchor.constraint(equalTo: safeArea.topAnchor), + recordView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor), + recordView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor), + recordView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor), + ]) + } + + func add(child viewController: UIViewController) { + addChild(viewController) + view.addSubview(viewController.view) + viewController.didMove(toParent: viewController) + } +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift index 115f72de..80811dd6 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift @@ -10,7 +10,7 @@ import UIKit // MARK: - RecordListViewController -public final class RecordListViewController: UIViewController { +final class RecordListViewController: UIViewController { private var workoutInforamtionDataSource: WorkoutInformationDataSource? private let todayLabel: UILabel = { @@ -41,7 +41,7 @@ public final class RecordListViewController: UIViewController { return button }() - override public func viewDidLoad() { + override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white configureUI() From 32da79e5fdd5a24c50bb3586312edf1d26fa4fc4 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 18 Nov 2023 20:09:59 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20custom=20CallendarCollectionView?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Projects/App/Sources/SceneDelegate.swift | 3 +- .../CalendarCollectionViewCell.swift | 77 ++++++++++ .../RecordCalendarViewController.swift | 142 ++++++++++++++++++ .../RecordContainerViewController.swift | 32 +++- .../RecordListViewController.swift | 20 +-- 5 files changed, 255 insertions(+), 19 deletions(-) create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift diff --git a/iOS/Projects/App/Sources/SceneDelegate.swift b/iOS/Projects/App/Sources/SceneDelegate.swift index 983518c7..0a22755a 100644 --- a/iOS/Projects/App/Sources/SceneDelegate.swift +++ b/iOS/Projects/App/Sources/SceneDelegate.swift @@ -6,6 +6,7 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // +import RecordFeature import UIKit final class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -14,7 +15,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } window = UIWindow(windowScene: windowScene) - window?.rootViewController = ViewController() + window?.rootViewController = RecordContainerViewController() window?.makeKeyAndVisible() } } diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift new file mode 100644 index 00000000..7b8202bb --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift @@ -0,0 +1,77 @@ +// +// CalendarCollectionViewCell.swift +// RecordFeature +// +// Created by 안종표 on 2023/11/18. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - CalendarCollectionViewCell + +final class CalendarCollectionViewCell: UICollectionViewCell { + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.spacing = 1 + stackView.alignment = .center + stackView.distribution = .fillEqually + stackView.axis = .vertical + return stackView + }() + + let dayOfWeekLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .footnote) + label.text = "월" + label.textColor = DesignSystemColor.gray03 + return label + }() + + let dateLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body, with: .traitBold) + label.text = "16" + label.textColor = DesignSystemColor.gray03 + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("No Xib") + } + + func configure(calendarInformation: CalendarInforamtion) { + configureUI() + dayOfWeekLabel.text = calendarInformation.dayOfWeek + dateLabel.text = calendarInformation.date + } +} + +private extension CalendarCollectionViewCell { + func configureUI() { + [dayOfWeekLabel, dateLabel].forEach { + stackView.addArrangedSubview($0) + } + contentView.addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: contentView.topAnchor), + stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + } +} + +// MARK: - CalendarInforamtion + +struct CalendarInforamtion { + let dayOfWeek: String + let date: String +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift new file mode 100644 index 00000000..9e41f6ac --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift @@ -0,0 +1,142 @@ +// +// RecordCalendarViewController.swift +// RecordFeature +// +// Created by 안종표 on 2023/11/18. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - RecordCalendarViewController + +final class RecordCalendarViewController: UIViewController { + private var dataSource: RecordCalendarDiffableDataSource? + private lazy var calendarCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .horizontal + flowLayout.minimumLineSpacing = 0 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.showsHorizontalScrollIndicator = false + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + configureUI() + configureDataSource() + let items = [ + CalendarInforamtionItem(dayOfWeek: "화", date: "17"), + CalendarInforamtionItem(dayOfWeek: "화", date: "18"), + CalendarInforamtionItem(dayOfWeek: "화", date: "19"), + CalendarInforamtionItem(dayOfWeek: "화", date: "20"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + ] + configureSnapshot(items: items) + } +} + +private extension RecordCalendarViewController { + func configureUI() { + view.addSubview(calendarCollectionView) + NSLayoutConstraint.activate([ + calendarCollectionView.topAnchor.constraint(equalTo: view.topAnchor), + calendarCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + calendarCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + calendarCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + func configureDataSource() { + let cellRegistration = RecordCalendarCellRegistration { cell, _, itemIdentifier in + cell.configure( + calendarInformation: CalendarInforamtion( + dayOfWeek: itemIdentifier.dayOfWeek, + date: itemIdentifier.date + ) + ) + } + + dataSource = RecordCalendarDiffableDataSource( + collectionView: calendarCollectionView, + cellProvider: { collectionView, indexPath, itemIdentifier in + collectionView.dequeueConfiguredReusableCell( + using: cellRegistration, + for: indexPath, + item: itemIdentifier + ) + } + ) + } + + func configureSnapshot(items: [CalendarInforamtionItem]) { + var snapShot = RecordCalendarSnapShot() + snapShot.appendSections([.calendar]) + snapShot.appendItems(items) + dataSource?.apply(snapShot) + } +} + +// MARK: UICollectionViewDelegateFlowLayout + +extension RecordCalendarViewController: UICollectionViewDelegateFlowLayout { + func collectionView( + _ collectionView: UICollectionView, + layout _: UICollectionViewLayout, + sizeForItemAt _: IndexPath + ) -> CGSize { + return CGSize(width: view.frame.width / 7.5, height: collectionView.frame.height) + } + + func collectionView( + _ collectionView: UICollectionView, + didSelectItemAt indexPath: IndexPath + ) { + guard let cell = collectionView.cellForItem(at: indexPath) as? CalendarCollectionViewCell else { + return + } + cell.dayOfWeekLabel.textColor = DesignSystemColor.primaryText + cell.dateLabel.textColor = DesignSystemColor.primaryText + } + + func collectionView( + _ collectionView: UICollectionView, + didDeselectItemAt indexPath: IndexPath + ) { + guard let cell = collectionView.cellForItem(at: indexPath) as? CalendarCollectionViewCell else { + return + } + cell.dayOfWeekLabel.textColor = DesignSystemColor.gray03 + cell.dateLabel.textColor = DesignSystemColor.gray03 + } +} + +private extension RecordCalendarViewController { + typealias RecordCalendarCellRegistration = UICollectionView.CellRegistration + typealias RecordCalendarDiffableDataSource = UICollectionViewDiffableDataSource + typealias RecordCalendarSnapShot = NSDiffableDataSourceSnapshot +} + +// MARK: - Section + +private enum Section { + case calendar +} + +// MARK: - CalendarInforamtionItem + +private struct CalendarInforamtionItem: Identifiable, Hashable { + let id = UUID() + let dayOfWeek: String + let date: String +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift index 024c6f8f..8a72f8e5 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift @@ -19,17 +19,29 @@ public final class RecordContainerViewController: UIViewController { private extension RecordContainerViewController { func configureUI() { + view.backgroundColor = .systemBackground let safeArea = view.safeAreaLayoutGuide + let recordCalendarViewController = RecordCalendarViewController() + guard let calendarView = recordCalendarViewController.view else { return } + calendarView.translatesAutoresizingMaskIntoConstraints = false + add(child: recordCalendarViewController) + NSLayoutConstraint.activate([ + calendarView.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: Metrics.componentInterval), + calendarView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), + calendarView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + calendarView.heightAnchor.constraint(equalToConstant: Metrics.calendarHeight), + ]) + let recordListViewController = RecordListViewController() - guard let recordView = recordListViewController.view else { return } - recordView.translatesAutoresizingMaskIntoConstraints = false + guard let listView = recordListViewController.view else { return } + listView.translatesAutoresizingMaskIntoConstraints = false add(child: recordListViewController) NSLayoutConstraint.activate([ - recordView.topAnchor.constraint(equalTo: safeArea.topAnchor), - recordView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor), - recordView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor), - recordView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor), + listView.topAnchor.constraint(equalTo: calendarView.bottomAnchor, constant: Metrics.componentInterval), + listView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), + listView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + listView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -Metrics.bottomInterval), ]) } @@ -39,3 +51,11 @@ private extension RecordContainerViewController { viewController.didMove(toParent: viewController) } } + +// MARK: - Metrics + +private enum Metrics { + static let componentInterval: CGFloat = 24 + static let bottomInterval: CGFloat = 215 + static let calendarHeight: CGFloat = 51 +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift index 80811dd6..aa8f63c5 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift @@ -62,28 +62,26 @@ final class RecordListViewController: UIViewController { private extension RecordListViewController { func configureUI() { - let safeArea = view.safeAreaLayoutGuide - view.addSubview(todayLabel) NSLayoutConstraint.activate([ - todayLabel.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: Metrics.topInterval), - todayLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), - todayLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + todayLabel.topAnchor.constraint(equalTo: view.topAnchor), + todayLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor), + todayLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) view.addSubview(workoutInformationCollectionView) NSLayoutConstraint.activate([ workoutInformationCollectionView.topAnchor.constraint(equalTo: todayLabel.bottomAnchor, constant: Metrics.componentInterval), - workoutInformationCollectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), - workoutInformationCollectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + workoutInformationCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + workoutInformationCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) view.addSubview(recordButton) NSLayoutConstraint.activate([ recordButton.topAnchor.constraint(equalTo: workoutInformationCollectionView.bottomAnchor, constant: Metrics.componentInterval), - recordButton.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), - recordButton.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), - recordButton.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -Metrics.bottomInterval), + recordButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + recordButton.trailingAnchor.constraint(equalTo: view.trailingAnchor), + recordButton.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } @@ -135,8 +133,6 @@ extension RecordListViewController: UICollectionViewDelegateFlowLayout { private enum Metrics { static let componentInterval: CGFloat = 24 - static let topInterval: CGFloat = 92 - static let bottomInterval: CGFloat = 215 static let itemWidthRatio: CGFloat = 2.45 static let itemHeightRatio: CGFloat = 1.5 } From 5580910be5770c96e1e444c16769d6e7024604ef Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 18 Nov 2023 20:09:59 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20custom=20CallendarCollectionView?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CalendarCollectionViewCell.swift | 77 ++++++++++ .../RecordCalendarViewController.swift | 142 ++++++++++++++++++ .../RecordContainerViewController.swift | 32 +++- .../RecordListViewController.swift | 20 +-- 4 files changed, 253 insertions(+), 18 deletions(-) create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift create mode 100644 iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift new file mode 100644 index 00000000..7b8202bb --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/CollectionViewCell/CalendarCollectionViewCell.swift @@ -0,0 +1,77 @@ +// +// CalendarCollectionViewCell.swift +// RecordFeature +// +// Created by 안종표 on 2023/11/18. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - CalendarCollectionViewCell + +final class CalendarCollectionViewCell: UICollectionViewCell { + private let stackView: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.spacing = 1 + stackView.alignment = .center + stackView.distribution = .fillEqually + stackView.axis = .vertical + return stackView + }() + + let dayOfWeekLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .footnote) + label.text = "월" + label.textColor = DesignSystemColor.gray03 + return label + }() + + let dateLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body, with: .traitBold) + label.text = "16" + label.textColor = DesignSystemColor.gray03 + return label + }() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("No Xib") + } + + func configure(calendarInformation: CalendarInforamtion) { + configureUI() + dayOfWeekLabel.text = calendarInformation.dayOfWeek + dateLabel.text = calendarInformation.date + } +} + +private extension CalendarCollectionViewCell { + func configureUI() { + [dayOfWeekLabel, dateLabel].forEach { + stackView.addArrangedSubview($0) + } + contentView.addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: contentView.topAnchor), + stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor), + stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + ]) + } +} + +// MARK: - CalendarInforamtion + +struct CalendarInforamtion { + let dayOfWeek: String + let date: String +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift new file mode 100644 index 00000000..9e41f6ac --- /dev/null +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordCalendarViewController.swift @@ -0,0 +1,142 @@ +// +// RecordCalendarViewController.swift +// RecordFeature +// +// Created by 안종표 on 2023/11/18. +// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. +// + +import DesignSystem +import UIKit + +// MARK: - RecordCalendarViewController + +final class RecordCalendarViewController: UIViewController { + private var dataSource: RecordCalendarDiffableDataSource? + private lazy var calendarCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.scrollDirection = .horizontal + flowLayout.minimumLineSpacing = 0 + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.showsHorizontalScrollIndicator = false + return collectionView + }() + + override func viewDidLoad() { + super.viewDidLoad() + configureUI() + configureDataSource() + let items = [ + CalendarInforamtionItem(dayOfWeek: "화", date: "17"), + CalendarInforamtionItem(dayOfWeek: "화", date: "18"), + CalendarInforamtionItem(dayOfWeek: "화", date: "19"), + CalendarInforamtionItem(dayOfWeek: "화", date: "20"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + CalendarInforamtionItem(dayOfWeek: "화", date: "21"), + ] + configureSnapshot(items: items) + } +} + +private extension RecordCalendarViewController { + func configureUI() { + view.addSubview(calendarCollectionView) + NSLayoutConstraint.activate([ + calendarCollectionView.topAnchor.constraint(equalTo: view.topAnchor), + calendarCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + calendarCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + calendarCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } + + func configureDataSource() { + let cellRegistration = RecordCalendarCellRegistration { cell, _, itemIdentifier in + cell.configure( + calendarInformation: CalendarInforamtion( + dayOfWeek: itemIdentifier.dayOfWeek, + date: itemIdentifier.date + ) + ) + } + + dataSource = RecordCalendarDiffableDataSource( + collectionView: calendarCollectionView, + cellProvider: { collectionView, indexPath, itemIdentifier in + collectionView.dequeueConfiguredReusableCell( + using: cellRegistration, + for: indexPath, + item: itemIdentifier + ) + } + ) + } + + func configureSnapshot(items: [CalendarInforamtionItem]) { + var snapShot = RecordCalendarSnapShot() + snapShot.appendSections([.calendar]) + snapShot.appendItems(items) + dataSource?.apply(snapShot) + } +} + +// MARK: UICollectionViewDelegateFlowLayout + +extension RecordCalendarViewController: UICollectionViewDelegateFlowLayout { + func collectionView( + _ collectionView: UICollectionView, + layout _: UICollectionViewLayout, + sizeForItemAt _: IndexPath + ) -> CGSize { + return CGSize(width: view.frame.width / 7.5, height: collectionView.frame.height) + } + + func collectionView( + _ collectionView: UICollectionView, + didSelectItemAt indexPath: IndexPath + ) { + guard let cell = collectionView.cellForItem(at: indexPath) as? CalendarCollectionViewCell else { + return + } + cell.dayOfWeekLabel.textColor = DesignSystemColor.primaryText + cell.dateLabel.textColor = DesignSystemColor.primaryText + } + + func collectionView( + _ collectionView: UICollectionView, + didDeselectItemAt indexPath: IndexPath + ) { + guard let cell = collectionView.cellForItem(at: indexPath) as? CalendarCollectionViewCell else { + return + } + cell.dayOfWeekLabel.textColor = DesignSystemColor.gray03 + cell.dateLabel.textColor = DesignSystemColor.gray03 + } +} + +private extension RecordCalendarViewController { + typealias RecordCalendarCellRegistration = UICollectionView.CellRegistration + typealias RecordCalendarDiffableDataSource = UICollectionViewDiffableDataSource + typealias RecordCalendarSnapShot = NSDiffableDataSourceSnapshot +} + +// MARK: - Section + +private enum Section { + case calendar +} + +// MARK: - CalendarInforamtionItem + +private struct CalendarInforamtionItem: Identifiable, Hashable { + let id = UUID() + let dayOfWeek: String + let date: String +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift index 024c6f8f..8a72f8e5 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordContainerViewController.swift @@ -19,17 +19,29 @@ public final class RecordContainerViewController: UIViewController { private extension RecordContainerViewController { func configureUI() { + view.backgroundColor = .systemBackground let safeArea = view.safeAreaLayoutGuide + let recordCalendarViewController = RecordCalendarViewController() + guard let calendarView = recordCalendarViewController.view else { return } + calendarView.translatesAutoresizingMaskIntoConstraints = false + add(child: recordCalendarViewController) + NSLayoutConstraint.activate([ + calendarView.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: Metrics.componentInterval), + calendarView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), + calendarView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + calendarView.heightAnchor.constraint(equalToConstant: Metrics.calendarHeight), + ]) + let recordListViewController = RecordListViewController() - guard let recordView = recordListViewController.view else { return } - recordView.translatesAutoresizingMaskIntoConstraints = false + guard let listView = recordListViewController.view else { return } + listView.translatesAutoresizingMaskIntoConstraints = false add(child: recordListViewController) NSLayoutConstraint.activate([ - recordView.topAnchor.constraint(equalTo: safeArea.topAnchor), - recordView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor), - recordView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor), - recordView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor), + listView.topAnchor.constraint(equalTo: calendarView.bottomAnchor, constant: Metrics.componentInterval), + listView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), + listView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + listView.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -Metrics.bottomInterval), ]) } @@ -39,3 +51,11 @@ private extension RecordContainerViewController { viewController.didMove(toParent: viewController) } } + +// MARK: - Metrics + +private enum Metrics { + static let componentInterval: CGFloat = 24 + static let bottomInterval: CGFloat = 215 + static let calendarHeight: CGFloat = 51 +} diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift index 80811dd6..aa8f63c5 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift @@ -62,28 +62,26 @@ final class RecordListViewController: UIViewController { private extension RecordListViewController { func configureUI() { - let safeArea = view.safeAreaLayoutGuide - view.addSubview(todayLabel) NSLayoutConstraint.activate([ - todayLabel.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: Metrics.topInterval), - todayLabel.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), - todayLabel.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + todayLabel.topAnchor.constraint(equalTo: view.topAnchor), + todayLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor), + todayLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) view.addSubview(workoutInformationCollectionView) NSLayoutConstraint.activate([ workoutInformationCollectionView.topAnchor.constraint(equalTo: todayLabel.bottomAnchor, constant: Metrics.componentInterval), - workoutInformationCollectionView.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), - workoutInformationCollectionView.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), + workoutInformationCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + workoutInformationCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) view.addSubview(recordButton) NSLayoutConstraint.activate([ recordButton.topAnchor.constraint(equalTo: workoutInformationCollectionView.bottomAnchor, constant: Metrics.componentInterval), - recordButton.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: Metrics.componentInterval), - recordButton.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -Metrics.componentInterval), - recordButton.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor, constant: -Metrics.bottomInterval), + recordButton.leadingAnchor.constraint(equalTo: view.leadingAnchor), + recordButton.trailingAnchor.constraint(equalTo: view.trailingAnchor), + recordButton.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) } @@ -135,8 +133,6 @@ extension RecordListViewController: UICollectionViewDelegateFlowLayout { private enum Metrics { static let componentInterval: CGFloat = 24 - static let topInterval: CGFloat = 92 - static let bottomInterval: CGFloat = 215 static let itemWidthRatio: CGFloat = 2.45 static let itemHeightRatio: CGFloat = 1.5 } From 996bb342bf60d2ab5c29d1e72dee1843b2d635b8 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sat, 18 Nov 2023 20:09:59 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20custom=20CallendarCollectionView?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 1baf0e0c8dadbaadf986d7c848be7c9429047391 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sun, 19 Nov 2023 00:44:05 +0900 Subject: [PATCH 09/10] =?UTF-8?q?chore:=20SceneDelegate=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=B7=B0=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iOS/Projects/App/Sources/SceneDelegate.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/iOS/Projects/App/Sources/SceneDelegate.swift b/iOS/Projects/App/Sources/SceneDelegate.swift index 0a22755a..983518c7 100644 --- a/iOS/Projects/App/Sources/SceneDelegate.swift +++ b/iOS/Projects/App/Sources/SceneDelegate.swift @@ -6,7 +6,6 @@ // Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved. // -import RecordFeature import UIKit final class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -15,7 +14,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) { guard let windowScene = scene as? UIWindowScene else { return } window = UIWindow(windowScene: windowScene) - window?.rootViewController = RecordContainerViewController() + window?.rootViewController = ViewController() window?.makeKeyAndVisible() } } From be366e646c5d0a6e72c56b5b3ec9629b0d369cc9 Mon Sep 17 00:00:00 2001 From: JongPyoAhn Date: Sun, 19 Nov 2023 01:11:58 +0900 Subject: [PATCH 10/10] =?UTF-8?q?chore:=20=EC=98=A4=ED=83=88=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewController/RecordListViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift index aa8f63c5..a2bc4af9 100644 --- a/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift +++ b/iOS/Projects/Features/Record/Sources/RecordScene/ViewController/RecordListViewController.swift @@ -11,7 +11,7 @@ import UIKit // MARK: - RecordListViewController final class RecordListViewController: UIViewController { - private var workoutInforamtionDataSource: WorkoutInformationDataSource? + private var workoutInformationDataSource: WorkoutInformationDataSource? private let todayLabel: UILabel = { let label = UILabel() @@ -96,7 +96,7 @@ private extension RecordListViewController { ) } - workoutInforamtionDataSource = WorkoutInformationDataSource( + workoutInformationDataSource = WorkoutInformationDataSource( collectionView: workoutInformationCollectionView, cellProvider: { collectionView, indexPath, itemIdentifier in @@ -113,7 +113,7 @@ private extension RecordListViewController { var snapShot = WorkoutInformationSnapShot() snapShot.appendSections([.workoutList]) snapShot.appendItems(items) - workoutInforamtionDataSource?.apply(snapShot) + workoutInformationDataSource?.apply(snapShot) } }