I've been looking for a way in SwiftUI to get today's view only with hour interval's like iOS's Calendar app, if SwiftUI have default built in view for something like that then that's great, if not and there is a GitHub library to do the work then I'm okay with using that as well, I found online only a way to show a Year / Month view in SwiftUI:
import SwiftUI
fileprivate extension DateFormatter {
static var month: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM"
return formatter
}
static var monthAndYear: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
return formatter
}
}
fileprivate extension Calendar {
func generateDates(
inside interval: DateInterval,
matching components: DateComponents
) -> [Date] {
var dates: [Date] = []
dates.append(interval.start)
enumerateDates(
startingAfter: interval.start,
matching: components,
matchingPolicy: .nextTime
) { date, _, stop in
if let date = date {
if date < interval.end {
dates.append(date)
} else {
stop = true
}
}
}
return dates
}
}
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()
}
}
}
}
}
}
struct MonthView<DateView>: View where DateView: View {
@Environment(\.calendar) var calendar
let month: Date
let showHeader: Bool
let content: (Date) -> DateView
init(
month: Date,
showHeader: Bool = true,
@ViewBuilder content: @escaping (Date) -> DateView
) {
self.month = month
self.content = content
self.showHeader = showHeader
}
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: calendar.firstWeekday)
)
}
private var header: some View {
let component = calendar.component(.month, from: month)
let formatter = component == 1 ? DateFormatter.monthAndYear : .month
return Text(formatter.string(from: month))
.font(.title)
.padding()
}
var body: some View {
VStack {
if showHeader {
header
}
ForEach(weeks, id: \.self) { week in
WeekView(week: week, content: self.content)
}
}
}
}
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)
}
}
}
}
}
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)))
)
}
}
}
and I'm trying to get only today's view like this one (without the days of the month at the top):
UPDATE:
My progress so far to recreate the timeline view.
import SwiftUI
struct TimelineView: View {
let hours: [String] = ["12 AM","1 AM", "2 AM", "3 AM", "4 AM", "5 AM", "6 AM", "7 AM", "8 AM", "9 AM", "10 AM", "11 AM", "12 PM", "1 PM", "2 PM", "3 PM", "5 PM", "6 PM", "7 PM", "8 PM", "9 PM", "10 PM", "11 PM"]
var body: some View {
ScrollView {
HStack {
VStack(spacing: 24) {
ForEach(hours, id: \.self) { hour in
HStack {
Text(hour)
.font(Font.custom("Avenir", size: 9))
.frame(width: 28, height: 20, alignment: .center)
VStack {
Divider()
}
}
}
}
Spacer()
}
}
.padding(.top, 150)
.padding(.bottom, 32)
.padding(.horizontal, 16)
.background(backgroundPrimary)
.ignoresSafeArea()
}
}
I need help re structuring the array of time that I made our strings because I'm pretty sure its not the right way, plus I would like to make it Date type as I have events coming with time stamp and would need to compare the array of time I have with the event's time to see which block it will fill

