-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] #28 - 커스텀 캘린더 컴포넌트 UI 구현 #36
Open
kms0233
wants to merge
5
commits into
develop
Choose a base branch
from
feat/#28-CustomCalendar
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
5f66366
[Feat/#28] Date+ extension 구현
kms0233 b9926b7
[Feat/#28] CustomCalendar 컴포넌트 구현
kms0233 c2e10d1
Merge branch 'develop' into feat/#28-CustomCalendar
kms0233 5422ea1
[Add/#28] 이전 날짜 비활성화 처리 추가
kms0233 92aa19b
[Add/#28] 로그 추가
kms0233 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
247 changes: 247 additions & 0 deletions
247
Gongbaek_iOS/Gongbaek_iOS/Global/Component/Others/CustomCalendar.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
// | ||
// CustomCalendar.swift | ||
// Gongbaek_iOS | ||
// | ||
// Created by 김민서 on 1/15/25. | ||
// | ||
|
||
import SwiftUI | ||
|
||
struct CustomCalendar: View { | ||
@State private var month: Date = Date() | ||
@State private var clickedCurrentMonthDates: Date? | ||
|
||
var today: Date { | ||
let now = Date() | ||
let components = Calendar.current.dateComponents([.year, .month, .day], from: now) | ||
return Calendar.current.date(from: components)! | ||
} | ||
|
||
static let calendarHeaderDateFormatter: DateFormatter = { | ||
let formatter = DateFormatter() | ||
formatter.dateFormat = "yyyy년 M월" | ||
return formatter | ||
}() | ||
|
||
static let weekdaySymbols: [String] = { | ||
let formatter = DateFormatter() | ||
formatter.locale = Locale(identifier: "ko_KR") | ||
return formatter.veryShortWeekdaySymbols | ||
}() | ||
|
||
init( | ||
month: Date = Date(), | ||
clickedCurrentMonthDates: Date? = nil | ||
) { | ||
_month = State(initialValue: month) | ||
_clickedCurrentMonthDates = State(initialValue: clickedCurrentMonthDates) | ||
} | ||
|
||
var body: some View { | ||
VStack { | ||
headerView | ||
calendarGridView | ||
} | ||
} | ||
|
||
private var headerView: some View { | ||
VStack { | ||
HStack(alignment: .center, spacing: 10) { | ||
Button( | ||
action: { | ||
changeMonth(by: -1) | ||
}) { | ||
Image(.icArrowLeft32) | ||
.foregroundColor(.gray04) | ||
.frame(width: 32, height: 32) | ||
} | ||
.disabled(false) | ||
|
||
Text(month, formatter: Self.calendarHeaderDateFormatter) | ||
.font(.pretendard(.title2_sb_18)) | ||
|
||
Button( | ||
action: { | ||
changeMonth(by: 1) | ||
}) { | ||
Image(.icArrowRight32) | ||
.foregroundColor(.gray04) | ||
.frame(width: 32, height: 32) | ||
} | ||
.disabled(false) | ||
} | ||
.padding(.bottom, 24) | ||
|
||
HStack { | ||
ForEach(Self.weekdaySymbols.indices, id: \.self) { index in | ||
let symbol = Self.weekdaySymbols[index] | ||
Text(symbol) | ||
.font(.pretendard(.body1_sb_16)) | ||
.foregroundColor(.gray06) | ||
.frame(maxWidth: .infinity) | ||
} | ||
} | ||
.padding(.bottom, 28) | ||
} | ||
} | ||
|
||
private var calendarGridView: some View { | ||
let daysInMonth = numberOfDays(in: month) | ||
let firstWeekday = firstWeekdayOfMonth(in: month) - 1 | ||
let lastDayOfMonthBefore = numberOfDays(in: previousMonth()) | ||
let numberOfRows = Int(ceil(Double(daysInMonth + firstWeekday) / 7.0)) | ||
let visibleDaysOfNextMonth = numberOfRows * 7 - (daysInMonth + firstWeekday) | ||
|
||
return LazyVGrid(columns: Array(repeating: GridItem(), count: 7), spacing: 24) { | ||
ForEach(-firstWeekday ..< daysInMonth + visibleDaysOfNextMonth, id: \.self) { index in | ||
Group { | ||
if index > -1 && index < daysInMonth { | ||
let date = getDate(for: index) | ||
let day = Calendar.current.component(.day, from: date) | ||
let clicked = clickedCurrentMonthDates == date | ||
let isToday = date.formattedCalendarDayDate == today.formattedCalendarDayDate | ||
let weekday = Calendar.current.component(.weekday, from: date) | ||
let isPast = date < today | ||
|
||
CalendarCell( | ||
day: day, | ||
clicked: clicked, | ||
isToday: isToday, | ||
isCurrentMonthDay: true, | ||
isDisabled: isWeekend(weekday) || isPast | ||
) | ||
.onTapGesture { | ||
if !isWeekend(weekday) && !isPast { | ||
clickedCurrentMonthDates = date | ||
print("Clicked date: \(date)") // 클릭된 날짜 출력 | ||
|
||
} | ||
} | ||
} else if let prevMonthDate = Calendar.current.date( | ||
byAdding: .day, | ||
value: index + lastDayOfMonthBefore, | ||
to: previousMonth() | ||
) { | ||
let day = Calendar.current.component(.day, from: prevMonthDate) | ||
|
||
CalendarCell( | ||
day: day, | ||
isDisabled: true | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private func isWeekend(_ weekday: Int) -> Bool { | ||
return weekday == 1 || weekday == 7 | ||
} | ||
} | ||
|
||
private struct CalendarCell: View { | ||
private var day: Int | ||
private var clicked: Bool | ||
private var isToday: Bool | ||
private var isCurrentMonthDay: Bool | ||
private var isDisabled: Bool | ||
|
||
private var textColor: Color { | ||
return getTextColor() | ||
} | ||
|
||
private var backgroundColor: Color { | ||
return getBackgroundColor() | ||
} | ||
|
||
fileprivate init( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
day: Int, | ||
clicked: Bool = false, | ||
isToday: Bool = false, | ||
isCurrentMonthDay: Bool = true, | ||
isDisabled: Bool = false | ||
) { | ||
self.day = day | ||
self.clicked = clicked | ||
self.isToday = isToday | ||
self.isCurrentMonthDay = isCurrentMonthDay | ||
self.isDisabled = isDisabled | ||
} | ||
|
||
var body: some View { | ||
Circle() | ||
.fill(backgroundColor) | ||
.overlay( | ||
Text(String(day)) | ||
.font(.pretendard(.body1_m_16)) | ||
) | ||
.foregroundColor(textColor) | ||
.frame(height: 28) | ||
} | ||
|
||
private func getTextColor() -> Color { | ||
switch (isDisabled, clicked, isToday, isCurrentMonthDay) { | ||
case (true, _, _, _): return .gray04 | ||
case (_, true, _, _): return .grayWhite | ||
case (_, _, true, _): return .mainOrange | ||
case (_, _, _, true): return .grayBlack | ||
default: return .gray04 | ||
} | ||
} | ||
|
||
private func getBackgroundColor() -> Color { | ||
return clicked ? .mainOrange : .white | ||
} | ||
} | ||
|
||
extension CustomCalendar { | ||
|
||
func getDate(for index: Int) -> Date { | ||
let calendar = Calendar.current | ||
guard let firstDayOfMonth = calendar.date( | ||
from: DateComponents( | ||
year: calendar.component(.year, from: month), | ||
month: calendar.component(.month, from: month), | ||
day: 1 | ||
) | ||
) else { | ||
return Date() | ||
} | ||
|
||
var dateComponents = DateComponents() | ||
dateComponents.day = index | ||
|
||
let timeZone = TimeZone.current | ||
let offset = Double(timeZone.secondsFromGMT(for: firstDayOfMonth)) | ||
dateComponents.second = Int(offset) | ||
|
||
let date = calendar.date(byAdding: dateComponents, to: firstDayOfMonth) ?? Date() | ||
return date | ||
} | ||
|
||
func numberOfDays(in date: Date) -> Int { | ||
return Date.numberOfDays(in: date) | ||
} | ||
|
||
func firstWeekdayOfMonth(in date: Date) -> Int { | ||
return Date.firstWeekdayOfMonth(in: date) | ||
} | ||
|
||
func previousMonth() -> Date { | ||
return Date.previousMonth(from: month) | ||
} | ||
|
||
func adjustedMonth(by value: Int) -> Date { | ||
return Date.adjustedMonth(from: month, by: value) | ||
} | ||
|
||
func changeMonth(by value: Int) { | ||
self.month = adjustedMonth(by: value) | ||
} | ||
} | ||
|
||
#Preview { | ||
CustomCalendar() | ||
.padding(16) | ||
Spacer() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// | ||
// Date+.swift | ||
// Gongbaek_iOS | ||
// | ||
// Created by 김민서 on 1/15/25. | ||
// | ||
|
||
import SwiftUI | ||
|
||
extension Date { | ||
|
||
static let calendarDayDateFormatter: DateFormatter = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이거 좋다 나도 나중에 쓸래요 |
||
let formatter = DateFormatter() | ||
formatter.dateFormat = "MMMM yyyy dd" | ||
return formatter | ||
}() | ||
|
||
var formattedCalendarDayDate: String { | ||
return Date.calendarDayDateFormatter.string(from: self) | ||
} | ||
|
||
static func numberOfDays(in date: Date) -> Int { | ||
return Calendar.current.range(of: .day, in: .month, for: date)?.count ?? 0 | ||
} | ||
|
||
static func firstWeekdayOfMonth(in date: Date) -> Int { | ||
let components = Calendar.current.dateComponents([.year, .month], from: date) | ||
let firstDayOfMonth = Calendar.current.date(from: components)! | ||
return Calendar.current.component(.weekday, from: firstDayOfMonth) | ||
} | ||
|
||
static func previousMonth(from date: Date) -> Date { | ||
let components = Calendar.current.dateComponents([.year, .month], from: date) | ||
let firstDayOfMonth = Calendar.current.date(from: components)! | ||
let previousMonth = Calendar.current.date(byAdding: .month, value: -1, to: firstDayOfMonth)! | ||
return previousMonth | ||
} | ||
|
||
static func adjustedMonth(from date: Date, by value: Int) -> Date { | ||
if let newMonth = Calendar.current.date(byAdding: .month, value: value, to: date) { | ||
return newMonth | ||
} | ||
return date | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init()으로 넣어두는 거구나... 난 다들 init을 왜쓰나 했어 초기값넣어두면 되는데 ! ㅋㅋ