SwiftUI2.0 实现农历日历

2,720 阅读3分钟

最近发现Xcode12的SwiftUI模板变了,适配其他端设备变得更加简单,所以就试着写了一个日历功能,主要参考: www.jianshu.com/p/26c550fc7…

DateInterval 这个时间间隔用的最多,主要是年间隔,月间隔,周间隔。 比如:

///获取当前时间所在年的间隔,即1月1日到12月31日
calendar.dateInterval(of: .year, for: Date())!

LazyVGrid网格视图。用于每月视图布局

///生成网格
                LazyVGrid(columns: Array(repeating: GridItem(spacing: 2, alignment: .center), count: 7)) {
                    ///枚举每个月
                    ForEach(months, id: \.self) { month in
                        ///以每月为一个Section,添加月份
                        Section(header: Text("1月")) {
                            ///添加日
                            ForEach(days(for: month), id: \.self) { date in
                                ///在当月就添加
                                if calendar.isDate(date, equalTo: month, toGranularity: .month) {
                                    Text("1")
                                }
                            }
                        }
  
                    }
                }

完整的列表滚动式图,这里边我标记了下Section的ID不想要滚动到指定位置的话可以不加

        ///添加到可以滚动
        ScrollView(.vertical, showsIndicators: false){
            ///添加滚动监听
            ScrollViewReader { (proxy: ScrollViewProxy) in
                ///生成网格
                LazyVGrid(columns: Array(repeating: GridItem(spacing: 2, alignment: .center), count: 7)) {
                    ///枚举每个月
                    ForEach(months, id: \.self) { month in
                        ///以每月为一个Section,添加月份
                        Section(header: header(for: month)) {
                            ///添加日
                            ForEach(days(for: month), id: \.self) { date in
                                ///如果不在当月就隐藏
                                if calendar.isDate(date, equalTo: month, toGranularity: .month) {
                                    content(date).id(date)
                                } else {
                                    content(date).hidden()
                                }
                            }
                        }
                        .id(sectionID(for: month))///给每个月创建ID,方便进行滚动标记
                    }
                }
                .onAppear(){
                    ///当View展示的时候直接滚动到标记好的月份
                    proxy.scrollTo(scroolSectionID() )
                }
            }
        }

获取每个月日期的数组,这里就开始用月间隔、周间隔了

///获取每个月,网格范围内的起始结束日期数组
    private func days(for month: Date) -> [Date] {
        ///重点讲解
        ///先拿到月份间距,例如1号--31号
        guard let monthInterval = calendar.dateInterval(of: .month, for: month) else { return [] }
        ///先获取第一天所在周的周一到周日
        let monthFirstWeek = monthInterval.start.getWeekStartAndEnd()
        ///获取月最后一天所在周的周一到周日
        let monthLastWeek = monthInterval.end.getWeekStartAndEnd()
        ///然后根据月初所在周的周一为0号row 到月末所在周的周日为最后一个row生成数组
        return calendar.generateDates(
            inside: DateInterval(start: monthFirstWeek.start, end: monthLastWeek.end),
            matching: DateComponents(hour: 0, minute: 0, second: 0)
        )
    }
    

对Date进行一次扩展方便使用

extension Date {
    
    func getWeekDay() -> Int{
        let calendar = Calendar.current
        ///拿到现在的week数字
        let components = calendar.dateComponents([.weekday], from: self)
        return components.weekday!
    }
    
    ///获取当前Date所在周的周一到周日
    func getWeekStartAndEnd() -> DateInterval{
        var date = self
        ///因为一周的起始日是周日,周日已经算是下一周了
        ///如果是周日就到退回去两天
        if date.getWeekDay() == 1 {
            date = date.addingTimeInterval(-60 * 60 * 24 * 2)
        }
        ///使用处理后的日期拿到这一周的间距: 周日到周六
        let week = Calendar.current.dateInterval(of: .weekOfMonth, for: date)!
        ///处理一下周日加一天到周一
        let monday = week.start.addingTimeInterval(60 * 60 * 24)
        ///周六加一天到周日
        let sunday = week.end.addingTimeInterval(60 * 60 * 24)
        ///生成新的周一到周日的间距
        let interval = DateInterval(start: monday, end: sunday)
        return interval
    }
}

周视图,我们习惯的是周一是开头第一天,周日是最后一天

public struct CalendarWeek: View {
    public var body: some View {
        HStack{
            ForEach(1...7, id: \.self) { count in
                Text(Tool.getWeek(week: count))
                    .frame(maxWidth: .infinity)
            }
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
    }
}

到这个时候基本就能实现了一个符合我们使用习惯的日历,只是没有农历。

写一个静态方法处理一下农历的信息然后就可以了,如果还想添加放假信息,处理一下Day视图就可以了,就注释掉的那段

    ///获取农历, 节假日名
    static func getInfo(date: Date) -> String{
        //初始化农历日历
        let lunarCalendar = Calendar.init(identifier: .chinese)
        
        ///获得农历月
        let lunarMonth = DateFormatter()
        lunarMonth.locale = Locale(identifier: "zh_CN")
        lunarMonth.dateStyle = .medium
        lunarMonth.calendar = lunarCalendar
        lunarMonth.dateFormat = "MMM"
        
        let month = lunarMonth.string(from: date)
        
        //获得农历日
        let lunarDay = DateFormatter()
        lunarDay.locale = Locale(identifier: "zh_CN")
        lunarDay.dateStyle = .medium
        lunarDay.calendar = lunarCalendar
        lunarDay.dateFormat = "d"
        
        let day = lunarDay.string(from: date)

        //返回农历月
        if day == "初一" {
            return month
        }
        
        //返回农历日期
        return day
    }

为了方便阅读,我添加了大量的注释。 github.com/jackiehu/Lu…