You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

技术咨询:如何将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 14:19:09