English · 简体中文
A lightweight SwiftUI font manager. Drop any font file into your project and render it with one modifier.
Text("hello").anyFontUse(size: 24, weight: 100) // CSS-style numeric weight
Text("hello").anyFontUse(size: 24, weight: .thin) // semantic name- Zero config. Drop fonts into your target and AnyFontUse auto-scans the bundle and registers them. No
register(...)calls, noInfo.plistUIAppFonts, not evenApp.init(). SwiftUI Previews work out of the box. - One-line modifier, same shape as SwiftUI's native
.font(). - Weight accepts both numeric literals (
100,400,700…) and semantic names (.thin,.regular,.bold…), aligned with CSS andFont.Weight. - Multi-family:
.anyFontFamily("Inter")switches the default family for a subtree,anyFontUse(..., family: "JetBrains Mono")overrides at the call site. - Auto-detects family + weight: reads OS/2
usWeightClassfirst (CSS-aligned 100~900), falls back to PostScript name keywords, then CT trait. - Manual
register(...)still available for fine control — explicit entries override auto-detected ones. - Nearest-neighbor weight matching: register only 3 weights and
weight: 500snaps to the closest registered value. - Variable fonts: drive axes (
wght,wdth,opsz, custom ones…) per call — supported axes are applied and clamped to their valid range, unsupported axes and non-variable fonts stay unchanged. - Graceful fallback to the system font when nothing is registered.
- Pure Swift, zero dependencies, Swift 6 strict concurrency friendly.
In Xcode: File > Add Packages…, then paste the repo URL.
Or in Package.swift:
dependencies: [
.package(url: "https://github.com/jackcring/AnyFontUse.git", from: "1.1.0")
],
targets: [
.target(
name: "YourApp",
dependencies: ["AnyFontUse"]
)
]Minimum deployment targets:
| Platform | Min |
|---|---|
| iOS | 15.0 |
| macOS | 12.0 |
| tvOS | 15.0 |
| watchOS | 8.0 |
| visionOS | 1.0 |
Drag .ttf / .otf files into your app target and confirm Target Membership is checked.
No need to touch
Info.plist'sUIAppFonts, no registration code required.
import AnyFontUse
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 12) {
Text("hello").anyFontUse(size: 24, weight: 100)
Text("hello").anyFontUse(size: 24, weight: .thin)
Text("hello").anyFontUse(size: 24, weight: .regular)
Text("hello").anyFontUse(size: 24, weight: .bold)
}
}
}The first call to .anyFontUse(...) triggers a one-time scan of Bundle.main. AnyFontUse extracts each font's family and weight (OS/2 usWeightClass → PostScript name → CT trait) and builds the registry. SwiftUI Previews are covered too — no App.init() required.
If your project ships a single family it becomes the default automatically. Otherwise pin one with:
AnyFontManager.shared.defaultFamily = "Inter"
Text("Body").anyFontUse(size: 16, family: "Inter")
Text("Code").anyFontUse(size: 16, family: "JetBrains Mono")Same idea as .foregroundStyle(_:) or .font(_:) — propagates down the subtree:
ContentView()
.anyFontFamily("Inter") // App-wide default: InterVStack {
Text("Hello world") // uses Inter
.anyFontUse(size: 18)
Text("let x = 42") // local override
.anyFontUse(size: 16, family: "JetBrains Mono")
HStack {
Text("a"); Text("b"); Text("c")
}
.anyFontFamily("Playfair Display") // only this HStack uses Playfair
.anyFontUse(size: 20)
}
.anyFontFamily("Inter")The family used at render time is resolved in this order (highest priority first):
- The
family:argument passed toanyFontUse(...); - The nearest
.anyFontFamily("...")up the view tree; AnyFontManager.shared.defaultFamily(auto-picked in zero-config mode — the family with the most weight variants wins).
print(AnyFontManager.shared.registeredFamilies)
// ["Inter", "JetBrains Mono", "Playfair Display"]Drop that into .onAppear { ... } once. Family names come from font metadata — they should match what macOS Font Book shows.
- When it runs. First call to
.anyFontUse(...),registeredFamilies, orregisteredWeights(in:). Once per process. - What it scans.
[Bundle.main]by default. If your fonts live in an SPM resource bundle, configure early in launch:AnyFontManager.shared.autoBootstrapBundles = [.main, .module]
- Supported formats.
.ttf,.otf,.ttc,.otc. - Weight detection priority:
- OS/2
usWeightClass(used when the file contains a single face — most authoritative, CSS-aligned 100~900); - PostScript name keywords (
Thin/ExtraLight/Bold/ExtraBold…); - CoreText
kCTFontWeightTrait, snapped to the nearest CSS anchor.
- OS/2
- Italics. Detected italics are NOT inserted into the family/weight index (so
weight: .regularwon't accidentally pick an italic). They're still registered withCTFontManager, so you can use them viaFont.custom("…-Italic", size:)directly. - Disable auto mode:
AnyFontManager.shared.autoBootstrapEnabled = false
print(AnyFontManager.shared.registeredFamilies)
print(AnyFontManager.shared.registeredWeights(in: "JetBrains Mono"))Anything that fails to register prints a line prefixed with [AnyFontUse] ... to the console.
Reach for this when you want to:
- pick a custom family name (different from what's in the font file);
- correct an auto-detected weight that's wrong;
- load fonts from a non-
Bundle.mainresource bundle.
Manual entries override auto-detected entries with the same family + weight:
AnyFontManager.shared.register(
family: "Mixed",
weights: [
.regular: AnyFontResource(fileName: "Mixed-Regular", fileExtension: "ttf"),
.bold: AnyFontResource(
fileName: "Mixed-Bold",
fileExtension: "otf",
postScriptName: "MixedDisplay-Bold" // skip auto-sniffing
),
]
)Call register(family:…) as many times as you need; the first registered family becomes the default unless you set one explicitly.
AnyFontManager.shared.register(family: "Inter", weights: […])
AnyFontManager.shared.register(family: "PlayfairDisplay", weights: […])
AnyFontManager.shared.defaultFamily = "Inter"For one-off fonts where you don't care about weight mapping:
let psName = AnyFontManager.shared.registerSingleFont(fileName: "MyIcon", fileExtension: "ttf")
// Use the returned PostScript name directly: .font(.custom(psName!, size: 24))AnyFontManager.shared.register(
family: "Brand",
weights: [.regular: "Brand-Regular", .bold: "Brand-Bold"],
bundle: .module
)Registered only .regular (400) and .bold (700)? Any weight in between still works:
Text("auto").anyFontUse(size: 16, weight: 500) // → uses .regular(400)
Text("auto").anyFontUse(size: 16, weight: 600) // → uses .bold(700)If a font ships variable axes (wght, wdth, opsz, custom ones…), you can drive them per call. The rule is simple: axes the font supports are applied (clamped to their valid range); axes it doesn't have — or non-variable fonts — are left unchanged.
// Squeeze the width to 75% (no-op if the font has no wdth axis)
Text("hello").anyFontUse(size: 24, variations: [.width: 75])
// Drive several axes at once, including a custom height axis
Text("hello").anyFontUse(
size: 24,
variations: [.width: 110, .weight: 650, AnyFontAxis(tag: "HGHT"): 120]
)Built-in axis shortcuts: .weight (wght), .width (wdth), .opticalSize (opsz), .slant (slnt), .italic (ital), .grade (GRAD). Any other axis: AnyFontAxis(tag: "XXXX").
Discover what a font actually supports (handy for building sliders):
for info in AnyFontManager.shared.variationAxes(family: "Inter") {
print(info.axis.tag, info.name, info.minimumValue, info.maximumValue, info.defaultValue)
// e.g. wght Weight 100.0 900.0 400.0
}An empty result means the font isn't a variable font.
AnyFontWeight mirrors CSS font-weight and SwiftUI Font.Weight:
| Numeric | Semantic | SwiftUI fallback |
|---|---|---|
| 100 | .ultraLight |
.ultraLight |
| 200 | .thin |
.thin |
| 300 | .light |
.light |
| 400 | .regular |
.regular |
| 500 | .medium |
.medium |
| 600 | .semibold |
.semibold |
| 700 | .bold |
.bold |
| 800 | .heavy |
.heavy |
| 900 | .black |
.black |
Any value between 1 and 1000 is accepted; non-anchor values snap to the nearest semantic for the system fallback path.
Q: My text doesn't change. What's wrong?
A: Two things to verify: 1) the .ttf file is in the app target's membership; 2) Bundle.main.urls(forResourcesWithExtension:) actually finds it. If there's no [AnyFontUse] ... log, call AnyFontManager.shared.registeredFamilies to see what was detected.
Q: Does it work in SwiftUI Previews?
A: Yes. Auto-scan triggers on the first anyFontUse(...) call, which runs in previews regardless of whether App.init() is called.
Q: I want to opt out of auto-registration.
A: AnyFontManager.shared.autoBootstrapEnabled = false, then call register(...) explicitly.
Q: I don't know my font's PostScript name. Now what? A: You don't need to. AnyFontUse sniffs it from the file.
Q: How is this different from plain Font.custom(_:size:)?
A: No PostScript names to remember, no Info.plist plumbing, weights as numbers or semantics, and a system font fallback when nothing is registered.
Q: Performance?
A: Auto-scan runs once on first query. After that, a lookup is a dictionary read behind an NSLock.
PRs and issues welcome. Before submitting, please run:
swift buildMIT — see LICENSE.
Jc © jackcirng.com

