我正在寻找有关如何构建 SwiftUI 每日日历列表的建议,因为我正在努力思考如何解决这个问题。
这是我要创建的列表类型:image
我可以创建 VStack 以垂直循环一天中的时间,然后也是分界线 - 这看起来像 Apple Calendar 应用程序视图:空白列表
我想不通的是如何将日历条目与时间对齐,如果是长事件,则将其跨越多个小时。
我从 MSGraph 接收带有开始日期和结束日期的事件数据。
我不需要事件的拖动能力,只在时间段显示它们。
我的目标是自己构建它,尽管我知道可能有一些包可以提供帮助。这只是应用程序的一小部分,当我只想拥有一个查看器时,我不想让日历功能过载。
这是我所在的位置,但不知道如何将事件添加到列表中:
struct ContentView: View {
var body: some View {
ScrollView {
VStack(alignment: .leading) {
ForEach(8..<17) { index in
Text("\(index)")
Divider()
.offset(y: -22)
.padding(.leading, 30)
Spacer()
}
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
}
我会在小时网格上添加单个事件作为覆盖或在 ZStack 中。您必须根据开始日期计算偏移量。
这里是一个示例实现:
struct Event: Identifiable {
let id = UUID()
var startDate: Date
var endDate: Date
var title: String
}
struct ContentView: View {
let date: Date = dateFrom(9, 5, 2023)
let events: [Event] = [
Event(startDate: dateFrom(9,5,2023,7,0), endDate: dateFrom(9,5,2023,8,0), title: "Event 1"),
Event(startDate: dateFrom(9,5,2023,9,0), endDate: dateFrom(9,5,2023,10,0), title: "Event 2"),
Event(startDate: dateFrom(9,5,2023,11,0), endDate: dateFrom(9,5,2023,12,00), title: "Event 3"),
Event(startDate: dateFrom(9,5,2023,13,0), endDate: dateFrom(9,5,2023,14,45), title: "Event 4"),
Event(startDate: dateFrom(9,5,2023,15,0), endDate: dateFrom(9,5,2023,15,45), title: "Event 5"),
]
let hourHeight = 50.0
var body: some View {
VStack(alignment: .leading) {
// Date headline
HStack {
Text(date.formatted(.dateTime.day().month()))
.bold()
Text(date.formatted(.dateTime.year()))
}
.font(.title)
Text(date.formatted(.dateTime.weekday(.wide)))
ScrollView {
ZStack(alignment: .topLeading) {
VStack(alignment: .leading, spacing: 0) {
ForEach(7..<19) { hour in
HStack {
Text("\(hour)")
.font(.caption)
.frame(width: 20, alignment: .trailing)
Color.gray
.frame(height: 1)
}
.frame(height: hourHeight)
}
}
ForEach(events) { event in
eventCell(event)
}
}
}
}
.padding()
}
func eventCell(_ event: Event) -> some View {
let duration = event.endDate.timeIntervalSince(event.startDate)
let height = duration / 60 / 60 * hourHeight
let calendar = Calendar.current
let hour = calendar.component(.hour, from: event.startDate)
let minute = calendar.component(.minute, from: event.startDate)
let offset = Double(hour-7) * (hourHeight)
// + Double(minute)/60 ) * hourHeight
print(hour, minute, Double(hour-7) + Double(minute)/60 )
return VStack(alignment: .leading) {
Text(event.startDate.formatted(.dateTime.hour().minute()))
Text(event.title).bold()
}
.font(.caption)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(4)
.frame(height: height, alignment: .top)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(.teal).opacity(0.5)
)
.padding(.trailing, 30)
.offset(x: 30, y: offset + 24)
}
}
func dateFrom(_ day: Int, _ month: Int, _ year: Int, _ hour: Int = 0, _ minute: Int = 0) -> Date {
let calendar = Calendar.current
let dateComponents = DateComponents(year: year, month: month, day: day, hour: hour, minute: minute)
return calendar.date(from: dateComponents) ?? .now
}
将您的活动时间与您可以使用的
ForEach
时间相匹配DateComponents
.
///Hours in a day - First ForEach
let hourlyComponents: [DateComponents] = (0...23).map { hour in
DateComponents(hour: hour)
}
然后您可以按小时“分组”您的活动。
var eventsByHour: [DateComponents: [Event]]{
Dictionary(grouping: events) { element in
Calendar.current.dateComponents([.hour], from: element.startDate)
}
}
现在找到您可以使用的每小时事件。
ForEach(eventsByHour[hourlyComponent, default: []], id:\.id){ event in
要调整它的大小,您可以计算出
elapsedHours
并使用类似的东西
.frame(height: height * event.elapsedHours)
如果活动时间为 1.5 小时,尺寸将是高度的 1.5 倍。
关于分钟的调整,比如11:15,你可以得到分钟等值的小时,然后将其转化为小时,然后抵消。
.offset(y: height * event.startPosition)
15 分钟等于 0.25 小时。
我建议使用
Calendar
和 Measurement
来做所有的数学。
let components = Calendar.current.dateComponents([.hour, .minute], from: max(startDate, midnightStart), to: min(endDate, midnightEnd))
let minToHour = Measurement(value: Double(components.minute ?? 0), unit: UnitDuration.minutes).converted(to: .hours)
let elapsedHours = Double(components.hour ?? 0) + minToHour.value