SwiftUI 2.0最令人期待的功能之一是可以替代UICollectionView的SwiftUI。UICollectionView为我们提供了一种构建超级自定义界面(如日历或照片网格)的简便方法。但是今天仅使用纯SwiftUI来创建个的日历视图。
本文价值与收获
看完本文后,您将能够作出下面的界面
Jietu20200512-064349.gif
需求
首先我们先表述一下日历视图需求。日历视图是一个容器视图,它使用基于日历的网格显示其子视图。这些是我对日历视图的要求
- 它应该垂直滚动数月。
- 它应考虑用户在设备上具有的日历设置。
- 它应该提供一个不错的API来构建自定义日间单元。
CalendarView代码
好的,现在我们有了组件的要求列表。我们可以开始编码了。
1 2 3 4 5 6 7 8 9 10 11 12 | 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闭包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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来呈现日历预览。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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结构是一个纯视图,使用系统提供的日历生成周,并使用具有周视图集合的垂直堆栈呈现周。
创建周视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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闭包来每天构建视图,从而使用水平堆栈进行渲染.
主视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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具有如此友好的布局系统,我们可以用来构建出色的视图。建议大家能用原生就优先原生。

