SwiftUI无第三方包日历开发:日期与星期错位问题求助
SwiftUI日历日期错位问题修复
你的核心问题在于:直接从1号开始填充日历格子,没有考虑当月第一天对应的星期位置,导致1号固定落在第一个格子(周日),完全忽略了实际月份的起始星期。
修复思路
- 计算当月第一天是星期几,得出需要在1号前预留的空格子数量
- 在日历网格开头添加对应数量的空占位符
- 可选:在月末添加空占位符,让日历保持固定的6行高度(提升视觉一致性)
修改后的完整代码
struct CustomCalendarView: View { let calendar = Calendar.current let daysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] @State private var selectedDate: Date = Date() var body: some View { VStack { HStack { Button(action: { navigateToPreviousMonth() }) { Image(systemName: "chevron.left") .font(.headline) } .padding(.trailing, 8) Text(monthYearFormatter.string(from: selectedDate)) .font(.title) .padding(.bottom, 10) Button(action: { navigateToNextMonth() }) { Image(systemName: "chevron.right") .font(.headline) } .padding(.leading, 8) } HStack { ForEach(daysOfWeek, id: \.self) { day in Text(day) .font(.headline) .frame(maxWidth: .infinity) } } LazyVGrid(columns: Array(repeating: .init(.flexible()), count: 7)) { // 1. 添加前置空占位符,对齐星期位置 ForEach(0..<leadingEmptyDays(), id: \.self) { _ in Text("") .frame(maxWidth: .infinity) .padding(8) } // 2. 当月日期(原逻辑保留) ForEach(1...daysInMonth(date: selectedDate), id: \.self) { day in Text("\(day)") .font(.headline) .frame(maxWidth: .infinity) .padding(8) .background(dayBackgroundColor(for: day)) .foregroundColor(dayForegroundColor(for: day)) .clipShape(Circle()) .onTapGesture { selectDate(day: day) } } // 3. 可选:添加后置空占位符,保持日历6行高度 let totalCells = leadingEmptyDays() + daysInMonth(date: selectedDate) let trailingEmptyDays = (6 * 7) - totalCells if trailingEmptyDays > 0 { ForEach(0..<trailingEmptyDays, id: \.self) { _ in Text("") .frame(maxWidth: .infinity) .padding(8) } } } } .padding() } // 新增:获取当月第一天 private func firstDayOfMonth(date: Date) -> Date { let components = calendar.dateComponents([.year, .month], from: date) return calendar.date(from: components)! } // 新增:计算需要前置的空格子数量 private func leadingEmptyDays() -> Int { let firstDay = firstDayOfMonth(date: selectedDate) // Calendar.current的weekday从1(周日)开始,所以减1得到空位数 return calendar.component(.weekday, from: firstDay) - 1 } // 以下为原代码,无需修改 private func selectDate(day: Int) { var components = calendar.dateComponents([.year, .month, .day], from: selectedDate) components.day = day guard let updatedDate = calendar.date(from: components) else { return } selectedDate = updatedDate } private func daysInMonth(date: Date) -> Int { guard let range = calendar.range(of: .day, in: .month, for: date) else { return 0 } return range.count } private func dayBackgroundColor(for day: Int) -> Color { let isCurrentMonth = calendar.isDate(selectedDate, equalTo: Date(), toGranularity: .month) let isSelected = calendar.isDate(selectedDate, equalTo: date(withDay: day), toGranularity: .day) return isCurrentMonth && isSelected ? .blue : .clear } private func dayForegroundColor(for day: Int) -> Color { let isCurrentMonth = calendar.isDate(selectedDate, equalTo: Date(), toGranularity: .month) let isSelected = calendar.isDate(selectedDate, equalTo: date(withDay: day), toGranularity: .day) return isCurrentMonth && isSelected ? .white : .primary } private func date(withDay day: Int) -> Date { var components = calendar.dateComponents([.year, .month, .day], from: selectedDate) components.day = day return calendar.date(from: components) ?? Date() } private var monthYearFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "MMMM yyyy" return formatter }() private func navigateToPreviousMonth() { guard let previousMonth = calendar.date(byAdding: .month, value: -1, to: selectedDate) else { return } selectedDate = previousMonth } private func navigateToNextMonth() { guard let nextMonth = calendar.date(byAdding: .month, value: 1, to: selectedDate) else { return } selectedDate = nextMonth } }
关键逻辑说明
firstDayOfMonth:提取当前选中月份的第一天日期leadingEmptyDays:通过calendar.component(.weekday, from: firstDay)获取第一天的星期值(1=周日,2=周一…7=周六),减1后就是需要前置的空格子数量,确保1号能落在正确的星期列下- 后置空占位符是可选优化,让所有月份的日历都保持6行高度,避免切换月份时布局跳动
内容的提问来源于stack exchange,提问作者kitchen800
相关产品推荐
相关产品推荐

