在 SwiftUI 中创建每日时间线日历

问题描述 投票:0回答:2

我正在寻找有关如何构建 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()
  }
}
swift swiftui calendar
2个回答
0
投票

我会在小时网格上添加单个事件作为覆盖或在 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
}

0
投票

将您的活动时间与您可以使用的

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
© www.soinside.com 2019 - 2024. All rights reserved.