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

Swift中FSCalendar范围选择自定义单元格样式实现求助

解决FSCalendar范围选中等问题的实现方案

1. 完善自定义RangePickerCell的状态逻辑

确保你的RangePickerCell继承自FSCalendarCell,并实现起始、结束、中间日期的UI区分逻辑,同时处理Cell复用的状态重置:

class RangePickerCell: FSCalendarCell {
    // 中间日期的阴影填充图层
    lazy var middleRangeLayer: CAShapeLayer = {
        let layer = CAShapeLayer()
        layer.fillColor = UIColor.systemGray5.cgColor
        self.contentView.layer.insertSublayer(layer, below: self.titleLabel.layer)
        return layer
    }()
    
    override func layoutSubviews() {
        super.layoutSubviews()
        middleRangeLayer.frame = CGRect(x: 0, y: bounds.height/4, width: bounds.width, height: bounds.height/2)
    }
    
    // 重置状态,避免复用残留
    override func prepareForReuse() {
        super.prepareForReuse()
        middleRangeLayer.isHidden = true
        backgroundColor = .clear
        layer.cornerRadius = 0
        layer.maskedCorners = []
    }
    
    // 配置起始日期样式
    func setupAsStartDate() {
        middleRangeLayer.isHidden = true
        backgroundColor = UIColor.systemBlue
        layer.cornerRadius = bounds.height/2
        layer.maskedCorners = [.layerMinXMinYCorner, .layerMinXMaxYCorner]
    }
    
    // 配置结束日期样式
    func setupAsEndDate() {
        middleRangeLayer.isHidden = true
        backgroundColor = UIColor.systemBlue
        layer.cornerRadius = bounds.height/2
        layer.maskedCorners = [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
    }
    
    // 配置中间日期样式
    func setupAsMiddleDate() {
        backgroundColor = .clear
        middleRangeLayer.isHidden = false
    }
    
    // 配置正常日期样式
    func setupAsNormal() {
        backgroundColor = .clear
        middleRangeLayer.isHidden = true
    }
}

2. 在CalendarViewController中注册并复用Cell

在视图加载阶段完成自定义Cell的注册,同时设置日历的基础配置:

override func viewDidLoad() {
    super.viewDidLoad()
    // 注册自定义Cell
    calendar.register(RangePickerCell.self, forCellReuseIdentifier: "RangePickerCell")
    // 开启多选(范围选择依赖此设置)
    calendar.allowsMultipleSelection = true
    calendar.scope = .month
    // 设置代理
    calendar.dataSource = self
    calendar.delegate = self
}

实现FSCalendarDataSource的Cell复用方法,每次复用都重新配置Cell状态:

func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
    let cell = calendar.dequeueReusableCell(withIdentifier: "RangePickerCell", for: date, at: position) as! RangePickerCell
    // 根据日期状态配置Cell
    configureCell(cell, for: date)
    return cell
}

3. 处理范围选择逻辑与状态更新

在ViewController中维护起始、结束日期变量,实现正确的范围选择逻辑,避免随机选中异常:

private var startDate: Date?
private var endDate: Date?

// 处理日期选中事件
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
    guard let start = startDate else {
        startDate = date
        return
    }
    
    if endDate == nil {
        // 确保结束日期不早于起始日期
        if date >= start {
            endDate = date
            // 选中中间所有日期
            let intervalDates = getDatesBetween(start, end: date)
            intervalDates.forEach { calendar.select($0) }
        } else {
            // 若选了更早的日期,重置起始/结束
            calendar.deselect(start)
            endDate = start
            startDate = date
        }
    } else {
        // 重新选择范围,清空之前的选中状态
        calendar.deselect(startDate!)
        calendar.deselect(endDate!)
        getDatesBetween(startDate!, end: endDate!).forEach { calendar.deselect($0) }
        
        startDate = date
        endDate = nil
    }
    calendar.reloadData()
}

// 辅助方法:获取两个日期之间的所有日期
private func getDatesBetween(_ start: Date, end: Date) -> [Date] {
    var dates: [Date] = []
    var current = start
    let calendar = Calendar.current
    while current <= end {
        dates.append(current)
        current = calendar.date(byAdding: .day, value: 1, to: current)!
    }
    return dates
}

实现Cell状态配置方法,根据日期在范围中的位置设置对应样式:

private func configureCell(_ cell: RangePickerCell, for date: Date) {
    guard let start = startDate, let end = endDate else {
        cell.setupAsNormal()
        return
    }
    
    if date == start {
        cell.setupAsStartDate()
    } else if date == end {
        cell.setupAsEndDate()
    } else if date > start && date < end {
        cell.setupAsMiddleDate()
    } else {
        cell.setupAsNormal()
    }
}

4. 修复随机日期选中异常

这个问题核心是Cell复用导致的状态残留,确保:

  • 在RangePickerCell的prepareForReuse方法中重置所有UI状态(背景色、图层显示、圆角等)
  • 每次复用Cell时,都通过configureCell方法重新计算并设置状态,不依赖Cell的旧状态
  • 范围选择逻辑中,仅选中起始、结束及中间的日期,通过getDatesBetween方法确保日期范围的准确性,避免跨月份的误选

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 17:52:56