Custom Font is a font file (TTF, OTF, WOFF2) that a developer adds to a mobile application to replace the default system font. Custom fonts are used to create a unique visual style, maintain brand consistency and improve readability across different languages. According to Apple Fonts, custom fonts are supported on all Apple platforms, and Android provides the Font Family mechanism to simplify loading. Proper font integration critically impacts typography and the overall feel of the application.
Key Takeaways
Custom Font is a typeface file that is not part of the operating system’s default font set. Developers add such fonts to their projects for unique brand-consistent typography. Custom fonts can be either paid commercial typefaces or free open-source fonts such as Inter, Montserrat or a custom version of Roboto.
Typical use cases for custom fonts include: brand app logos and headlines, reading apps with specific typographic requirements, games with thematic fonts, and applications for non-Latin languages where the system font does not support the required glyphs. According to Google Fonts, more than 60% of top-100 apps customize their fonts for visual differentiation.
System fonts (San Francisco on iOS, Roboto on Android) are optimized for interfaces but are not suitable for brand typography. A custom font strengthens brand recognition, can include special characters (icon fonts) and improves readability for specific audiences. However, excessive use of different fonts degrades UX — it is recommended to use no more than 2–3 families per application.
Mobile platforms support several font formats. TrueType (TTF) is the most common format, compatible with all versions of iOS and Android. OpenType (OTF) is an extension of TTF with support for ligatures, alternate glyphs and OpenType features. Both formats have .ttf and .otf extensions and work identically on mobile platforms.
WOFF2 (Web Open Font Format 2) is a compressed format for the web, supported on Android starting from version 10. WOFF2 provides up to 30–50% compression compared to TTF. For iOS, WOFF2 is supported through Safari, but native applications require conversion to TTF/OTF. Variable Fonts are a modern format that stores all weights in a single file.
| Format | iOS | Android | Compression | Features |
|---|---|---|---|---|
| TTF | Yes | Yes | None | Standard format, broad compatibility |
| OTF | Yes | Yes | None | OpenType features, ligatures, alternate glyphs |
| WOFF2 | Via Safari | API 29+ | 30–50% | Compressed, saves APK size |
| Variable | iOS 11+ | API 29+ | High | Single file for all weights |
For mobile development, it is recommended to use OTF as the primary format — it supports OpenType features at the same size as TTF. To save APK space, use compression via woff2 or on-demand font loading.
The process of adding a custom font in iOS consists of two steps: adding the file to the project bundle and registering it in Info.plist. The font file is placed in the project directory (usually Resources/Fonts) and included in the target. In Info.plist, a UIAppFonts array (Fonts provided by application) is added with the font file names including the extension.
After registration, the font is accessible via UIFont(name:size:) using its PostScript name. The PostScript name can be found through UIFont.familyNames and UIFont.fontNames(forFamilyName:). If the name is incorrect, UIFont(name:size:) returns nil. For debugging, it is recommended to display all registered fonts on screen at first launch.
Let us walk through the full cycle of integrating the Montserrat font into an iOS project in Swift. The Montserrat-Regular.ttf file is added to the project, declared in Info.plist, then loaded via UIFont and applied to a UILabel. Each weight (regular, bold, italic) requires a separate file and a separate entry in Info.plist.
// 1. In Info.plist: UIAppFonts → "Montserrat-Regular.ttf"
// 2. Load font in code
guard let customFont = UIFont(name: "Montserrat-Regular", size: 16) else {
// fallback to system font
label.font = UIFont.systemFont(ofSize: 16)
return
}
label.font = customFont
// 3. Use in NSAttributedString
let attributes = [NSAttributedString.Key.font: customFont]
let attributed = NSAttributedString(string: "Text", attributes: attributes)
Handling the case when the font is not loaded (UIFont returns nil) is mandatory. Instead of crashing the application, use the system font as a fallback. This is especially important for fonts loaded from the network or added to optional modules.
For fonts downloaded on demand, CTFontManagerRegisterGraphicsFont is used. This method registers a font from in-memory data (Data) and makes it available for UIFont. After use, the font can be unregistered via CTFontManagerUnregisterGraphicsFont. Dynamic loading is useful for applications with a large set of fonts where not all are needed immediately.
On Android, custom fonts are added through the res/font resource system. TTF or OTF files are placed in res/font/, after which XML Font Family resources are created to group weights. Starting from Android 8.0 (API 26), this is the only recommended approach. For older versions, assets and Typeface.createFromAsset are used.
An XML Font Family describes the mapping between a weight (regular, bold, italic) and a font file. In XML layout, the attribute android:fontFamily="@font/my_font" automatically selects the correct file when setTypeface is called with the corresponding style. This simplifies typography — simply specify the family in the theme, and Android will pick the bold version when setTypeface(textView, Typeface.BOLD) is called.
Create an XML file at res/font/my_font.xml listing all font files for different weights. Each element has fontStyle (normal/italic) and fontWeight (100–900) attributes. Then in the layout specify android:fontFamily="@font/my_font". When textStyle="bold" is used, Android automatically selects the file with fontWeight 700.
<!-- res/font/my_font.xml -->
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
<font
android:fontStyle="normal"
android:fontWeight="400"
app:font="@font/my_font_regular" />
<font
android:fontStyle="normal"
android:fontWeight="700"
app:font="@font/my_font_bold" />
</font-family>
When using Font Family, the resource approach automatically caches fonts and selects the correct weight. If the required weight is missing from the family, Android synthesizes it from the nearest available one. For precise control over the font, use Typeface.create() with an explicit file reference.
In cross-platform frameworks, font integration is unified. Flutter uses pubspec.yaml to declare fonts: files are placed in the fonts/ folder of the project and then listed in the fonts section with family and weights. The font is then applied via TextStyle(fontFamily: 'Montserrat') or in the app theme. Flutter supports TTF and OTF.
React Native uses two approaches: native (via Info.plist and res/font) or through libraries such as react-native-vector-icons and @expo-google-fonts. Expo simplifies the process — fonts are loaded via expo-font: Font.loadAsync({ 'Montserrat': require('./assets/fonts/Montserrat.ttf') }). React Navigation and themes allow setting a font globally.
Declaring fonts in pubspec.yaml and applying them in the MaterialApp theme. The Montserrat-Regular.ttf and Montserrat-Bold.ttf files are placed in fonts/. After declaration, the font is accessible via TextStyle throughout the application. Flutter automatically uses the bold file when fontWeight: FontWeight.bold is specified.
// pubspec.yaml
flutter:
fonts:
- family: Montserrat
fonts:
- asset: fonts/Montserrat-Regular.ttf
- asset: fonts/Montserrat-Bold.ttf
weight: 700
// Usage in theme
MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
headlineLarge: TextStyle(
fontFamily: 'Montserrat',
fontWeight: FontWeight.bold,
fontSize: 28
)
)
)
)
If Flutter does not find a font at the specified path, the application uses the system fallback font without errors. For debugging, enable checkConsistency in MaterialApp — it checks font availability and prints warnings to the console when fonts are missing.
The APK and IPA size directly depends on the number of included fonts. A single TTF file weighs 50–500 KB for the Latin character set and up to 2–5 MB for fonts supporting Cyrillic, CJK characters or other extended sets. Ten fonts can add 10–20 MB to the application size, which is critical for mobile downloads.
For optimization, use subsetting — removing unused characters from the font file. Tools such as glyphhanger, fonttools (pyftsubset) and Google Webfont Optimizer create a font version with only the needed glyphs (Latin + Cyrillic + digits + punctuation). This reduces the size by 50–80%. On iOS, on-demand resources can be used for fonts that are not needed at first launch.
A Variable Font is a single file containing all weights from Thin to Black and from Condensed to Expanded. One variable font can replace 10–20 separate files with different weights. The size of such a file is approximately equal to 1–2 static files. Support: iOS 11+, Android 10+, Flutter (via FontVariation), React Native (via custom libraries).
Use WOFF2 to reduce font size by 30–50%. On Android with API 29, WOFF2 can be used directly. For iOS, WOFF2 needs to be converted to TTF before registration. Fonts used only during onboarding or on specific screens should be loaded via on-demand resources (iOS) or Dynamic Feature (Android) — they will not be included in the base APK.
Custom fonts affect performance in two ways: loading time and memory. On first access, the system reads the file from disk, parses font tables and creates internal rendering structures. For a 1 MB font, this takes 20–50 ms. Caching after the first load eliminates the delay on subsequent accesses.
Memory: each loaded font is stored in the process cache. System fonts are preloaded; custom fonts are loaded on first use. In memory, a font takes approximately 2–3 times more space than on disk due to parsed structures: cmap, glyf, head, hmtx tables. For 5 custom fonts of average size (total 3 MB), about 6–9 MB of RAM is required.
Limit custom fonts to 2–3 families per application. Use variable fonts to replace multiple weights. Apply system fonts for UI elements (buttons, labels, lists) and custom fonts only for accent typography (headings, banners). Cache Typeface on Android via a Map and UIFont on iOS via static variables.
object FontCache {
private val cache = mutableMapOf<String, Typeface>()
fun get(context: Context, fontId: Int): Typeface {
return cache.getOrPut(fontId.toString()) {
ResourcesCompat.getFont(context, fontId)
}
}
}
// Usage in RecyclerView
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.typeface = FontCache.get(context, R.font.montserrat_regular)
}
Loading Typeface in onBindViewHolder is acceptable only with caching. Without a cache, each createFromAsset call creates a new object and reads the file from disk. During RecyclerView scrolling, this causes noticeable lag. The cache solves the problem because the font is loaded once and reused.
Frequently Asked Questions
OTF is the optimal choice for mobile applications. It supports OpenType features (ligatures, alternate glyphs) at the same size as TTF. To save space, use variable fonts (TTX or OTF) with subsetting. WOFF2 is only suitable for Android 10+ and is not supported on iOS in native applications.
Yes. Google Fonts provides fonts under the OFL (Open Font License) for commercial use. Fonts can be downloaded from fonts.google.com and added to the project. Android supports Downloadable Fonts via Google Play Services, which allows font files to be excluded from the APK and instead loaded on first use.
The most common reasons: the file is not added to the target (iOS), not declared in Info.plist (iOS), the file is not in res/font (Android), the PostScript name is incorrect, or the file is corrupted. For diagnostics on iOS, print UIFont.familyNames to the console. On Android, use FontLoader to debug font loading from assets.
Use subsetting (pyftsubset) to remove unused characters — this reduces size by 50–80%. Use variable fonts instead of 10 separate files. Use WOFF2 compression for Android 10+. Configure ProGuard to obfuscate font paths. For iOS, use on-demand resources.
It is recommended to limit to 2–3 families (including different weights). Each family adds 200 KB to 3 MB to the application size. System fonts do not burden the app, so use system fonts for UI elements and keep custom fonts for headings and accent blocks.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also