SwiftUI 2.0最令人期待的功能之一是可以替代UICollectionView的SwiftUI。UICollectionView为我们提供了一种构建超级自定义界面(如日历或照片网格)的简便方法。但是今天仅使用纯SwiftUI来创建个的日历视图。
本文价值与收获
看完本文后,您将能够作出下面的界面
需求
首先我们先表述一下日历视图需求。日历视图是一个容器视图,它使用基于日历的网格显示其子视图。这些是我对日历视图的要求
- 它应该垂直滚动数月。
- 它应考虑用户在设备上具有的日历设置。
- 它应该提供一个不错的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具有如此友好的布局系统,我们可以用来构建出色的视图。建议大家能用原生就优先原生。