技术咨询:如何将SwiftUI日历设置为指定图片中的样式?
实现自定义样式的SwiftUI日历(带日历图标关联)
嘿,我帮你整了一套能实现你要的那种带日历图标的SwiftUI日历方案,从核心逻辑到样式细节一步步来,你直接就能复用:
1. 先搞定日期逻辑基础
首先得用系统Calendar类处理月份切换、日期范围这些核心逻辑,先整个视图模型管理状态:
class CalendarViewModel: ObservableObject { @Published var currentMonth: Date = Date() @Published var selectedDate: Date? = Date() // 获取当前月份的所有日期(含前后月份的填充日期,保证6行日历布局) func getMonthDates() -> [Date] { let calendar = Calendar.current guard let monthStart = calendar.date(from: calendar.dateComponents([.year, .month], from: currentMonth)), let monthEnd = calendar.date(byAdding: .month, value: 1, to: monthStart), let prevMonthEnd = calendar.date(byAdding: .day, value: -1, to: monthStart) else { return [] } let prevMonthDays = calendar.component(.weekday, from: monthStart) - 1 let prevMonthDates = (0..<prevMonthDays).compactMap { calendar.date(byAdding: .day, value: -$0-1, to: monthStart) } let currentMonthDates = calendar.generateDates(from: monthStart, to: monthEnd, matching: DateComponents(hour: 0)) let nextMonthDays = 42 - prevMonthDates.count - currentMonthDates.count return prevMonthDates.reversed() + currentMonthDates + (0..<nextMonthDays).compactMap { calendar.date(byAdding: .day, value: $0+1, to: prevMonthEnd) } } // 获取星期表头(大写缩写) func getWeekdays() -> [String] { Calendar.current.shortWeekdaySymbols.map { $0.uppercased() } } } // 扩展Calendar,方便生成月份日期数组 extension Calendar { func generateDates(from start: Date, to end: Date, matching components: DateComponents) -> [Date] { var dates: [Date] = [] var currentDate = start while currentDate < end { if let date = nextDate(after: currentDate, matching: components, matchingPolicy: .nextTime) { dates.append(date) currentDate = date } else { break } } return dates } }
2. 核心:自定义日期单元格(带日历图标)
这部分是实现你要的样式的关键,我们做一个DateCell视图,专门处理选中/未选中状态的样式,以及日历图标的显示:
struct DateCell: View { let date: Date let isCurrentMonth: Bool let isSelected: Bool let calendar = Calendar.current var body: some View { VStack(spacing: 2) { if isSelected { // 选中状态:显示日历图标+日期,搭配高亮背景 Image(systemName: "calendar") .font(.caption) .foregroundColor(.white) Text("\(calendar.component(.day, from: date))") .font(.headline) .foregroundColor(.white) } else { // 未选中状态:只显示日期,区分当前月/非当前月的颜色 Text("\(calendar.component(.day, from: date))") .font(.subheadline) .foregroundColor(isCurrentMonth ? .primary : .secondary) } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background( isSelected ? RoundedRectangle(cornerRadius: 8) .fill(Color.blue) : RoundedRectangle(cornerRadius: 8) .fill(Color.clear) ) } }
3. 组装完整日历视图
把上面的组件整合起来,加上月份切换按钮、星期表头,形成完整的日历:
struct CustomCalendarView: View { @StateObject var viewModel = CalendarViewModel() let calendar = Calendar.current var body: some View { VStack(spacing: 16) { // 月份标题+左右切换按钮 HStack { Button(action: { viewModel.currentMonth = calendar.date(byAdding: .month, value: -1, to: viewModel.currentMonth)! }) { Image(systemName: "chevron.left") .foregroundColor(.primary) } Text(viewModel.currentMonth.formatted(.dateTime.month(.wide).year())) .font(.title) .fontWeight(.semibold) Button(action: { viewModel.currentMonth = calendar.date(byAdding: .month, value: 1, to: viewModel.currentMonth)! }) { Image(systemName: "chevron.right") .foregroundColor(.primary) } } // 星期表头 HStack { ForEach(viewModel.getWeekdays(), id: \.self) { weekday in Text(weekday) .font(.caption) .fontWeight(.medium) .frame(maxWidth: .infinity) } } // 日期网格(固定7列,自动6行) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 7), spacing: 8) { ForEach(viewModel.getMonthDates(), id: \.self) { date in let isCurrentMonth = calendar.isDate(date, equalTo: viewModel.currentMonth, toGranularity: .month) let isSelected = calendar.isDate(date, equalTo: viewModel.selectedDate!, toGranularity: .day) DateCell(date: date, isCurrentMonth: isCurrentMonth, isSelected: isSelected) .onTapGesture { viewModel.selectedDate = date } } } } .padding() .background(Color(.systemBackground)) .cornerRadius(12) .shadow(radius: 2) } } // 预览用 struct CustomCalendarView_Previews: PreviewProvider { static var previews: some View { CustomCalendarView() } }
4. 按需调整细节
你可以根据目标图标的样式灵活修改:
- 替换
Image(systemName: "calendar")为你的自定义图标(比如Image("your-custom-cal-icon")) - 调整选中状态的背景色、圆角半径、图标/字体大小
- 修改日期字体、颜色,适配你的设计风格
- 调整网格间距、内边距,让整体布局更贴合需求
内容的提问来源于stack exchange,提问作者JIANG
相关产品推荐
相关产品推荐

