Skip to content

Commit fe97d38

Browse files
authored
[GWL-442] WriteBoard뷰 구현 (#448)
* feat: Tuist에 features에 WirteBoard 추가 * feat: View, ViewModel 생성 * feat: Coordaintor 생성 * faet: ContainerViewController 생성 * feat: ContainerViewController 매서드 생성 * feat: ProfileFlow에 WriteBoardFeature을 불러올 수 있게 Coordainotr 및 VM 코드 수정 * feat: WriteBoardFlow 삭제 * feat: Dependency 수정 * feat: ProfileFeature와 연결 * feat: WorkoutHistoryCell 생성 * feat: CollectionView -> TableView로 변경 * feat: stackViewHeight추가 * feat: TableView DataSource Type 변경 * build: 접근 제어자 수정 * feat: WriteBoard와 WorkoutHistory Coordinator로 연결 * feat: �WorkoutHistoryCell Contraints에러 수정 * feat: Header StackView 생성 * feat: 헤더 뷰 구현 * style: 변수 tableViewCell 에서 Row로 이름 변경 * feat: AttachPicutre CollectionView 및 cell 추가 * feat: StackView 에러 수정 * feat: Cell 위치 조정 * feat: ScrollView Scroll 가능하게 기능 추가 * add: WriteBoard의 복잡한 데이터 흐름을 쪼개기 위한 파일 분리 * delete: 중복 파일 삭제 (ContainerViewController) * feat: KeyBoard가 View를 문제 해결 * feat: Spacing변경 * feat: 매직넘버 처리 * chore: SwiftFormat 적용
1 parent 9dd4665 commit fe97d38

File tree

12 files changed

+783
-14
lines changed

12 files changed

+783
-14
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//
2+
// Record.swift
3+
// WriteBoardFeature
4+
//
5+
// Created by MaraMincho on 1/11/24.
6+
// Copyright © 2024 kr.codesquad.boostcamp8. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
// MARK: - Record
12+
13+
/// 기록 목록을 표시하기위해 사용하는 모델입니다.
14+
public struct Record: Codable, Hashable {
15+
/// 현재 운동의 날짜를 나타냅니다.
16+
let dateString: String
17+
18+
/// 현재 운동의 목록을 나타냅니다.
19+
let workoutID: Int
20+
21+
/// 운동 시작 시간
22+
///
23+
/// HH:MM 으로 표시
24+
let startTime: String
25+
26+
/// 운동 끝 시간
27+
///
28+
/// HH:MM 으로 표시
29+
let endTime: String
30+
31+
/// 총 운동한 거리를 "미터"단위로 표시해줍니다.
32+
let distance: Int
33+
}
34+
35+
public extension Record {
36+
var name: String {
37+
switch workoutID {
38+
case 1:
39+
return "달리기"
40+
case 2:
41+
return "수영"
42+
case 3:
43+
return "사이클"
44+
default:
45+
return "달리기"
46+
}
47+
}
48+
49+
var durationTime: String {
50+
guard
51+
let endDate = DateFormatter.HHmmFormatter.date(from: endTime),
52+
let startDate = DateFormatter.HHmmFormatter.date(from: startTime)
53+
else {
54+
return ""
55+
}
56+
let timeInterval = endDate.timeIntervalSince(startDate)
57+
let hours = Int(timeInterval / 3600)
58+
let minutes = Int((timeInterval.truncatingRemainder(dividingBy: 3600)) / 60)
59+
if minutes == 0 {
60+
return hours == 0 ? "" : "\(hours)시간"
61+
}
62+
return hours == 0 ? "\(minutes)" : "\(hours)시간\(minutes)"
63+
}
64+
}
65+
66+
private extension DateFormatter {
67+
static let HHmmFormatter: DateFormatter = {
68+
let formatter = DateFormatter()
69+
formatter.dateFormat = "HH:mm"
70+
return formatter
71+
}()
72+
}

iOS/Projects/Features/WriteBoard/Sources/Presentation/Common/Coordinator/WriteBoardCoordinator.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public protocol WriteBoardFeatureFinishDelegate: AnyObject {
2020
// MARK: - WriteBoardFeatureCoordinating
2121

2222
public protocol WriteBoardFeatureCoordinating: Coordinating {
23-
func pushWriteBoardScene()
23+
func pushWriteBoardScene(record: Record)
2424
func didFinishWriteBoard()
2525
func cancelWriteBoard()
2626
}
@@ -59,12 +59,18 @@ public final class WriteBoardCoordinator: WriteBoardFeatureCoordinating {
5959

6060
private func pushWorkoutHistorySelectScene() {
6161
let viewModel = WorkoutHistorySelectViewModel()
62+
viewModel.writeBoardCoordinator = self
6263
let viewController = WorkoutHistorySelectViewController(viewModel: viewModel)
6364

6465
containerViewController?.pushViewController(viewController, animated: false)
6566
}
6667

67-
public func pushWriteBoardScene() {}
68+
public func pushWriteBoardScene(record: Record) {
69+
let viewModel = WriteBoardViewModel(record: record)
70+
let viewController = WriteBoardViewController(viewModel: viewModel)
71+
72+
containerViewController?.pushViewController(viewController, animated: true)
73+
}
6874

6975
public func didFinishWriteBoard() {}
7076

File renamed without changes.
File renamed without changes.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// AttachPictureCollectionViewCell.swift
3+
// WriteBoardFeature
4+
//
5+
// Created by MaraMincho on 1/11/24.
6+
// Copyright © 2024 kr.codesquad.boostcamp8. All rights reserved.
7+
//
8+
9+
import DesignSystem
10+
import UIKit
11+
12+
// MARK: - AttachPictureCollectionViewCell
13+
14+
final class AttachPictureCollectionViewCell: UICollectionViewCell {
15+
static let identifier = "AttachPictureCollectionViewCell"
16+
17+
override init(frame _: CGRect) {
18+
super.init(frame: .zero)
19+
setup()
20+
}
21+
22+
@available(*, unavailable)
23+
required init?(coder _: NSCoder) {
24+
fatalError("이 생성자는 사용할 수 없습니다.")
25+
}
26+
27+
private let contentImageView: UIImageView = {
28+
let imageView = UIImageView()
29+
30+
imageView.translatesAutoresizingMaskIntoConstraints = false
31+
return imageView
32+
}()
33+
34+
func configure(image: UIImage?) {
35+
contentImageView.image = image
36+
}
37+
}
38+
39+
private extension AttachPictureCollectionViewCell {
40+
func setup() {
41+
setupStyle()
42+
setupViewHierarchyAndConstraints()
43+
makeShadowAndRounded()
44+
}
45+
46+
func setupStyle() {
47+
backgroundColor = DesignSystemColor.primaryBackground
48+
}
49+
50+
func setupViewHierarchyAndConstraints() {
51+
addSubview(contentImageView)
52+
contentImageView.topAnchor.constraint(equalTo: topAnchor).isActive = true
53+
contentImageView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
54+
contentImageView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
55+
contentImageView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
56+
}
57+
58+
func makeShadowAndRounded() {
59+
let radius: CGFloat = 10
60+
contentView.layer.cornerRadius = radius
61+
contentView.layer.borderWidth = 1
62+
contentView.layer.borderColor = UIColor.clear.cgColor
63+
contentView.layer.masksToBounds = true
64+
65+
layer.shadowColor = UIColor.black.cgColor
66+
layer.shadowOffset = CGSize(width: 0, height: 1.0)
67+
layer.shadowRadius = 2.0
68+
layer.shadowOpacity = 0.5
69+
layer.masksToBounds = false
70+
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath
71+
layer.cornerRadius = radius
72+
}
73+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//
2+
// WorkoutHistoryDescriptionView.swift
3+
// WriteBoardFeature
4+
//
5+
// Created by MaraMincho on 1/11/24.
6+
// Copyright © 2024 kr.codesquad.boostcamp8. All rights reserved.
7+
//
8+
9+
import DesignSystem
10+
import UIKit
11+
12+
// MARK: - WorkoutHistoryDescriptionView
13+
14+
final class WorkoutHistoryDescriptionView: UIStackView {
15+
init(record: Record) {
16+
super.init(frame: .zero)
17+
axis = .vertical
18+
tableCellStackView = makeTableCellStackView(record)
19+
setupViewHierarchyAndConstraints()
20+
}
21+
22+
/// UIComponents
23+
private var tableCellStackView: UIStackView?
24+
25+
private let workoutHistoryTitleLabel: UILabel = {
26+
let label = UILabel()
27+
label.text = "운동 정보"
28+
label.textColor = DesignSystemColor.primaryText
29+
label.font = .preferredFont(forTextStyle: .title2, weight: .bold)
30+
31+
label.translatesAutoresizingMaskIntoConstraints = false
32+
return label
33+
}()
34+
35+
func setupViewHierarchyAndConstraints() {
36+
addArrangedSubview(workoutHistoryTitleLabel)
37+
guard let tableCellStackView else {
38+
return
39+
}
40+
spacing = Constants.inGroupTitleAndContentSpacing
41+
addArrangedSubview(tableCellStackView)
42+
}
43+
44+
private func makeTableCellStackView(_ record: Record) -> UIStackView {
45+
let tableCellStackView: UIStackView = {
46+
let stackView = UIStackView(arrangedSubviews: [
47+
WorkoutHistoryDescriptionRowView(titleString: Constants.workoutName, description: record.name),
48+
WorkoutHistoryDescriptionRowView(titleString: Constants.date, description: record.dateString),
49+
WorkoutHistoryDescriptionRowView(titleString: Constants.time, description: record.durationTime + " (" + record.startTime + "~" + record.endTime + ")"),
50+
WorkoutHistoryDescriptionRowView(titleString: Constants.distance, description: String(format: "%.1f", Double(record.distance) / 1000) + "km"),
51+
])
52+
stackView.axis = .vertical
53+
stackView.spacing = Constants.cellSpacing
54+
55+
stackView.translatesAutoresizingMaskIntoConstraints = false
56+
return stackView
57+
}()
58+
return tableCellStackView
59+
}
60+
61+
override init(frame: CGRect) {
62+
super.init(frame: frame)
63+
}
64+
65+
@available(*, unavailable)
66+
required init(coder _: NSCoder) {
67+
fatalError("사용할 수 없는 생성자 입니다.")
68+
}
69+
70+
private enum Constants {
71+
static let workoutName: String = "운동 종류"
72+
static let date: String = "날짜"
73+
static let time = "시간"
74+
static let distance = "거리"
75+
76+
static let inGroupTitleAndContentSpacing: CGFloat = 12
77+
78+
static let cellSpacing: CGFloat = 6
79+
}
80+
}
81+
82+
// MARK: - WorkoutHistoryDescriptionRowView
83+
84+
private final class WorkoutHistoryDescriptionRowView: UIStackView {
85+
init(titleString: String, description: String) {
86+
super.init(frame: .zero)
87+
titleLabel.text = titleString
88+
descriptionLabel.text = description
89+
90+
distribution = .fillProportionally
91+
setupViewHierarchyAndConstraints()
92+
}
93+
94+
override init(frame _: CGRect) {
95+
super.init(frame: .zero)
96+
}
97+
98+
@available(*, unavailable)
99+
required init(coder _: NSCoder) {
100+
fatalError("사용할 수 없습니다.")
101+
}
102+
103+
private let spacingView: UIView = {
104+
let view = UIView()
105+
106+
view.translatesAutoresizingMaskIntoConstraints = false
107+
return view
108+
}()
109+
110+
private let titleLabel: UILabel = {
111+
let label = UILabel()
112+
label.font = .preferredFont(forTextStyle: .title3)
113+
label.tintColor = DesignSystemColor.primaryText
114+
label.textAlignment = .left
115+
116+
label.setContentHuggingPriority(.defaultLow, for: .horizontal)
117+
label.translatesAutoresizingMaskIntoConstraints = false
118+
return label
119+
}()
120+
121+
private let descriptionLabel: UILabel = {
122+
let label = UILabel()
123+
label.font = .preferredFont(forTextStyle: .headline, weight: .bold)
124+
label.tintColor = DesignSystemColor.primaryText
125+
126+
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
127+
label.translatesAutoresizingMaskIntoConstraints = false
128+
return label
129+
}()
130+
131+
func setupViewHierarchyAndConstraints() {
132+
addArrangedSubview(spacingView)
133+
spacingView.widthAnchor.constraint(equalToConstant: Constants.spacingViewWidth).isActive = true
134+
135+
addArrangedSubview(titleLabel)
136+
titleLabel.widthAnchor.constraint(equalToConstant: Constants.titleWidth).isActive = true
137+
138+
addArrangedSubview(descriptionLabel)
139+
descriptionLabel.widthAnchor.constraint(equalToConstant: Constants.descriptionWidth).isActive = true
140+
}
141+
142+
enum Constants {
143+
static let titleWidth: CGFloat = 99
144+
static let descriptionWidth: CGFloat = 212
145+
146+
static let titleAndDescriptionWidthSpacing: CGFloat = 15
147+
148+
static let intrinsicContentHeight: CGFloat = 24
149+
150+
static let spacingViewWidth: CGFloat = 9
151+
}
152+
}

0 commit comments

Comments
 (0)