diff --git a/Cherrish-iOS/Cherrish-iOS/Domain/Model/TreatmentEntity.swift b/Cherrish-iOS/Cherrish-iOS/Domain/Model/TreatmentEntity.swift index c1b40828..3f5431cf 100644 --- a/Cherrish-iOS/Cherrish-iOS/Domain/Model/TreatmentEntity.swift +++ b/Cherrish-iOS/Cherrish-iOS/Domain/Model/TreatmentEntity.swift @@ -7,7 +7,7 @@ import Foundation -struct TreatmentEntity: Equatable, Hashable { +struct TreatmentEntity: Identifiable, Equatable, Hashable { var id: Self { self } let name: String let benefits: [String] diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/Model/DownTimeSettingView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift similarity index 67% rename from Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/Model/DownTimeSettingView.swift rename to Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift index bb1fd5f5..334c9918 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/Model/DownTimeSettingView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DownTimeSettingView.swift @@ -8,8 +8,10 @@ import SwiftUI struct DownTimeSettingView: View { + @State var selectedTreatment: TreatmentEntity? = nil var treatments: [TreatmentEntity] - + let setday: (year: Int, month: Int, day: Int) + let today: (year: Int, month: Int, day: Int) var body: some View { VStack { Spacer() @@ -27,7 +29,19 @@ struct DownTimeSettingView: View { } ScrollView(.vertical, showsIndicators: false) { ForEach(treatments, id: \.self) { treatment in - TreatmentRowView(displayMode: .completeBoxView, treatmentEntity: treatment, isSelected: .constant(false), isCompleted: .constant(false), action: {}) + TreatmentRowView( + displayMode: .completeBoxView, + treatmentEntity: treatment, + isSelected: .constant( + selectedTreatment == treatment + ), + isCompleted: .constant( + false + ), + action: { + selectedTreatment = treatment + + }) } Spacer() @@ -64,9 +78,18 @@ struct DownTimeSettingView: View { Spacer() } } - + } .padding(.horizontal, 25.adjustedW) + .sheet(item: $selectedTreatment) { treatment in + DowntimeBottomSheetView( + treatment: treatment, + today: today, + setday: setday + ) + .presentationDetents([.extraLarge]) + .presentationBackground(.gray0) + .presentationDragIndicator(.visible) + } } - } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/DowntimeBottomSheetView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift similarity index 50% rename from Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/DowntimeBottomSheetView.swift rename to Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift index cd44cc45..1b446ee7 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/DowntimeBottomSheetView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/DowntimeBottomSheetView.swift @@ -8,13 +8,17 @@ import SwiftUI struct DowntimeBottomSheetView: View { - @State var selectedDowntime: Int = 5 - @State var rate: Double = 0.5 + let treatment: TreatmentEntity + let today: (year: Int, month: Int, day: Int) + let setday: (year: Int, month: Int, day: Int) + @State private var selectedDowntime: Int = 1 + @State private var rate: Double = 0.0 + @State private var betweenDays: Int = 0 var body: some View { VStack(spacing: 0) { Spacer() - .frame(height: 25.adjustedH) + .frame(height: 35.adjustedH) TypographyText("개인 다운타임으로 설정해주세요.", style: .title1_sb_18, color: .gray1000) .frame(height: 27.adjustedH) @@ -34,17 +38,30 @@ struct DowntimeBottomSheetView: View { grayLineView .padding(.horizontal, 25.adjustedW) - + pickerView grayLineView .padding(.horizontal, 25.adjustedW) + Spacer() + .frame(height: 44.adjustedH) + buttonView + + } + .onAppear { + selectedDowntime = treatment.downtimeMax + betweenDays = Date.daysBetween(from: setday, to: today) ?? 0 + rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 + } + .onChange(of: selectedDowntime) { + rate = betweenDays > 0 ? min(Double(selectedDowntime) / Double(betweenDays), 1.0) : 1.0 } } } extension DowntimeBottomSheetView { + private var speechBubble: some View { ZStack { Image(.speechBubble) @@ -52,7 +69,21 @@ extension DowntimeBottomSheetView { .scaledToFill() .frame(height: 53.adjustedH) - TypographyText("회복 목표디데이로부터 약 7일 전에 안정될 수 있어요.", style: .body1_r_14, color: .gray1000) + Group { + if betweenDays - selectedDowntime < 1 { + TypographyText( + "설정한 다운타임은 목표일을 넘깁니다.", + style: .body1_r_14, + color: .gray1000 + ) + } else { + TypographyText( + "회복 목표디데이로부터 약 \(betweenDays - selectedDowntime)일 전에 안정될 수 있어요.", + style: .body1_r_14, + color: .gray1000 + ) + } + } .lineLimit(1) .minimumScaleFactor(0.8) .padding(.horizontal, 18.adjustedW) @@ -72,8 +103,7 @@ extension DowntimeBottomSheetView { Spacer () - TypographyText("여유기간 7일", style: .title2_m_16, color: .gray800) - + TypographyText("여유기간 \(betweenDays - selectedDowntime < 0 ? 0 : betweenDays - selectedDowntime)일", style: .title2_m_16, color: .gray800) Spacer() } @@ -96,12 +126,12 @@ extension DowntimeBottomSheetView { .padding(.horizontal, 25.adjustedW) HStack { - TypographyText("1월 2일", style: .body2_r_13, color: .gray700) + TypographyText("\(today.month)월 \(today.day)일", style: .body2_r_13, color: .gray700) .frame(height: 18.adjustedH) Spacer() - TypographyText("1월 14일", style: .body2_r_13, color: .gray700) + TypographyText("\(setday.month)월 \(setday.day)일", style: .body2_r_13, color: .gray700) .frame(height: 18.adjustedH) } .padding(.horizontal, 25.adjustedW) @@ -119,10 +149,19 @@ extension DowntimeBottomSheetView { Spacer() VStack(spacing: 0) { - TypographyText("다운타임", style: .headline_sb_20, color: .gray1000) + TypographyText( + "다운타임", + style: .headline_sb_20, + color: .gray1000 + ) .frame(height: 30.adjustedH) - TypographyText("보통 3-5일", style: .title2_m_16, color: .gray600) + TypographyText( + "보통 \(treatment.downtimeMin)-\(treatment.downtimeMax)일", + style: .title2_m_16, + color: .gray600 + ) + .frame(height: 24.adjustedH) } Spacer() @@ -132,4 +171,37 @@ extension DowntimeBottomSheetView { Spacer() } } + + private var buttonView: some View { + GeometryReader { geo in + HStack(spacing: 4.adjustedW) { + CherrishButton( + title: "다운타임 없이 일정 추가", + type: .addEvent, + state: .constant( + .normal + ), + leadingIcon: .none, + trailingIcon: .none, + action: { }) + .frame(width: geo.size.width * 2/3 - 2) + + CherrishButton( + title: "확인", + type: .small, + state: .constant( + .normal + ), + leadingIcon: .none, + trailingIcon: .none, + action: { }) + .frame( + width: geo.size.width * 1/3 - 2 + ) + + } + } + .frame(height: 50.adjustedH) + .padding(.horizontal, 24.adjustedW) + } } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/TargetDdaySettingView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/TargetDdaySettingView.swift index ed6a2096..4189524c 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/TargetDdaySettingView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Components/TargetDdaySettingView.swift @@ -30,67 +30,70 @@ struct TargetDdaySettingView: View { @Binding var day: String var body: some View { - VStack { - Spacer() - .frame(height: 50.adjustedH) - - HStack(spacing:0){ - VStack(alignment: .leading, spacing: 0) { - TypographyText("회복을 계획할 때 고려해야 할", style: .title1_sb_18, color: .gray1000) - - TypographyText("중요한 일정이 있나요?", style: .title1_sb_18, color: .gray1000) + ScrollView(.vertical, showsIndicators: false) { + VStack { + Spacer() + .frame(height: 50.adjustedH) + + HStack(spacing:0){ + VStack(alignment: .leading, spacing: 0) { + TypographyText("회복을 계획할 때 고려해야 할", style: .title1_sb_18, color: .gray1000) + + TypographyText("중요한 일정이 있나요?", style: .title1_sb_18, color: .gray1000) + + } + Spacer() } + .frame(height: 54.adjustedH) Spacer() - } - .frame(height: 54.adjustedH) - - Spacer() - .frame(height: 40) - - HStack(spacing: 12.adjustedW) { - ForEach(DdayState.allCases, id: \.self) { state in - SelectionChip( - title: state.title, - isSelected: Binding( - get: { - dDayState == state - }, - set: {isSelected in - guard isSelected else { - return + .frame(height: 40) + + HStack(spacing: 12.adjustedW) { + ForEach(DdayState.allCases, id: \.self) { state in + SelectionChip( + title: state.title, + isSelected: Binding( + get: { + dDayState == state + }, + set: {isSelected in + guard isSelected else { + return + } + dDayState = state } - dDayState = state - } + ) ) - ) + } } - } - - Spacer() - .frame(height: 56.adjustedH) - - if let state = dDayState { - HStack(spacing: 0) { - switch state { + + Spacer() + .frame(height: 56.adjustedH) + + if let state = dDayState { + HStack(spacing: 0) { + switch state { case .yes: - TypographyText("언제까지 회복이 완료되면 좋을까요?", style: .title1_sb_18, color: .gray1000) + TypographyText("언제까지 회복이 완료되면 좋을까요?", style: .title1_sb_18, color: .gray1000) case .no: - TypographyText("대략적인 회복 목표일을 정해볼까요?", style: .title1_sb_18, color: .gray1000) + TypographyText("대략적인 회복 목표일을 정해볼까요?", style: .title1_sb_18, color: .gray1000) + } + + Spacer() + } + .frame(height: 27.adjustedH) Spacer() + .frame(height: 24.adjustedH) + DateTextBox(year: $year, month: $month, day: $day) } - - Spacer() - .frame(height: 24.adjustedH) - - DateTextBox(year: $year, month: $month, day: $day) - } } + .scrollDismissesKeyboard(.interactively) } } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/View/NoTreatmentView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/View/NoTreatmentView.swift index a8bdaee5..c8fb154f 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/View/NoTreatmentView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/View/NoTreatmentView.swift @@ -67,7 +67,20 @@ struct NoTreatmentView: View { NoTreatmentFilterView(viewModel: viewModel) case .downTimeSetting: - DownTimeSettingView(treatments: viewModel.selectedTreatments) + DownTimeSettingView( + treatments: viewModel.selectedTreatments, + setday: ( + viewModel.toInt( + viewModel.year + ), + viewModel.toInt( + viewModel.month + ), + viewModel.toInt( + viewModel.day + ) + ), today: viewModel.today + ) } } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/ViewModel/NoTreatmentViewModel.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/ViewModel/NoTreatmentViewModel.swift index 1b8e6365..d23422eb 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/ViewModel/NoTreatmentViewModel.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/NoTreatment/ViewModel/NoTreatmentViewModel.swift @@ -32,6 +32,16 @@ final class NoTreatmentViewModel: ObservableObject{ } } + var today: (year: Int, month: Int, day: Int) { + let calendar = Calendar.current + let now = Date() + return ( + calendar.component(.year, from: now), + calendar.component(.month, from: now), + calendar.component(.day, from: now) + ) + } + func next() { state.next() } @@ -40,6 +50,10 @@ final class NoTreatmentViewModel: ObservableObject{ state.previous() } + func toInt(_ value: String) -> Int { + Int(value) ?? 0 + } + func isDateTextFieldNotEmpty() -> Bool { guard !year.isEmpty, !month.isEmpty, !day.isEmpty else { return false diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift index 2a0acdc2..ce94a4ed 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/View/TreatmentView.swift @@ -13,126 +13,95 @@ struct TreatmentView: View { init(viewModel: TreatmentViewModel = TreatmentViewModel()) { _viewModel = StateObject(wrappedValue: viewModel) } + var body: some View { - VStack { + VStack(spacing: 0) { CherrishNavigationBar( - title:viewModel.state.title, - leftButtonAction: { - viewModel.previous() - }, - rightButtonAction: { - - } + title: viewModel.state.title, + leftButtonAction: { viewModel.previous() }, + rightButtonAction: { } ) + Spacer().frame(height: 20.adjustedH) + ProgressBar( totalSteps: Treatment.allCases.count, currentStep: .constant(viewModel.step) ) - .padding( .horizontal, 33.5.adjustedW) + .padding(.leading, 34.adjustedW) + .padding(.trailing, 33.adjustedW) + .padding(.bottom, 20.adjustedH) - VStack(spacing: 0){ - Group { - switch viewModel.state { - case .targetDdaySetting: - TargetDdaySettingView(dDayState: $viewModel.dDay, year: $viewModel.year, month: $viewModel.month, day: $viewModel.day) - .padding( .leading, 34.adjustedW) - .padding( .trailing, 33.adjustedW) - .id(viewModel.state) - case .treatmentFilter: - TreatmentFilterView(viewModel: viewModel) - case .downTimeSetting: - DownTimeSettingView(treatments: viewModel.selectedTreatments) - } - } - - Spacer() - - Group { - if viewModel.state == .treatmentFilter { - if !viewModel.selectedTreatments.isEmpty { - SelectedTreatmentView(selectedTreatments: viewModel.selectedTreatments, removeTreatment: viewModel.removeTreatment(_:)) - } - } - - CherrishButton( - title: "다음", - type: .large, - state: .constant(viewModel.canProceed ? .active : .normal), - leadingIcon: nil, - trailingIcon: nil - ) { - viewModel.next() - - } - .padding(.horizontal, 25.adjustedW) - - } - + VStack(spacing: 0) { + contentView() Spacer() - .frame(height: 38.adjustedH) - + bottomView() + Spacer().frame(height: 38.adjustedH) } .id(viewModel.step) } - .ignoresSafeArea(.keyboard) + .ignoresSafeArea(.keyboard,edges: .bottom) + .onTapGesture { + hideKeyboard() + } } -} - -//MARK: - TreatmentSelectedCategoryView - -private struct TreatmentSelectedCategory: View { - @ObservedObject var viewModel: NoTreatmentViewModel - let columns = [ - GridItem(.flexible()), - GridItem(.flexible()) - ] - var body: some View { - VStack { - Spacer() - .frame(height: 70.adjustedH) - HStack(spacing: 0) { - VStack(alignment: .leading, spacing: 0) { - TypographyText( - "요즘 가장 신경 쓰이는 ", - style: .title1_sb_18, - color: .gray1000 - ) - TypographyText( - "피부 고민은 무엇인가요?", - style: .title1_sb_18, - color: .gray1000 - ) - TypographyText( - "선택한 고민을 기준으로 시술 정보를 정리해줘요.", - style: .body1_m_14, - color: .gray700 + + @ViewBuilder + private func contentView() -> some View { + switch viewModel.state { + case .targetDdaySetting: + TargetDdaySettingView( + dDayState: $viewModel.dDay, + year: $viewModel.year, + month: $viewModel.month, + day: $viewModel.day + ) + .padding(.leading, 34.adjustedW) + .padding(.trailing, 33.adjustedW) + .id(String(describing: viewModel.state)) + + case .treatmentFilter: + TreatmentFilterView(viewModel: viewModel) + + case .downTimeSetting: + DownTimeSettingView( + treatments: viewModel.selectedTreatments, + setday: ( + viewModel.toInt( + viewModel.year + ), + viewModel.toInt( + viewModel.month + ), + viewModel.toInt( + viewModel.day ) - } - Spacer() + ), + today: viewModel.today + ) + } + } + + @ViewBuilder + private func bottomView() -> some View { + VStack(spacing: 0) { + if viewModel.state == .treatmentFilter, !viewModel.selectedTreatments.isEmpty { + SelectedTreatmentView( + selectedTreatments: viewModel.selectedTreatments, + removeTreatment: viewModel.removeTreatment(_:) + ) } - Spacer() - .frame(height: 40.adjustedH) - LazyVGrid(columns: columns, spacing: 12) { - ForEach(TreatmentCategory.allCases, id: \.id) { catagory in - SelectionChip( - title: catagory.title, - isSelected: Binding( - get: { - viewModel.treatmentCatagory == catagory - }, - set: { - isSelected in - guard isSelected else { - return - } - viewModel.treatmentCatagory = catagory - } - ) - ) - - } + + CherrishButton( + title: "다음", + type: .large, + state: .constant(viewModel.canProceed ? .active : .normal), + leadingIcon: nil, + trailingIcon: nil + ) { + viewModel.next() } + .padding(.horizontal, 25.adjustedW) } } } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift index 38b68709..6e2f33c8 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Feature/Calendar/Treatment/Treatment/ViewModel/TreatmentViewModel.swift @@ -10,7 +10,6 @@ import Combine final class TreatmentViewModel: ObservableObject{ @Published var state: Treatment = .targetDdaySetting - var step: Int { state.rawValue } @Published var dDay: DdayState? @Published var year: String = "" @Published var month: String = "" @@ -20,8 +19,22 @@ final class TreatmentViewModel: ObservableObject{ @Published var searchText = "" @Published var filteredTreatments: [TreatmentEntity] = [] + + + var step: Int { state.rawValue } + var cancellables = Set() + var today: (year: Int, month: Int, day: Int) { + let calendar = Calendar.current + let now = Date() + return ( + calendar.component(.year, from: now), + calendar.component(.month, from: now), + calendar.component(.day, from: now) + ) + } + init() { filteredTreatments = treatments setupSearch() @@ -45,6 +58,10 @@ final class TreatmentViewModel: ObservableObject{ state.previous() } + func toInt(_ value: String) -> Int { + Int(value) ?? 0 + } + func isDateTextFieldNotEmpty() -> Bool { guard !year.isEmpty, !month.isEmpty, !day.isEmpty else { return false diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishPicker.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishPicker.swift index 7bad6e24..2b27f249 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishPicker.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/CherrishPicker.swift @@ -79,6 +79,10 @@ extension PickerViewRepresentable { } func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { + pickerView.subviews.forEach { subview in + subview.backgroundColor = .clear + } + let label = (view as? UILabel) ?? UILabel() label.text = "\(parent.range.lowerBound + row)" label.textAlignment = .center diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/Treatment/TreatmentRowView.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/Treatment/TreatmentRowView.swift index 3bd3582f..242f2a01 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/Treatment/TreatmentRowView.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Components/Treatment/TreatmentRowView.swift @@ -62,14 +62,14 @@ private struct TreatmentSummaryView: View { color: .gray800 ) Spacer() - .frame(width: 12) + .frame(width: 12.adjustedW) TypographyText( "|", style: .body2_r_13, color: .gray600 ) Spacer() - .frame(width: 12) + .frame(width: 12.adjustedW) TypographyText( "다운타임*\(treatmentEntity.downtimeMin)-\(treatmentEntity.downtimeMax)일", style: .body1_r_14, @@ -82,9 +82,10 @@ private struct TreatmentSummaryView: View { action() } } - .padding(.leading, 17) - .padding(.trailing,10) - .padding(.vertical, 7) + .frame(height: 20.adjustedH) + .padding(.leading, 17.adjustedW) + .padding(.trailing, 10.adjustedW) + .padding(.vertical, 7.adjustedH) .background( RoundedRectangle(cornerRadius: 6) .fill(.gray200) @@ -119,6 +120,7 @@ private struct TreatmentCheckBoxView: View { HStack { Text(treatmentEntity.name) .typography(.title1_sb_18) + .frame(height: 24.adjustedH) Spacer() if isCompletedView{ Image(isCompleted ? .checkCircular : .checkCircularGray) @@ -128,6 +130,7 @@ private struct TreatmentCheckBoxView: View { Text(treatmentEntity.benefits.joinedWithSeparator()) .typography(.body3_r_12) .foregroundStyle(.gray700) + .frame(height: 18.adjustedH) Spacer() } @@ -181,6 +184,7 @@ private struct DownTimeLabel: View { style: .body2_r_13, color: .gray700 ) + .frame(height: 18.adjustedH) } } } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date+.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date+.swift index 3fb9a4cd..07f365eb 100644 --- a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date+.swift +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/Date+.swift @@ -25,4 +25,18 @@ extension Date { return formatter.string(from: self) } + + static func daysBetween( + from: (year: Int, month: Int, day: Int), + to: (year: Int, month: Int, day: Int) + ) -> Int? { + let calendar = Calendar.current + + guard let fromDate = calendar.date(from: DateComponents(year: from.year, month: from.month, day: from.day)), + let toDate = calendar.date(from: DateComponents(year: to.year, month: to.month, day: to.day)) else { + return nil + } + + return abs(calendar.dateComponents([.day], from: fromDate, to: toDate).day ?? 0) + } } diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/PresentationDetent+.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/PresentationDetent+.swift new file mode 100644 index 00000000..962c7738 --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/PresentationDetent+.swift @@ -0,0 +1,13 @@ +// +// PresentationDetent+.swift +// Cherrish-iOS +// +// Created by 어재선 on 1/19/26. +// + +import SwiftUI + +extension PresentationDetent { + static let extraLarge = Self.fraction(0.675) +} + diff --git a/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+HideKeyboard.swift b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+HideKeyboard.swift new file mode 100644 index 00000000..c20751aa --- /dev/null +++ b/Cherrish-iOS/Cherrish-iOS/Presentation/Global/Extension/View+HideKeyboard.swift @@ -0,0 +1,15 @@ +// +// View+HideKeyboard.swift +// Cherrish-iOS +// +// Created by 어재선 on 1/19/26. +// + +import SwiftUI + +extension View { + + func hideKeyboard() { + UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +}