Set the navigation and tab bars in the library's type
CI / build-and-deploy (pull_request) Successful in 27s
CI / build-and-deploy (push) Successful in 28s

The screen titles were still system bold sans — `navigationTitle` renders
through UIKit, which no SwiftUI font modifier reaches, so paperSurface()
could tint the bar but never restyle its text. PaperAppearance configures
UINavigationBarAppearance once at launch: serif large and inline titles in
ink, and monospaced tab bar labels.

One trap worth recording: configuring that appearance with
configureWithOpaqueBackground() makes iOS 26 stop laying out the large
title entirely — it vanishes rather than restyling, and it does so even
with the font attributes removed, so it reads like a font problem when it
isn't. Transparent works, and is right here anyway: every screen already
paints the paper ground itself.

Verified in both schemes. Suite passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgLHztZGaEHvS3KNeGQmRM
This commit is contained in:
Krishna Kumar
2026-07-27 13:17:03 -05:00
co-authored by Claude Opus 5
parent fffb3999bf
commit dd1c09a00c
2 changed files with 64 additions and 3 deletions
+2
View File
@@ -12,6 +12,8 @@ private let defaultConfig = ServerConfig(
struct MarksApp: App {
@State private var serverConfig: ServerConfig = ServerConfig.load() ?? defaultConfig
init() { PaperAppearance.apply() }
var body: some Scene {
WindowGroup {
MainContainer(config: serverConfig) {
+62 -3
View File
@@ -83,11 +83,20 @@ enum Paper {
}
static func dynamic(light: UInt32, dark: UInt32) -> Color {
Color(uiColor: UIColor { traits in
UIColor(rgb: traits.userInterfaceStyle == .dark ? dark : light)
})
Color(uiColor: dynamicUI(light: light, dark: dark))
}
static func dynamicUI(light: UInt32, dark: UInt32) -> UIColor {
UIColor { traits in
UIColor(rgb: traits.userInterfaceStyle == .dark ? dark : light)
}
}
/// UIKit equivalents, for the chrome SwiftUI can't reach chiefly the
/// navigation bar, whose title font has no SwiftUI API at all.
static let uiSheet = dynamicUI(light: 0xF8F5EF, dark: 0x15130F)
static let uiInk = dynamicUI(light: 0x14110C, dark: 0xF1ECE1)
static func luminance(of hex: UInt32) -> Double {
let r = Double((hex >> 16) & 0xFF) / 255
let g = Double((hex >> 8) & 0xFF) / 255
@@ -154,6 +163,56 @@ extension View {
}
}
// MARK: - UIKit chrome
/// The navigation and tab bars are UIKit underneath, and their fonts have no
/// SwiftUI equivalent `navigationTitle` will render in the system bold sans
/// whatever you do to the view. Both are configured once at launch so screen
/// titles speak the same serif as the content beneath them.
enum PaperAppearance {
static func apply() {
let bar = UINavigationBarAppearance()
// Transparent, not opaque: the screens already paint the paper ground
// themselves, and an opaque bar config stops iOS 26 laying out the
// large title at all.
bar.configureWithTransparentBackground()
bar.largeTitleTextAttributes = [
.font: serif(34, weight: .regular),
.foregroundColor: Paper.uiInk,
]
bar.titleTextAttributes = [
.font: serif(17, weight: .medium),
.foregroundColor: Paper.uiInk,
]
UINavigationBar.appearance().standardAppearance = bar
UINavigationBar.appearance().compactAppearance = bar
UINavigationBar.appearance().scrollEdgeAppearance = bar
let tabs = UITabBarAppearance()
tabs.configureWithDefaultBackground()
let label: [NSAttributedString.Key: Any] = [
.font: UIFont.monospacedSystemFont(ofSize: 10, weight: .medium)
]
for item in [tabs.stackedLayoutAppearance,
tabs.inlineLayoutAppearance,
tabs.compactInlineLayoutAppearance] {
item.normal.titleTextAttributes = label
item.selected.titleTextAttributes = label
}
UITabBar.appearance().standardAppearance = tabs
UITabBar.appearance().scrollEdgeAppearance = tabs
}
/// `withDesign(.serif)` is the only route to the system serif in UIKit, and
/// it returns nil if the design is unavailable fall back rather than
/// force-unwrap a font.
private static func serif(_ size: CGFloat, weight: UIFont.Weight) -> UIFont {
let base = UIFont.systemFont(ofSize: size, weight: weight)
guard let descriptor = base.fontDescriptor.withDesign(.serif) else { return base }
return UIFont(descriptor: descriptor, size: size)
}
}
// MARK: - Empty state
/// The paper equivalent of `ContentUnavailableView`, which can't be restyled