Squirreleye LLC Squirreleye Service Notes Naked Labs Mirror · Revival Series Index Tools About
23
Section 23 · follows 22

MoreTab + HistoryView: the dark-list pattern in SwiftUI

Written
2026-05-17

The final phase. Two surfaces: MoreTab (the bottom-nav More entry: links to scanner status, account, history, calibration, scan tips, about) and HistoryView (a chart + a list of past scans + a compare button).

Both were using the iOS system list styling (default grouped style on a system-grouped background), which on iOS 18 renders as light-gray section dividers on a slightly-lighter-gray background, with white card insets behind each section. That’s fine on system-themed apps. It looks out of place on top of #052235 navy.

The APK version is much simpler visually: flat navy background everywhere, section headers as small-caps muted-white text, rows white-on-navy with subtle dividers between them. The structure is the same as iOS’s grouped list. The styling is what differs.

§ 23.1

SwiftUI’s “make this list transparent” magic chord

Fig. 23-1
swift
List { Section { ... } header: { Text("SCANNER").foregroundStyle(AppTheme.secondaryText) }}.scrollContentBackground(.hidden).background(AppTheme.background)

.scrollContentBackground(.hidden) is the modifier that hides the default list/form background (iOS 16+). Without it, you can’t see your custom .background through the list. The system one wins. With it, your custom background shows through, and you get the dark-navy look you wanted.

Bonus catch: .scrollContentBackground is also the right tool for Form. If you start putting .background on a Form and wondering why it does nothing, this is why.

§ 23.2

The navigation bar’s three-modifier dance

The iOS navigation bar needs three separate modifiers to look right on a dark theme:

Fig. 23-2
swift
.toolbarBackground(AppTheme.background, for: .navigationBar).toolbarBackground(.visible, for: .navigationBar).toolbarColorScheme(.dark, for: .navigationBar)
  1. .toolbarBackground(color, for:): set the background color.
  2. .toolbarBackground(.visible, for:): force the background to render even when content isn’t scrolled under it. Without this, the bar stays translucent and shows the underlying content (which is your navy bg, so it half-works) but doesn’t apply the explicit color above.
  3. .toolbarColorScheme(.dark, for:): force the bar’s text and chevrons to render as if it were a dark-mode surface, regardless of the rest of the app’s color scheme.

The first two are similar to the bottom-tab dance I already did in Phase A:

Fig. 23-3
swift
.toolbarBackground(AppTheme.bottomBarBg, for: .tabBar).toolbarBackground(.visible, for: .tabBar)

Same pattern, different bar. The third (toolbarColorScheme) is only needed for navigation bars where back-button chevrons would otherwise default to system tint. Tab bars don’t have that problem.

§ 23.3

HistoryView chart restyling

Swift Charts inherits a lot from the system theme. LineMark defaults to the app’s tint color; PointMark defaults to the same. On the dark navy bg they render as a thin blue-purple line that’s hard to see.

Fig. 23-4
swift
LineMark(x: .value("Date", scan.createdAt), y: .value(metric.rawValue, v)) .interpolationMethod(.catmullRom) .foregroundStyle(AppTheme.accent)PointMark(x: .value("Date", scan.createdAt), y: .value(metric.rawValue, v)) .foregroundStyle(AppTheme.accent)

.foregroundStyle(AppTheme.accent) on both marks gives the hot-pink-red line, matching every other selection/highlight surface in the app.

The axis tick labels need their own pass:

Fig. 23-5
swift
.chartXAxis { AxisMarks { _ in AxisValueLabel().foregroundStyle(AppTheme.secondaryText) AxisGridLine().foregroundStyle(.white.opacity(0.1))}}.chartYAxis { AxisMarks { _ in AxisValueLabel().foregroundStyle(AppTheme.secondaryText) AxisGridLine().foregroundStyle(.white.opacity(0.1))}}

The default AxisMarks use the system foreground style (black-ish on light, white-ish on dark), but Swift Charts in iOS 18 renders against a white-ish chart-plot background by default, so the default labels would be invisible on my navy bg. Setting both AxisValueLabel (the numeric tick labels) and AxisGridLine (the faint horizontal/vertical reference lines) explicitly is what gives you a chart that looks like it belongs.

10% white opacity for the grid lines is barely visible: enough to give the chart depth without competing with the data. The APK’s chart has even subtler grid lines; 10% is close enough.

§ 23.4

The list rows inside HistoryView

Fig. 23-6
swift
List { ForEach(scans.reversed(), id: \.id) { scan in HStack { Text(scan.formattedDate).foregroundStyle(.white) Spacer() Text(Units.weight(scan.weight)) .foregroundStyle(AppTheme.secondaryText).monospacedDigit() Text(String(format: "%.1f%%", scan.bodyFatRatio)) .foregroundStyle(AppTheme.secondaryText).monospacedDigit() } .listRowBackground(Color.white.opacity(0.04)) }}.listStyle(.plain).scrollContentBackground(.hidden)

.listStyle(.plain) removes the inset gray section cards; .listRowBackground(Color.white.opacity(0.04)) puts a very faint white wash behind each row so they don’t disappear into the navy. This is one of the patterns the APK uses: a hairline-tinted row surface over a dark background, so individual rows feel discrete without needing strong dividers.

§ 23.5

What was NOT polished

Most of the other inner surfaces (ProfileEditView, CalibrationView, AboutView, MirrorSettingsView) still use the iOS system Form/List styling on system backgrounds. They’re functional, they’re just not APK-faithful in palette.

Doing those individually didn’t seem worth a separate phase. The pattern is the same as MoreTab: three modifiers (toolbarBackground, scrollContentBackground, toolbarColorScheme) plus secondaryText colors on labels. Anyone reading this can apply it. Phase F’s contribution is establishing the pattern and applying it to the two highest-visibility surfaces.

§ 23.6

Six phases in

Phase A (palette + chrome), Phase B (Compare rebuild), Phase C (KEY STATS sparklines + MEASURE L/R cards + waist-to-hip), Phase D (scan-flow chrome + palette sweep), Phase E (first-scan walkthrough), Phase F (MoreTab + HistoryView dark-list pattern). Each one a separate commit, each one with its own blog draft.

The app now looks like a deliberate port of the Android app rather than a custom-styled approximation. Same palette, same accent, same structural shape on Compare and Body tabs, same prep content on the walkthrough, same chart accent on History. The visible gaps from here are scanner-pairing UX (Phase D’s deferred BLE-gated screens), per-region history-graph expansions on MEASURE cards (Phase C’s deferred expanded variant), and styling polish on ProfileEditView / CalibrationView / AboutView / MirrorSettingsView. None of those block daily use; all are visible polish for whenever next.

§ 23.7
§ 23.8
Agent prompts Procedure 23-A as prompts for a coding agent · 4 prompts

Paste into Claude Code or a similar agent, one at a time, from the root of your iOS project. Each prompt maps to the step with the same number above.

1
Make List/Form backgrounds transparent
For every `List` or `Form` in the codebase that needs to sit on a custom dark background instead of the system grouped style (starting with `MoreTab` and `HistoryView`), add `.scrollContentBackground(.hidden)` followed by `.background(AppTheme.background)`. Verify visually that the default light-gray grouped-list background no longer shows through.
2
Apply navigation/tab bar modifier dance
On every `NavigationStack`/`NavigationView` screen that needs the dark theme, apply all three modifiers: `.toolbarBackground(AppTheme.background, for: .navigationBar)`, `.toolbarBackground(.visible, for: .navigationBar)`, and `.toolbarColorScheme(.dark, for: .navigationBar)`. For tab bars, apply only the first two with `AppTheme.bottomBarBg` and `for: .tabBar`. Confirm back-button chevrons and nav titles render in dark-appropriate colors after the change.
3
Restyle Swift Charts for dark background
In `HistoryView`'s chart, explicitly set `.foregroundStyle(AppTheme.accent)` on both the `LineMark` and `PointMark`. Inside `.chartXAxis { AxisMarks { ... } }` and `.chartYAxis { AxisMarks { ... } }`, explicitly style `AxisValueLabel().foregroundStyle(AppTheme.secondaryText)` and `AxisGridLine().foregroundStyle(.white.opacity(0.1))`. Do not rely on Swift Charts' default styling, since it assumes a light chart-plot background and is invisible on a dark navy one.
4
Switch history rows to plain style
On the scan-history `List` in `HistoryView`, set `.listStyle(.plain)` and add `.listRowBackground(Color.white.opacity(0.04))` to each row, combined with `.scrollContentBackground(.hidden)`. Confirm this removes the inset grouped-card look and gives each row a faint distinguishable surface against the navy background.