Skip to content

Commit bdfb023

Browse files
committed
fix: screenWidth와 safeAreaInset을 통해 width를 정의 후 수식으로 셀 크기를 정의하도록 변경
1 parent 11508e2 commit bdfb023

4 files changed

Lines changed: 88 additions & 37 deletions

File tree

DevLog/Presentation/Structure/Profile/ProfileCompletionDay.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ struct ProfileCompletionDay: Hashable {
1111
let date: Date
1212
let createdCount: Int
1313
let completedCount: Int
14-
let isInMonth: Bool
14+
let isVisible: Bool
1515
}

DevLog/Presentation/Structure/Profile/ProfileCompletionQuarter.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct ProfileCompletionQuarter: Identifiable, Hashable {
2020
months
2121
.flatMap { $0.weeks }
2222
.flatMap { $0 }
23-
.filter { $0.isInMonth }
23+
.filter { $0.isVisible }
2424
.map { $0.createdCount + $0.completedCount }
2525
.max() ?? 0
2626
}
@@ -32,7 +32,7 @@ struct ProfileCompletionQuarter: Identifiable, Hashable {
3232
let days = months
3333
.flatMap(\.weeks)
3434
.flatMap { $0 }
35-
.filter(\.isInMonth)
35+
.filter(\.isVisible)
3636
let groupedByWeekStart = Dictionary(grouping: days) { day in
3737
calendar.dateInterval(of: .weekOfYear, for: day.date)?.start
3838
?? calendar.startOfDay(for: day.date)

DevLog/Presentation/ViewModel/ProfileViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ private extension ProfileViewModel {
440440
date: normalizedDate,
441441
createdCount: createdCount,
442442
completedCount: completedCount,
443-
isInMonth: isInMonth
443+
isVisible: isInMonth
444444
)
445445
)
446446
guard let nextDay = calendar.date(byAdding: .day, value: 1, to: cursor) else { break }

DevLog/UI/Profile/ProfileHeatmapView.swift

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,35 @@
88
import SwiftUI
99

1010
struct ProfileHeatmapView: View {
11+
@Environment(\.safeAreaInsets) private var safeAreaInsets
12+
@Environment(\.sceneWidth) private var sceneWidth
1113
let quarter: ProfileCompletionQuarter
1214
let selectedActivityTypes: Set<ProfileActivityType>
1315
let selectedDay: ProfileCompletionDay?
1416
let onSelectDay: (ProfileCompletionDay) -> Void
1517

1618
var body: some View {
19+
let layout = ProfileHeatmapLayout(
20+
availableWidth: availableWidth,
21+
weekCounts: quarter.months.map(\.weeks.count)
22+
)
23+
1724
VStack(alignment: .leading, spacing: 10) {
1825
Text("활동 히트맵")
1926
.font(.subheadline)
2027
.bold()
2128
HStack(alignment: .top, spacing: 0) {
22-
weekdayLabel
23-
.padding(.trailing, 10)
24-
let months = quarter.months
25-
ForEach(Array(zip(months.indices, months)), id: \.1) { index, month in
26-
MonthCompactHeatmapView(
27-
month: month,
28-
maxCount: quarter.maxCount,
29-
selectedActivityTypes: selectedActivityTypes,
30-
selectedDay: selectedDay,
31-
onSelectDay: onSelectDay
32-
)
33-
if index < months.count - 1 {
34-
Spacer()
29+
weekdayLabel(layout: layout)
30+
HStack(alignment: .top, spacing: layout.monthSpacing) {
31+
ForEach(quarter.months) { month in
32+
MonthCompactHeatmapView(
33+
month: month,
34+
maxCount: quarter.maxCount,
35+
layout: layout,
36+
selectedActivityTypes: selectedActivityTypes,
37+
selectedDay: selectedDay,
38+
onSelectDay: onSelectDay
39+
)
3540
}
3641
}
3742
}
@@ -40,74 +45,120 @@ struct ProfileHeatmapView: View {
4045
}
4146

4247
@ViewBuilder
43-
private var weekdayLabel: some View {
48+
private func weekdayLabel(layout: ProfileHeatmapLayout) -> some View {
4449
let labels: [Int: String] = [
4550
2: "",
4651
4: "",
4752
6: ""
4853
]
4954
let orderedWeekdays = Array(1...7)
50-
let cellSize: CGFloat = 16
5155

52-
VStack(alignment: .leading, spacing: 4) {
56+
VStack(alignment: .leading, spacing: layout.cellSpacing) {
5357
ForEach(orderedWeekdays, id: \.self) { weekday in
5458
Group {
5559
if let label = labels[weekday] {
5660
Text(label)
5761
.font(.caption2)
5862
.foregroundStyle(.secondary)
59-
.frame(width: cellSize, height: cellSize)
63+
.frame(
64+
width: layout.cellSize,
65+
height: layout.cellSize,
66+
alignment: .leading
67+
)
6068
} else {
6169
Color.clear
62-
.frame(width: cellSize, height: cellSize)
70+
.frame(
71+
width: layout.cellSize,
72+
height: layout.cellSize
73+
)
6374
}
6475
}
6576
}
6677
}
67-
.padding(.top, 22)
78+
.padding(.top, layout.weekdayTopPadding)
79+
}
80+
81+
private var availableWidth: CGFloat {
82+
let horizontalPadding: CGFloat = 16 + 12
83+
return max(
84+
0,
85+
sceneWidth
86+
- safeAreaInsets.leading
87+
- safeAreaInsets.trailing
88+
- (horizontalPadding * 2)
89+
)
90+
}
91+
}
92+
93+
private struct ProfileHeatmapLayout {
94+
let cellSize: CGFloat
95+
let cellSpacing: CGFloat = 4
96+
let monthSpacing: CGFloat = 12
97+
let monthTitleSpacing: CGFloat = 6
98+
99+
init(availableWidth: CGFloat, weekCounts: [Int]) {
100+
let sanitizedWeekCounts = weekCounts.filter { 0 < $0 }
101+
let totalColumns = max(sanitizedWeekCounts.reduce(0, +), 1)
102+
let totalColumnSpacings = sanitizedWeekCounts.reduce(0) { partialResult, count in
103+
partialResult + max(count - 1, 0)
104+
}
105+
let fixedWidth = monthSpacing * CGFloat(max(sanitizedWeekCounts.count - 1, 0))
106+
+ cellSpacing * CGFloat(totalColumnSpacings)
107+
cellSize = max(0, availableWidth - fixedWidth) / CGFloat(totalColumns + 1)
108+
}
109+
110+
var weekdayTopPadding: CGFloat {
111+
cellSize + monthTitleSpacing
112+
}
113+
114+
var cellCornerRadius: CGFloat {
115+
max(2, cellSize * 0.2)
116+
}
117+
118+
var innerSelectionLineWidth: CGFloat {
119+
max(1.2, cellSize * 0.12)
68120
}
69121
}
70122

71123
private struct MonthCompactHeatmapView: View {
72124
@Environment(\.colorScheme) private var colorScheme
73125
let month: ProfileCompletionMonth
74126
let maxCount: Int
127+
let layout: ProfileHeatmapLayout
75128
let selectedActivityTypes: Set<ProfileActivityType>
76129
let selectedDay: ProfileCompletionDay?
77130
let onSelectDay: (ProfileCompletionDay) -> Void
78131
private let orderedWeekdays = Array(1...7)
79-
private let cellSize: CGFloat = 16
80-
private let cellSpacing: CGFloat = 4
81132

82133
var body: some View {
83-
VStack(alignment: .leading, spacing: 6) {
134+
VStack(alignment: .leading, spacing: layout.monthTitleSpacing) {
84135
Text(month.monthStart.formatted(.dateTime.month(.abbreviated)))
85-
.frame(height: cellSize)
136+
.frame(height: layout.cellSize)
86137
.font(.caption)
87138
.foregroundStyle(.secondary)
88139

89-
VStack(alignment: .leading, spacing: cellSpacing) {
140+
VStack(alignment: .leading, spacing: layout.cellSpacing) {
90141
ForEach(orderedWeekdays, id: \.self) { weekday in
91-
HStack(spacing: cellSpacing) {
142+
HStack(spacing: layout.cellSpacing) {
92143
ForEach(month.weeks.indices, id: \.self) { weekIndex in
93144
let day = month.weeks[weekIndex].first {
94145
Calendar.current.component(.weekday, from: $0.date) == weekday
95146
}
96147

97-
RoundedRectangle(cornerRadius: 3)
148+
RoundedRectangle(cornerRadius: layout.cellCornerRadius)
98149
.fill(fillColor(for: day, with: maxCount))
99-
.overlay(
100-
RoundedRectangle(cornerRadius: 3)
101-
.stroke(selectionInnerBorderColor(for: day), lineWidth: 2)
150+
.stroke(
151+
selectionInnerBorderColor(for: day),
152+
lineWidth: layout.innerSelectionLineWidth
102153
)
103154
.overlay(
104-
RoundedRectangle(cornerRadius: 4)
155+
RoundedRectangle(cornerRadius: layout.cellCornerRadius + 1)
105156
.stroke(selectionOuterBorderColor(for: day), lineWidth: 0.8)
106157
.padding(-1)
107158
)
108-
.frame(width: cellSize, height: cellSize)
159+
.frame(width: layout.cellSize, height: layout.cellSize)
109160
.onTapGesture {
110-
if let day, day.isInMonth {
161+
if let day, day.isVisible {
111162
onSelectDay(day)
112163
}
113164
}
@@ -135,7 +186,7 @@ private struct MonthCompactHeatmapView: View {
135186
}
136187

137188
private func fillColor(for day: ProfileCompletionDay?, with maxCount: Int) -> Color {
138-
guard let day, day.isInMonth else { return .clear }
189+
guard let day, day.isVisible else { return .clear }
139190
let count = dayCount(for: day)
140191
if count == 0 {
141192
return Color(.systemGray5)

0 commit comments

Comments
 (0)