I’m looking for advice on how to approach building a SwiftUI daily calendar list as I'm struggling to think how to even approach this.
This is the type of list I’m trying to create: image
I can create the VStack to loop over the hours in the day vertically and then the dividing lines too - which looks like the Apple Calendar app view: blank list
What I can’t figure out is how to align the calendar entries to the time, and then spanning it over multiple hours if it is long event.
I receive the event data from MSGraph with a startDate and endDate.
I don't require dragging ability of the events, only displaying them in the time slots.
My aim is to build it all myself, though I know there are probably packages that could help. It's just a small part of the app that I didn't want to overload with calendar functionalities when I wanted to have just a viewer.
This is where I am at, but dont know how to add the events into the list:
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()
}
}
I would add the single events over the hours grid as overlay or in an ZStack. You have to calculate an offset based on start date.
Here would be a sample implementation:
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
}