Skip to main content
FioriSwiftUICore provides a set of timeline components for rendering chronological sequences of events: Timeline for full-detail items, TimelinePreviewItem for compact preview rows, and TimelineNowIndicator to mark the current time in the sequence.

Timeline

Timeline is a single timeline event item. It renders a vertical line and a timeline node on the axis, a timestamp stack to the left of the axis, and a main content stack (title, subtitle, attribute, status) to the right. A divider separates each item in a list.

Key parameters

title
any View / AttributedString
required
The event title.
timelineNode
any View / TimelineNodeType
required
The node shape rendered on the timeline axis. Use TimelineNodeType values: .open, .inProgress, .complete, .opaqueCircle, .transparentCircle.
timestamp
any View / AttributedString?
Primary timestamp displayed to the left of the axis.
secondaryTimestamp
any View / TextOrIcon?
Secondary timestamp (text or icon) shown below the primary timestamp.
subtitle
any View / AttributedString?
Subtitle text below the title.
attribute
any View / AttributedString?
Attribute string displayed beside or below the title.
status
any View / TextOrIcon?
Status label or icon.
substatus
any View / TextOrIcon?
Secondary status label or icon.
subAttribute
any View / AttributedString?
Secondary attribute string.
isPast
Bool
default:"false"
When true, the item is rendered with a dimmed past-event appearance.
isPresent
Bool
default:"false"
When true, the item is highlighted as the current event.
icon
any View / Image?
Optional icon displayed inside or beside the node.

Usage

// A completed past event
Timeline(
    timestamp: "06/21/24",
    secondaryTimestamp: .icon(Image(systemName: "sun.max")),
    timelineNode: .complete,
    title: "Order confirmed",
    subtitle: "PO-20240621-0042",
    attribute: "Warehouse A",
    status: .text("Done"),
    isPast: true
)

// An in-progress event
Timeline(
    timestamp: "06/22/24",
    timelineNode: .inProgress,
    title: "Shipped",
    subtitle: "DHL Express",
    attribute: "Tracking: 1Z999AA10123456784",
    status: .text("In transit"),
    isPresent: true
)

// A future open event
Timeline(
    timestamp: "06/24/24",
    timelineNode: .open,
    title: "Estimated delivery",
    status: .text("Pending")
)

Removing list separators

SwiftUI List adds a separator between every row by default. Apply .listRowSeparator(.hidden) to each Timeline item to remove the duplicate lines.
List(events) { event in
    Timeline(
        timestamp: event.date,
        timelineNode: event.nodeType,
        title: AttributedString(event.title),
        isPast: event.isPast
    )
    .listRowSeparator(.hidden)
}

TimelinePreviewItem

TimelinePreviewItem is a compact version of Timeline designed for use in preview cards or summaries. It shows only a title, a node, and an optional timestamp and icon.

Key parameters

title
any View / AttributedString
required
The event title.
timelineNode
any View / TimelineNodeType
required
The node type for this item.
nodeType
TimelineNodeType
required
Duplicate of timelineNode carried as a value for layout calculations.
timestamp
any View / AttributedString?
Optional timestamp label.
icon
any View / Image?
Optional icon beside the node.
isFuture
Bool
default:"false"
When true, renders the item with a future-event style.

Usage

VStack(alignment: .leading, spacing: 0) {
    TimelinePreviewItem(
        title: "Order confirmed",
        timelineNode: .complete,
        timestamp: "Jun 21",
        nodeType: .complete
    )
    TimelinePreviewItem(
        title: "Shipped",
        timelineNode: .inProgress,
        timestamp: "Jun 22",
        nodeType: .inProgress
    )
    TimelinePreviewItem(
        title: "Delivery",
        timelineNode: .open,
        timestamp: "Jun 24",
        nodeType: .open,
        isFuture: true
    )
}

TimelineNowIndicator

TimelineNowIndicator renders a horizontal marker that can be placed between Timeline items to show the current point in time. It uses NowIndicatorNode as its axis marker.
List {
    ForEach(pastEvents) { event in
        Timeline(
            timestamp: event.date,
            timelineNode: .complete,
            title: AttributedString(event.title),
            isPast: true
        )
        .listRowSeparator(.hidden)
    }

    // Current time marker
    TimelineNowIndicator()
        .listRowSeparator(.hidden)

    ForEach(futureEvents) { event in
        Timeline(
            timestamp: event.date,
            timelineNode: .open,
            title: AttributedString(event.title)
        )
        .listRowSeparator(.hidden)
    }
}

Complete example: delivery timeline

struct DeliveryTimelineView: View {
    let shipment: Shipment

    var body: some View {
        ScrollView {
            LazyVStack(alignment: .leading, spacing: 0) {
                ForEach(shipment.events) { event in
                    Timeline(
                        timestamp: event.formattedDate,
                        secondaryTimestamp: .text(event.formattedTime),
                        timelineNode: event.nodeType,
                        title: AttributedString(event.title),
                        subtitle: event.location.map { AttributedString($0) },
                        status: event.statusLabel.map { .text(AttributedString($0)) },
                        isPast: event.isCompleted,
                        isPresent: event.isCurrent
                    )
                }
            }
            .padding(.horizontal)
        }
    }
}

Build docs developers (and LLMs) love