Set the navigation and tab bars in the library's type
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:
co-authored by
Claude Opus 5
parent
fffb3999bf
commit
dd1c09a00c
@@ -12,6 +12,8 @@ private let defaultConfig = ServerConfig(
|
|||||||
struct MarksApp: App {
|
struct MarksApp: App {
|
||||||
@State private var serverConfig: ServerConfig = ServerConfig.load() ?? defaultConfig
|
@State private var serverConfig: ServerConfig = ServerConfig.load() ?? defaultConfig
|
||||||
|
|
||||||
|
init() { PaperAppearance.apply() }
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
MainContainer(config: serverConfig) {
|
MainContainer(config: serverConfig) {
|
||||||
|
|||||||
@@ -83,11 +83,20 @@ enum Paper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static func dynamic(light: UInt32, dark: UInt32) -> Color {
|
static func dynamic(light: UInt32, dark: UInt32) -> Color {
|
||||||
Color(uiColor: UIColor { traits in
|
Color(uiColor: dynamicUI(light: light, dark: dark))
|
||||||
UIColor(rgb: traits.userInterfaceStyle == .dark ? dark : light)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
static func luminance(of hex: UInt32) -> Double {
|
||||||
let r = Double((hex >> 16) & 0xFF) / 255
|
let r = Double((hex >> 16) & 0xFF) / 255
|
||||||
let g = Double((hex >> 8) & 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
|
// MARK: - Empty state
|
||||||
|
|
||||||
/// The paper equivalent of `ContentUnavailableView`, which can't be restyled —
|
/// The paper equivalent of `ContentUnavailableView`, which can't be restyled —
|
||||||
|
|||||||
Reference in New Issue
Block a user