Pattern · Lists and Feeds
NewActivity Timeline SwiftUI Block
A day-by-day log on a rail: status-tinted nodes, attachment chips, folded runs, a filter by kind and paging to earlier activity.
For order history, account activity or a project log. Events group under Today, Yesterday and dated headers, each with a node on a hairline rail that is tinted only for a real outcome, a title and time, a detail line and an optional attachment chip. Runs of one kind fold into one line that opens in place, a menu filters by kind, and Show earlier activity pages back with its working, failed and end states.
- SwiftUI
- iOS 17.0+
- activity-timeline
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- Day groups on a hairline rail with a node per event
- Nodes tinted only for real outcomes, like a failed build
- Attachment chips that open their file or link
- Runs of one kind fold into "4 more edits"
- A filter menu by kind and paging to earlier activity
02
Implementation notes
How it behaves, what it handles, and the decisions behind it.
The timeline opens with "All activity" and a Filter menu that narrows it to one kind of event. Events group by day under Today, Yesterday and dated headers, each day on a surface card with a hairline rail down its leading edge. Every event has a 28 point node on the rail, its symbol in a neutral circle, or tinted by its status when the status is a real outcome: a deploy that succeeded, a build that failed. Beside the node sit the title and its time, an optional detail line, and an optional attachment chip that opens its file or link. Three or more events of one kind in a row fold under the first into one line ("4 more edits") with a small dot on the rail; tapping it opens the run in place and Show fewer folds it again. At the end, Show earlier activity runs your onLoadMore closure, shows a spinner with "Loading earlier activity", and the older events land as new days; a thrown error shows under the button, which becomes Try again. When there is nothing older, the log ends with "That's everything". A filter with nothing to show and an empty log each have their own message, and passing nil shows grey shapes while the log loads.
Notes
ActivityTimeline(events:kinds:hasMore:onLoadMore:onOpenAttachment:)takes the events loaded so far and the kinds that describe them. It is a section: no navigation stack, background, padding or scroll view of its own, so place it in your ownScrollViewcolumn.ActivityTimeline.Kindhas anid, a pluraltitlefor the menu ("Deploys"), the railsymbol, theunita folded run is counted in ("edits", "updates" by default) andcollapses, which turns folding off for kinds that should always show every event.ActivityTimeline.sampleKindsis a project's deploys, edits, comments, files and members.ActivityTimeline.Eventholds anid, itskindid, thetitle, an optionaldetail, thedate, an optionalsymbolthat overrides the kind's, an optionalStatus(.success,.warning,.danger,.info) and an optionalAttachment(title,symbol,url). Leavestatusnil for events with no outcome, so the rail stays neutral.Event.samplesandEvent.earlierSamplesare the two pages from the preview.- Events can arrive in any order: the block sorts them newest first and groups them by the calendar day.
- Folding only joins events of one kind that sit next to each other in the same day, so the order of what happened is never changed.
hasMoreshows Show earlier activity whileonLoadMoreis set; false shows the end of the log.onLoadMoreisasync throws: append the older page toeventsand updatehasMorebefore it returns. A thrown error shows itserrorDescription(or "Couldn't load earlier activity.");CancellationErrorreturns to rest.- Attachment chips open
urlwithopenURL. PassonOpenAttachmentto handle them yourself, for example to open a file in your own viewer; then chips without a URL show too. - Haptics mark a selection when the filter changes, success when a page loads, and an error when it fails.
- VoiceOver reads each event as its title, detail, outcome and time, folded runs as buttons with hints, and announces a loaded page. Titles and times stack at large text sizes, and the node keeps a readable size.
- Reduce Motion turns every change into a short fade.
ActivityTimelineExampleat the end ofActivityTimeline.swiftis the Activity screen in the preview: two days of a website project with five edits folded under the first, and an earlier page that reaches the project's start. PasswrapsInNavigationStack: falseto push it in your own stack.
Theming
- Reads
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - Day cards are
color.surfacewithradius.lgand 24 points between days (space.xl). Day headers aretype.subheadsemibold intextSecondary; the filter title istype.headline. - The rail is a 1 point line in
separator. Nodes are 28 point circles onfillwithtextSecondarysymbols, or a 14% tint ofsuccess,warning,dangerorinfowith the symbol in that colour. - Titles use
type.body, detailstype.subheadintextSecondary, timestype.footnoteintextTertiarywith monospaced digits. Attachment chips arefillcapsules withtype.footnotetext. - The Filter button is a
surfacecapsule with aseparatorstroke, onfillwhile a filter is on. Show earlier activity is the secondary button:fillatradius.md, 52 points,type.headline. - Changes animate with
motion.smoothandmotion.snappy, and become short fades under Reduce Motion.
03
Keep building
Pieces that work well next to this one.