SwiftUI 如何构建日历App (grid网格化显示)

iCloudEnd  IP属地: 山西
0.829字数 855阅读 3,249

SwiftUI 2.0最令人期待的功能之一是可以替代UICollectionView的SwiftUI。UICollectionView为我们提供了一种构建超级自定义界面(如日历或照片网格)的简便方法。但是今天仅使用纯SwiftUI来创建个的日历视图。

本文价值与收获

看完本文后,您将能够作出下面的界面

Jietu20200512-064329@2x.jpg
Jietu20200512-064349.gif

需求

首先我们先表述一下日历视图需求。日历视图是一个容器视图,它使用基于日历的网格显示其子视图。这些是我对日历视图的要求

  • 它应该垂直滚动数月。
  • 它应考虑用户在设备上具有的日历设置。
  • 它应该提供一个不错的API来构建自定义日间单元。

CalendarView代码

好的,现在我们有了组件的要求列表。我们可以开始编码了。

struct CalendarView<DateView>: View where DateView: View {
    let interval: DateInterval
    let content: (Date) -> DateView

    init(
        interval: DateInterval,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.interval = interval
        self.content = content
    }
}

在这里,我们定义了CalendarView结构,该结构接受需要在其中显示日期的日期间隔和用于构建日单元格的@ViewBuilder闭包。

struct CalendarView<DateView>: View where DateView: View {
    @Environment(\.calendar) var calendar

    let interval: DateInterval
    let content: (Date) -> DateView

    init(
        interval: DateInterval,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.interval = interval
        self.content = content
    }

    private var months: [Date] {
        calendar.generateDates(
            inside: interval,
            matching: DateComponents(day: 1, hour: 0, minute: 0, second: 0)
        )
    }

    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
            VStack {
                ForEach(months, id: \.self) { month in
                    MonthView(month: month, content: self.content)
                }
            }
        }
    }
}

现在,我们可以显示一个以垂直堆栈为根视图的滚动视图。我们使用日历来生成用户提供给我们的日期间隔中的所有月份。如您所见,我们使用SwiftUI放入环境中的系统日历。用户在系统设置中更改日历后,SwiftUI还将更新视图。

创建MonthView

如您所见,我决定创建单独的MonthView结构,该结构在我们的日历视图中显示一个月。 SwiftUI允许我们组合多个视图以构建出色的视图层次结构。我想指出,我在应用程序的其他部分重用了MonthView来呈现日历预览。

struct MonthView<DateView>: View where DateView: View {
    @Environment(\.calendar) var calendar

    let month: Date
    let content: (Date) -> DateView

    init(
        month: Date,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.month = month
        self.content = content
    }

    private var weeks: [Date] {
        guard
            let monthInterval = calendar.dateInterval(of: .month, for: month)
            else { return [] }
        return calendar.generateDates(
            inside: monthInterval,
            matching: DateComponents(hour: 0, minute: 0, second: 0, weekday: 1)
        )
    }

    var body: some View {
        VStack {
            ForEach(weeks, id: \.self) { week in
                WeekView(week: week, content: self.content)
            }
        }
    }
}

如您在上面的代码示例中所看到的,MonthView结构是一个纯视图,使用系统提供的日历生成周,并使用具有周视图集合的垂直堆栈呈现周。

创建周视图

struct WeekView<DateView>: View where DateView: View {
    @Environment(\.calendar) var calendar

    let week: Date
    let content: (Date) -> DateView

    init(
        week: Date,
        @ViewBuilder content: @escaping (Date) -> DateView
    ) {
        self.week = week
        self.content = content
    }

    private var days: [Date] {
        guard
            let weekInterval = calendar.dateInterval(of: .weekOfYear, for: week)
            else { return [] }
        return calendar.generateDates(
            inside: weekInterval,
            matching: DateComponents(hour: 0, minute: 0, second: 0)
        )
    }

    var body: some View {
        HStack {
            ForEach(days, id: \.self) { date in
                HStack {
                    if self.calendar.isDate(self.week, equalTo: date, toGranularity: .month) {
                        self.content(date)
                    } else {
                        self.content(date).hidden()
                    }
                }
            }
        }
    }
}

周视图是我的日历视图的最新部分。它还使用系统提供的日历在给定的一周内生成日期,并通过应用传递的@ViewBuilder闭包来每天构建视图,从而使用水平堆栈进行渲染.

主视图

struct RootView: View {
    @Environment(\.calendar) var calendar

    private var year: DateInterval {
        calendar.dateInterval(of: .year, for: Date())!
    }

    var body: some View {
        CalendarView(interval: year) { date in
            Text("30")
                .hidden()
                .padding(8)
                .background(Color.blue)
                .clipShape(Circle())
                .padding(.vertical, 4)
                .overlay(
                    Text(String(self.calendar.component(.day, from: date)))
                )
        }
    }
}

在上面的示例中,您将看到我们如何使用日历视图。我希望您注意构建日视图的方式。我称之为模板视图。我创建具有最大宽度的模板值的隐藏文本。然后,我将实际内容显示为模板视图的叠加层。这种方法使我可以看到相同大小的日视图。我们应避免frame修饰符,因为通过frame限制空间将会破坏动态类型支持。

总结

SwiftUI具有如此友好的布局系统,我们可以用来构建出色的视图。建议大家能用原生就优先原生。

项目源码

还有 11% 的精彩内容
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
支付 ¥1.59 继续阅读
3人点赞
更多精彩内容,就在简书APP
"小礼物走一走,来简书关注我"
还没有人赞赏,支持一下
总资产1.1W共写了137.6W字获得3,937个赞共2,602个粉丝