cleancocoa/timeline-ui
A SwiftUI component library for displaying calendar timeline views in iOS apps. Show daily schedules on an hour grid with automatic layout for overlapping events.
Installation
Add TimelineUI to your project using Swift Package Manager:
dependencies: [
.package(url: "https://codeberg.org/ctietze/timeline-ui.git", from: "1.0.0")
]Note: This project is canonically hosted on Codeberg. GitHub is a mirror.
Quick Start
import TimelineUI
struct ScheduleView: View {
let events: [TimelineItem] = [
TimelineItem(
title: "Team Meeting",
startDate: Date(),
endDate: Date().addingTimeInterval(3600),
color: .blue,
location: "Conference Room A"
),
TimelineItem(
title: "Lunch",
startDate: Date().addingTimeInterval(7200),
endDate: Date().addingTimeInterval(10800),
color: .green
)
]
var body: some View {
DayTimelineView(items: events)
}
}Components
DayTimelineView
Full day timeline that automatically expands to fill available space. Shows hour grid lines with events positioned by time.
DayTimelineView(items: [TimelineItem])CompactTimelineView
Compact timeline window, ideal for widgets or previews.
CompactTimelineView(items: [TimelineItem]) // Fills available height
CompactTimelineView(items: [TimelineItem], heightMode: .flexible) // Same as above
CompactTimelineView(items: [TimelineItem], heightMode: .fixed(hours: 2)) // Fixed 2-hour windowExpandable Timeline
Tap a compact timeline to expand into a full day view with a smooth matched geometry animation:
[Expandable timeline animation]
@State private var isExpanded = false
@Namespace private var timelineNamespace
// Compact view with tap-to-expand
CompactTimelineView(items: items, heightMode: .fixed(hours: 2))
.timelineTransition(in: timelineNamespace)
.onTapGesture {
withAnimation(.spring(duration: 0.4, bounce: 0.15)) {
isExpanded = true
}
}
// Apply expanded overlay at root level
.overlay {
if isExpanded {
ExpandedTimelineContent(items: items) { headerView }
.timelineTransition(in: timelineNamespace)
}
}Access Restricted View
Show a blurred timeline with a permission prompt when calendar access hasn't been granted:
CompactTimelineView(items: [])
.accessRestricted(!hasCalendarAccess) {
AccessPromptView.calendar(style: .compact) {
await requestCalendarAccess()
}
}Customize the prompt text:
AccessPromptView.calendar(
title: "Check for conflicts",
message: "See if this time works with your schedule",
buttonLabel: "Enable Calendar"
) { await requestAccess() }Or use ViewBuilders for full control over icon and button:
AccessPromptView(
title: "Connect Calendar",
message: "Show your events on the timeline",
icon: { Image(systemName: "calendar.badge.plus") },
buttonLabel: { Label("Allow Access", systemImage: "checkmark.circle") }
) { await requestAccess() }Screenshots
| | Light | Dark | |---|:-----:|:----:| | Compact - Focused 2-3 hour window | [Compact light] | [Compact dark] | | Day - Full schedule with hour grid | [Day light] | [Day dark] | | Overlapping - Events arranged side-by-side | [Overlapping light] | [Overlapping dark] | | Many events - Handles busy schedules gracefully | [Many light] | [Many dark] | | Access restricted - Blurred with permission prompt | [Restricted light] | [Restricted dark] |
Requirements
- iOS 26+
- Swift 6.2+
License
MIT
Package Metadata
Repository: cleancocoa/timeline-ui
Default branch: main
README: README.md