SwiftUI 2.0最令人期待的功能之一是可以替代UICollectionView的SwiftUI寞钥。UICollectionView為我們提供了一種構(gòu)建超級自定義界面(如日歷或照片網(wǎng)格)的簡便方法询枚。但是今天僅使用純SwiftUI來創(chuàng)建個(gè)的日歷視圖。
本文價(jià)值與收獲
看完本文后绒北,您將能夠作出下面的界面
需求
首先我們先表述一下日歷視圖需求戳稽。日歷視圖是一個(gè)容器視圖,它使用基于日歷的網(wǎng)格顯示其子視圖痹仙。這些是我對日歷視圖的要求
- 它應(yīng)該垂直滾動數(shù)月是尔。
- 它應(yīng)考慮用戶在設(shè)備上具有的日歷設(shè)置。
- 它應(yīng)該提供一個(gè)不錯的API來構(gòu)建自定義日間單元开仰。
CalendarView代碼
好的拟枚,現(xiàn)在我們有了組件的要求列表。我們可以開始編碼了众弓。
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結(jié)構(gòu),該結(jié)構(gòu)接受需要在其中顯示日期的日期間隔和用于構(gòu)建日單元格的@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)
}
}
}
}
}
現(xiàn)在脚乡,我們可以顯示一個(gè)以垂直堆棧為根視圖的滾動視圖。我們使用日歷來生成用戶提供給我們的日期間隔中的所有月份傻粘。如您所見每窖,我們使用SwiftUI放入環(huán)境中的系統(tǒng)日歷。用戶在系統(tǒng)設(shè)置中更改日歷后弦悉,SwiftUI還將更新視圖窒典。
創(chuàng)建MonthView
如您所見,我決定創(chuàng)建單獨(dú)的MonthView結(jié)構(gòu)稽莉,該結(jié)構(gòu)在我們的日歷視圖中顯示一個(gè)月瀑志。 SwiftUI允許我們組合多個(gè)視圖以構(gòu)建出色的視圖層次結(jié)構(gòu)。我想指出,我在應(yīng)用程序的其他部分重用了MonthView來呈現(xiàn)日歷預(yù)覽。
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結(jié)構(gòu)是一個(gè)純視圖烈评,使用系統(tǒng)提供的日歷生成周跷车,并使用具有周視圖集合的垂直堆棧呈現(xiàn)周。
創(chuàng)建周視圖
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()
}
}
}
}
}
}
周視圖是我的日歷視圖的最新部分。它還使用系統(tǒng)提供的日歷在給定的一周內(nèi)生成日期,并通過應(yīng)用傳遞的@ViewBuilder閉包來每天構(gòu)建視圖常侦,從而使用水平堆棧進(jìn)行渲染.
主視圖
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)))
)
}
}
}
在上面的示例中,您將看到我們?nèi)绾问褂萌諝v視圖贬媒。我希望您注意構(gòu)建日視圖的方式聋亡。我稱之為模板視圖。我創(chuàng)建具有最大寬度的模板值的隱藏文本际乘。然后坡倔,我將實(shí)際內(nèi)容顯示為模板視圖的疊加層。這種方法使我可以看到相同大小的日視圖。我們應(yīng)避免frame修飾符罪塔,因?yàn)橥ㄟ^frame限制空間將會破壞動態(tài)類型支持投蝉。
總結(jié)
SwiftUI具有如此友好的布局系統(tǒng),我們可以用來構(gòu)建出色的視圖征堪。建議大家能用原生就優(yōu)先原生墓拜。