前语

    日常开发中经常会遇到日期挑选,为了方便运用,简单封装了一个日历控件,在此抛砖引玉供我们参阅。

作用

iOS开发之自定义日历控件

功能

  • 支撑单选、区间
  • 支撑默许选中日期
  • 支撑约束月份
  • 支撑过去、当前、未来模式
  • 支撑frameAutoLayout

原理

层次结构运用UIStackView布局,UICollectionView复用,布景运用DecorationView

核心代码

日历核心代码在于核算每个月的日期,主要代码如下:

func month() -> [CLCalendarMonthModel] {
    func day(with date: Date, section: Int) -> [CLCalendarDayModel] {
        var newDate = date
        let tatalDay = newDate.daysInMonth
        let firstDay = max(0, newDate.weekday - 1)
        let columns = Int(ceil(CGFloat(tatalDay + firstDay) / CGFloat(weekArray.count)))
        var resultArray = [CLCalendarDayModel]()
        for column in 0 ..< columns {
            for weekDay in 0 ..< weekArray.count {
                if column == 0,
                   weekDay <= firstDay - 1
                {
                    resultArray.append(CLCalendarDayModel())
                } else {
                    let subtitle: String? = {
                        guard !newDate.isToday else { return "今日" }
                        guard config.isShowLunarCalendar else { return nil }
                        guard let index = chinese.dateComponents([.day], from: newDate).day else { return nil }
                        return chineseDayArray[index - 1]
                    }()
                    let type: CLCalendarDayModel.CLCalendarDayType = {
                        guard !newDate.isToday else { return .today }
                        guard newDate.compare(todayDate) == .orderedDescending else { return .future }
                        return .past
                    }()
                    let dayModel = CLCalendarDayModel(title: "\(newDate.day)", date: newDate, subtitle: subtitle, type: type)
                    resultArray.append(dayModel)
                    newDate = newDate + 1.days
                    if beginDate?.year == dayModel.date?.year,
                       beginDate?.month == dayModel.date?.month,
                       beginDate?.day == dayModel.date?.day
                    {
                        startIndexPath = .init(row: 0, section: section)
                    }
                    guard (resultArray.count - firstDay) != tatalDay else { break }
                }
            }
        }
        return resultArray
    }
    var resultArray = [CLCalendarMonthModel]()
    let month: Int = {
        var value = 0
        if config.type == .past {
            value = config.limitMonth - 1
        } else if config.type == .today {
            value = config.limitMonth / 2
        }
        return value
    }()
    let start = todayDate - month.months
    for i in 0 ..< config.limitMonth {
        let date = start + i.months
        let headerModel = CLCalendarMonthModel(headerText: date.format(with: "yyyy年MM月"),
                                               month: date.format(with: "MM"),
                                               daysArray: day(with: Date(year: date.year, month: date.month, day: 1), section: i))
        resultArray.append(headerModel)
    }
    return resultArray
}

基础装备

struct CLCalendarConfig {
    enum CLCalendarType {
        case past
        case today
        case future
    }
    enum CLSelectType {
        case single
        case area
    }
    struct CLTouchType: OptionSet {
        static let past = CLTouchType(rawValue: 1)
        static let today = CLTouchType(rawValue: 1 << 1)
        static let future = CLTouchType(rawValue: 1 << 2)
        let rawValue: Int64
        init(rawValue: Int64) {
            self.rawValue = rawValue
        }
    }
    struct CLColor {
        var background = "#ffffff".uiColor
        var topToolBackground = "#F4F4F4".uiColor
        var topToolText = "#444444".uiColor
        var topToolTextWeekend = "#3CCA79".uiColor
        var sectionBackgroundText = "f2f2f2".uiColor
        var selectStartBackground = "#4bce817f".uiColor
        var selectBackground = "#afe9c77f".uiColor
        var selectEndBackground = "#4bce817f".uiColor
        var todayText = "#32cd32".uiColor
        var titleText = "#555555".uiColor
        var subtitleText = "#555555".uiColor
        var selectTodayText = "#32cd32".uiColor
        var selectTitleText = "#ffffff".uiColor
        var selectSubtitleText = "#ffffff".uiColor
        var failureTitleText = "#a9a9a9".uiColor
        var failureSubtitleText = "#a9a9a9".uiColor
        var failureBackground = "#dcdcdc32".uiColor
    }
    var color = CLColor()
    var selectBegin: Date?
    var selectEnd: Date?
    var limitMonth = 12
    var type = CLCalendarType.today
    var selectType = CLSelectType.area
    var touchType: CLTouchType = [.today, .past]
    var isShowLunarCalendar = true
    var insetsLayoutMarginsFromSafeArea = true
    var headerHight = 50.0
}

总结

    定制化开发请自行参阅CLDemo修改 , 如果喜爱,欢迎star。

参阅资料

  1. DateToolsSwift

  2. UICollectionView: 装饰视图 Decoration View

  3. 运用UIStackView来简化iOS的界面布局