Journal

Terminal Log Auto-Scroll UI in SwiftUI: Smart Following

Follow the tail; release it the instant the user scrolls up. Here is the smart-follow log console in SwiftUI, firehose budget included.

Terminal Log Auto-Scroll UI in SwiftUI: Smart Following: a vivid neon 3D App Store icon on an orange, pink and blue gradient

TL;DR

A terminal log view in SwiftUI pins to the newest line while the user sits at the bottom, releases the instant they scroll up, and offers a "new lines" pill to jump back, the smart-follow pattern. Build it with rows in a lazy container, ScrollViewReader pinning gated by a following flag, and drag detection that separates user scrolls from your own programmatic ones. Treat the stream as a firehose: batch appends into a few UI commits per second, cap the buffer, and decide color and formatting once at ingest. A free VP0 design supplies the console screen an agent like Claude Code or Cursor extends from its source page while you build the follow logic.

What a terminal log view has to do

A terminal-style log view renders an append-only stream, build output, agent steps, server logs, and keeps the newest line visible without ever fighting the user for the scroll position. That second clause is the entire difficulty. Pinning to the bottom is easy; knowing when to stop pinning is the product. The rule every good console converges on: follow the tail while the user is at the bottom, stop following the instant they scroll up, and offer a clear way back down.

Everything else is staging. Monospaced text, dark background, color by severity, those make it read as a terminal, but the follow behavior is what makes it usable, because logs exist for two opposite postures: glancing at the latest line, and reading something that scrolled past three hundred lines ago. A view that auto-scrolls while someone is reading history is rude in the most literal sense, it takes the page out of their hands, and a view that never follows makes the live tail a chore. The same tension governs any streaming surface, including the AI chat streaming UI, where new tokens arrive under a reader who may have scrolled up.

How does auto-scroll work in SwiftUI?

The classic mechanism is ScrollViewReader: give the last line an identifier, and on each append call scrollTo on that identifier, animated or not, which pins the view to the tail. Newer SwiftUI offers scroll-position APIs on ScrollView that make the other half tractable, knowing where the user is, so you can read whether the view sits at the bottom rather than guessing.

The follow flag is the piece to engineer deliberately. Track a single boolean, following, set false the moment the user drags upward and true again when they return to within a line of the bottom, and only call scrollTo while following is true. Detecting the user’s drag matters because programmatic scrolls also move the position: the implementation must distinguish “the view moved because we pinned it” from “the user moved it,” or the flag flips itself. Get that one state transition right and the rest of the screen is layout.

The three follow behaviors

There are only three reasonable behaviors for a log surface, and choosing deliberately beats inheriting one.

BehaviorWhat it doesWhere it fits
Smart followPins to tail, releases on scroll-up, resumes at bottomThe default for interactive consoles
Hard pinAlways snaps to newest, no history readingDashboards and glanceable status walls
Manual with badgeNever auto-scrolls; a “new lines” pill offers the jumpForensic reading, error triage

Smart follow is the right default, and the badge from the third row belongs in it anyway: when following is off and lines arrive, show a quiet pill, “37 new lines”, that jumps to the tail on tap and doubles as the resume control. The console rendering those lines is ordinary SwiftUI, and a free VP0 design gives the screen around it, the toolbar, the filter chips, the status header, as a real layout with a machine-readable source page an agent like Claude Code or Cursor extends from a pasted link, while you build the follow logic the design cannot know. A live worked example of the genre is the step feed in an agent workspace, where the log is the product.

Performance: a log view is a firehose problem

Logs arrive faster than screens should update, so the view needs a budget between the stream and the render. Three disciplines keep it smooth. Batch the appends: collect arriving lines and commit them to the UI a few times per second rather than per line, because a hot loop emitting hundreds of lines per second will otherwise schedule hundreds of layout passes. Cap the buffer: a console that holds the last several thousand lines and drops the oldest serves every real reading need without growing without bound. And keep each line cheap: a lazy container, plain text, color decided once at ingest rather than re-parsed per frame.

The source side has its own half of the contract. On Apple platforms the system’s unified logging is the right backbone for your own app’s events, and on the server side structured logging via swift-log, at roughly 4,019 GitHub stars the ecosystem’s standard interface, means lines arrive with severity and metadata already attached instead of being regex-mined out of strings. Structure at the source is what makes color, filtering, and search cheap at the view.

Reading features that earn their place

Search and filter turn a log from a scroll into a tool. Severity filters, error, warning, info, are chips above the stream, and they interact with following naturally: filtering while following keeps tailing the filtered stream. Search wants to pause following, since finding is a history posture, and it should report matches as positions you can jump between, not just highlights. Copy deserves first-class treatment too: a long-press that copies the line, and a select mode for ranges, because the entire point of a log line is often to paste it somewhere else. Wrapping policy is a real decision as well: long lines either wrap, which preserves reading flow, or truncate with horizontal scroll, which preserves the columnar shape of structured output; pick per surface, and never silently clip without one of the two.

Color is information design, not decoration. Severity earns hue, errors red, warnings amber, and everything else stays quiet, because a rainbow console is as unreadable as a monochrome one. Timestamps belong on every line but rendered subordinate, dimmed and tabular, so the eye can ignore them until it needs them. The numbers-on-a-dark-screen craft is the same one a token usage dashboard lives by: density without noise.

Common mistakes when vibe coding the console

The classic one is the scroll fight: the agent wires scrollTo on every append and nothing else, producing a view that yanks the user to the bottom mid-read. The fix is the following flag and the drag detection, and it is worth stating in the prompt explicitly, because the happy-path demo, where nobody scrolls up, hides the bug completely.

Three more recur. The whole log lives in one Text view that gets rebuilt per append, which works for fifty lines and dies at five thousand; lines must be rows in a lazy container. The buffer grows forever, which reads as a slow leak that only appears in long sessions. And the “new lines” affordance gets skipped, so when following is off the stream just silently grows, leaving the user unaware anything happened, the small UX crime that makes people distrust the whole console. Each is invisible in a demo and obvious in an afternoon of real use, which is exactly why they survive generation.

Key takeaways: a terminal log view in SwiftUI

  • The follow flag is the product. Tail while at bottom, release on scroll-up, resume at bottom.
  • Distinguish user scrolls from programmatic ones. Otherwise the flag flips itself.
  • Budget the firehose. Batch appends, cap the buffer, keep lines cheap in a lazy container.
  • Structure at the source. Unified logging and swift-log make filtering and color cheap at the view.
  • Start the screen from a free VP0 design. Toolbar, chips, and status arrive shaped; you build the follow logic.

What to build

Build smart follow with the badge: the pinned tail for the glance, the respectful release for the reader, and the “new lines” pill that bridges them. Add severity chips and copy before search, because they pay on day one, and hold the line on the performance budget from the first commit, batched appends, a capped buffer, lazy rows, since retrofitting smoothness into a console is far harder than starting with it. Start the surrounding screen from a free VP0 design and let your agent extend it from the source page while you hand-build the one state machine that matters. Choose hard pin only when the surface is genuinely glanceable status, and manual-with-badge only for forensic tools, and in both cases say so in the design, because a console’s follow behavior is a promise to the reader about who controls the scroll.

Frequently asked questions

How do I build a terminal log auto-scroll UI in SwiftUI? Render lines as rows in a lazy container inside a ScrollView, give the tail an identifier, and use ScrollViewReader’s scrollTo to pin to it on each append, but only while a following flag is true. Set that flag false when the user drags upward and true when they return to the bottom, and show a “new lines” pill while it is off. Batch incoming lines into a few UI commits per second and cap the buffer. A free VP0 design supplies the console screen an agent extends while you wire that logic.

How do I stop auto-scroll from interrupting users reading old logs? Track whether the view is at the bottom, and treat any upward user drag as an instruction to stop following. The implementation detail that makes it work is separating user-initiated scrolls from the programmatic ones your own pinning causes, otherwise the flag flips itself off. While following is paused, show a quiet badge counting new lines with a tap-to-jump, so the stream stays discoverable without stealing the scroll. Resume following automatically when the user returns to within a line of the tail.

Why does my SwiftUI log view get slow with many lines? Almost always one of three causes: every append rebuilds the whole text instead of adding a row to a lazy container, the buffer grows without limit across a long session, or each line does expensive work per frame, like re-parsing severity or formatting timestamps during layout. Fix the structure first, lazy rows with stable identities, then batch appends into a few commits per second, cap history at a few thousand lines, and decide each line’s color and formatting once at ingest.

Should logs auto-scroll by default? Yes, when the user is at the bottom, because the tail is what a live console is for, and no, the moment they scroll up, because reading history is the other half of the job. That pair, called smart follow, is the default that fits interactive consoles. Reserve always-pinned behavior for glanceable status surfaces nobody reads backward, and no-auto-scroll-with-badge for forensic tools. The honest framing: the user owns the scroll, and following is a convenience they can revoke by moving.

Is there a free template for a log console UI? The screen around the stream is the reusable part, and VP0 provides it free: real iOS designs, toolbar, filter chips, status header, dark monospaced body, with a machine-readable source page that Claude Code, Cursor, or another agent reads from a pasted link and extends. The follow state machine, the append batching, and the buffer policy are the part you build, and they are small once stated clearly. That division works because console chrome transfers between products while stream behavior is where your judgment lives.

What VP0 builders also ask

How do I build a terminal log auto-scroll UI in SwiftUI?

Render lines as rows in a lazy container inside a ScrollView, give the tail an identifier, and use ScrollViewReader's scrollTo to pin to it on each append, but only while a following flag is true. Set that flag false when the user drags upward and true when they return to the bottom, and show a "new lines" pill while it is off. Batch incoming lines into a few UI commits per second and cap the buffer. A free VP0 design supplies the console screen an agent extends while you wire that logic.

How do I stop auto-scroll from interrupting users reading old logs?

Track whether the view is at the bottom, and treat any upward user drag as an instruction to stop following. The implementation detail that makes it work is separating user-initiated scrolls from the programmatic ones your own pinning causes, otherwise the flag flips itself off. While following is paused, show a quiet badge counting new lines with a tap-to-jump, so the stream stays discoverable without stealing the scroll. Resume following automatically when the user returns to within a line of the tail.

Why does my SwiftUI log view get slow with many lines?

Almost always one of three causes: every append rebuilds the whole text instead of adding a row to a lazy container, the buffer grows without limit across a long session, or each line does expensive work per frame, like re-parsing severity or formatting timestamps during layout. Fix the structure first, lazy rows with stable identities, then batch appends into a few commits per second, cap history at a few thousand lines, and decide each line's color and formatting once at ingest.

Should logs auto-scroll by default?

Yes, when the user is at the bottom, because the tail is what a live console is for, and no, the moment they scroll up, because reading history is the other half of the job. That pair, called smart follow, is the default that fits interactive consoles. Reserve always-pinned behavior for glanceable status surfaces nobody reads backward, and no-auto-scroll-with-badge for forensic tools. The honest framing: the user owns the scroll, and following is a convenience they can revoke by moving.

Is there a free template for a log console UI?

The screen around the stream is the reusable part, and VP0 provides it free: real iOS designs, toolbar, filter chips, status header, dark monospaced body, with a machine-readable source page that Claude Code, Cursor, or another agent reads from a pasted link and extends. The follow state machine, the append batching, and the buffer policy are the part you build, and they are small once stated clearly. That division works because console chrome transfers between products while stream behavior is where your judgment lives.

Part of the Native Apple & SwiftUI: The iOS Ecosystem hub. Browse all VP0 topics →

Keep reading

Spline 3D Model Background in SwiftUI: A Practical Guide: a reflective 3D App Store icon on a blue and purple gradient
Guides 9 min read

Spline 3D Model Background in SwiftUI: A Practical Guide

A Spline scene behind a SwiftUI interface looks alive because the GPU draws it live. Here is the embed, the performance caps, and when to choose SceneKit.

Lawrence Arya · June 10, 2026
SwiftUI App Intents Template for Apple Intelligence Apps: a glossy App Store icon on a blue, pink and orange gradient with bubbles
Guides 9 min read

SwiftUI App Intents Template for Apple Intelligence Apps

App Intents are how Apple Intelligence, Siri, Shortcuts, and Spotlight reach an app. Here is the template structure that gives an agent the right pattern.

Lawrence Arya · June 10, 2026
SwiftUI Code Audit Service: What to Buy and What It Costs: the App Store logo as a glossy glass icon on a purple and blue gradient with floating bubbles
Guides 9 min read

SwiftUI Code Audit Service: What to Buy and What It Costs

A SwiftUI code audit is judgment as a deliverable: a ranked map of what is fragile in your AI-built app. Here is what to buy, prepare, and expect to pay for.

Lawrence Arya · June 10, 2026
Zaawansowany SwiftUI tutorial: advanced iOS UI with AI: a glass iPhone UI wireframe icon on a holographic purple gradient
Guides 11 min read

Zaawansowany SwiftUI tutorial: advanced iOS UI with AI

Advanced (zaawansowany) SwiftUI comes down to state, performance, navigation, and animation. Here is how to get each right and build faster with AI.

Lawrence Arya · June 10, 2026
Swipe to Approve AI Actions in SwiftUI: The Approval UI: a reflective 3D App Store icon on a blue and purple gradient
Guides 9 min read

Swipe to Approve AI Actions in SwiftUI: The Approval UI

An AI agent proposes; the human commits. Here is the swipe-to-approve pattern in SwiftUI: thresholds, haptics, honest states, and the accessibility twin.

Lawrence Arya · June 10, 2026
Tarot Card Shuffle Animation in SwiftUI: Make It Feel Real: a reflective 3D App Store icon on a blue and purple gradient
Guides 10 min read

Tarot Card Shuffle Animation in SwiftUI: Make It Feel Real

In a tarot app the shuffle is the ritual, and the ritual is the product. Here is the SwiftUI choreography: stagger, arcs, springs, and an honest draw.

Lawrence Arya · June 10, 2026