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

SwiftUI中DatePicker自动高亮起止日期区间的实现方案

SwiftUI 实现起止日期选择及区间高亮

核心思路

原生DatePicker和MultiDatePicker无法满足自动高亮区间的需求,因此我们可以自定义日期选择视图,通过以下步骤实现:

  1. 管理开始/结束日期的状态变量
  2. 生成当前及未来的可选择日期范围
  3. 自定义日期单元格,根据日期是否在区间内动态调整样式
  4. 处理日期点击逻辑,确保只能选择当前及未来日期,且结束日期不早于开始日期

实现代码

import SwiftUI

struct DateRangePickerView: View {
    private let currentDate = Calendar.current.startOfDay(for: Date())
    @State private var startDate: Date?
    @State private var endDate: Date?
    
    // 生成未来90天的日期列表,可根据需求调整范围
    private var displayDates: [Date] {
        var dates = [Date]()
        for dayOffset in 0..<90 {
            guard let date = Calendar.current.date(byAdding: .day, value: dayOffset, to: currentDate) else { continue }
            dates.append(date)
        }
        return dates
    }
    
    var body: some View {
        VStack {
            // 已选日期展示栏
            HStack {
                Text("开始日期: \(startDate?.formatted(date: .abbreviated, time: .omitted) ?? "未选择")")
                Spacer()
                Text("结束日期: \(endDate?.formatted(date: .abbreviated, time: .omitted) ?? "未选择")")
            }
            .padding()
            .font(.subheadline)
            
            // 日历网格布局
            LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 7)) {
                ForEach(displayDates, id: \.self) { date in
                    DateCell(date: date, startDate: startDate, endDate: endDate, currentDate: currentDate)
                        .onTapGesture {
                            handleDateSelection(date)
                        }
                }
            }
            .padding()
        }
    }
    
    // 日期点击逻辑处理
    private func handleDateSelection(_ date: Date) {
        let targetDay = Calendar.current.startOfDay(for: date)
        
        // 禁止选择过去日期
        guard targetDay >= currentDate else { return }
        
        switch (startDate, endDate) {
        case (nil, _):
            // 首次点击,设置为开始日期
            startDate = targetDay
            endDate = nil
        case (_, nil):
            // 已有开始日期,设置结束日期(确保不早于开始日期)
            if targetDay >= startDate! {
                endDate = targetDay
            } else {
                // 点击日期早于开始日期,交换起止
                endDate = startDate
                startDate = targetDay
            }
        case (_, _):
            // 已有完整区间,重新选择开始日期
            startDate = targetDay
            endDate = nil
        }
    }
}

// 自定义日期单元格组件
struct DateCell: View {
    let date: Date
    let startDate: Date?
    let endDate: Date?
    let currentDate: Date
    
    // 判断日期是否在起止区间内
    private var isInRange: Bool {
        guard let start = startDate, let end = endDate else { return false }
        let targetDay = Calendar.current.startOfDay(for: date)
        return targetDay >= start && targetDay <= end
    }
    
    // 判断日期是否为起止点
    private var isSelectedPoint: Bool {
        let targetDay = Calendar.current.startOfDay(for: date)
        return targetDay == startDate || targetDay == endDate
    }
    
    // 判断是否为过去日期
    private var isPast: Bool {
        let targetDay = Calendar.current.startOfDay(for: date)
        return targetDay < currentDate
    }
    
    var body: some View {
        let dayNumber = Calendar.current.component(.day, from: date)
        
        Text("\(dayNumber)")
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .aspectRatio(1, contentMode: .fit)
            .foregroundColor(isPast ? .gray : (isInRange ? .white : .primary))
            .background(
                Group {
                    if isSelectedPoint {
                        // 起止点用蓝色圆圈标记
                        Circle()
                            .fill(Color.blue)
                    } else if isInRange {
                        // 区间内日期用半透明蓝色背景填充
                        Rectangle()
                            .fill(Color.blue.opacity(0.3))
                    }
                }
            )
            .disabled(isPast)
            .font(.body)
    }
}

struct DateRangePickerView_Previews: PreviewProvider {
    static var previews: some View {
        DateRangePickerView()
    }
}

关键细节说明

  • 禁用过去日期:通过currentDate获取当日起始时间,判断点击的日期是否早于该时间,若是则禁用点击并置灰文字。
  • 区间高亮逻辑:自定义DateCell中通过isInRange判断日期是否在起止区间内,动态切换背景色和文字颜色;起止点用蓝色圆圈突出显示。
  • 日期选择逻辑:点击日期时,依次设置开始日期、结束日期,若点击早于已选开始日期的未来日期,自动交换起止顺序,确保区间有效性。
  • 可扩展性:可以调整displayDates的日期范围(比如改为未来180天),或者修改高亮样式(比如用彩色圆圈代替背景填充)。

内容的提问来源于stack exchange,提问作者tech_human

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 17:40:33