Custom Font는 개발자가 기본 시스템 폰트를 대체하기 위해 모바일 애플리케이션에 추가하는 폰트 파일(TTF, OTF, WOFF2)입니다. 커스텀 폰트는 고유한 시각적 스타일을 만들고, 브랜드 일관성을 유지하며, 다양한 언어에서 가독성을 향상시키는 데 사용됩니다. Apple Fonts에 따르면 커스텀 폰트는 모든 Apple 플랫폼에서 지원되며, Android는 로딩을 간소화하기 위해 Font Family 메커니즘을 제공합니다. 올바른 폰트 통합은 타이포그래피와 애플리케이션의 전반적인 인식에 중요한 영향을 미칩니다.
핵심 포인트
Custom Font(커스텀 폰트)는 운영 체제의 기본 폰트 세트에 포함되지 않은 서체 파일입니다. 개발자는 브랜드에 일관된 고유한 타이포그래피를 위해 이러한 폰트를 프로젝트에 추가합니다. 커스텀 폰트는 유료 상용 서체이거나 Inter, Montserrat 또는 Roboto의 커스텀 버전과 같은 무료 오픈 소스 폰트일 수 있습니다.
커스텀 폰트의 일반적인 사용 사례는 다음과 같습니다: 브랜드 앱 로고 및 제목, 특정 타이포그래피 요구 사항이 있는 읽기 앱, 테마 폰트가 있는 게임, 시스템 폰트가 필요한 글리프를 지원하지 않는 비라틴어 애플리케이션.Google Fonts에 따르면 상위 100개 앱의 60% 이상이 시각적 차별화를 위해 폰트를 커스터마이징합니다.
시스템 폰트(iOS의 San Francisco, Android의 Roboto)는 인터페이스에 최적화되어 있지만 브랜드 타이포그래피에는 적합하지 않습니다. 커스텀 폰트는 브랜드 인지도를 강화하고, 특수 문자(아이콘 폰트)를 포함할 수 있으며, 특정 사용자를 위한 가독성을 향상시킵니다. 그러나 다양한 폰트를 과도하게 사용하면 UX가 저하되므로 애플리케이션당 2~3개 패밀리 이하로 사용하는 것이 좋습니다.
모바일 플랫폼은 여러 폰트 형식을 지원합니다. TrueType (TTF)는 가장 일반적인 형식으로 iOS와 Android의 모든 버전과 호환됩니다. OpenType (OTF)는 합자, 대체 글리프 및 OpenType 기능을 지원하는 TTF의 확장입니다. 두 형식 모두 .ttf 및 .otf 확장자를 가지며 모바일 플랫폼에서 동일하게 작동합니다.
WOFF2(Web Open Font Format 2)는 웹용 압축 형식으로 Android 버전 10부터 지원됩니다. WOFF2는 TTF에 비해 30~50%의 압축률을 제공합니다. iOS의 경우 WOFF2는 Safari를 통해 지원되지만 네이티브 애플리케이션은 TTF/OTF로 변환해야 합니다. 가변 폰트(Variable Fonts)는 모든 웨이트를 단일 파일에 저장하는 최신 형식입니다.
| 형식 | iOS | Android | 압축 | 특징 |
|---|---|---|---|---|
| TTF | 지원 | 지원 | 없음 | 표준 형식, 광범위한 호환성 |
| OTF | 지원 | 지원 | 없음 | OpenType 기능, 합자, 대체 글리프 |
| WOFF2 | Safari를 통해 | API 29+ | 30~50% | 압축됨, APK 크기 절약 |
| Variable | iOS 11+ | API 29+ | 높음 | 모든 웨이트를 하나의 파일로 |
모바일 개발에서는 기본 형식으로 OTF를 사용하는 것이 좋습니다. TTF와 동일한 크기로 OpenType 기능을 지원합니다. APK 공간을 절약하려면 woff2 압축 또는 주문형 폰트 로딩을 사용하세요.
iOS에서 커스텀 폰트를 통합하는 프로세스는 프로젝트 번들에 파일을 추가하고 Info.plist에 등록하는 두 단계로 구성됩니다. 폰트 파일은 프로젝트 디렉토리(일반적으로 Resources/Fonts)에 배치되고 target에 포함됩니다. Info.plist에는 확장자를 포함한 폰트 파일 이름으로 UIAppFonts 배열(Fonts provided by application)이 추가됩니다.
등록 후 폰트는 PostScript 이름을 사용하여 UIFont(name:size:)를 통해 액세스할 수 있습니다. PostScript 이름은 UIFont.familyNames 및 UIFont.fontNames(forFamilyName:)를 통해 확인할 수 있습니다. 이름이 잘못된 경우 UIFont(name:size:)는 nil을 반환합니다. 디버깅을 위해 첫 실행 시 등록된 모든 폰트를 화면에 표시하는 것이 좋습니다.
Swift로 iOS 프로젝트에 Montserrat 폰트를 통합하는 전체 과정을 살펴보겠습니다. Montserrat-Regular.ttf 파일이 프로젝트에 추가되고, Info.plist에서 선언된 후 UIFont를 통해 로드되어 UILabel에 적용됩니다. 각 웨이트(regular, bold, italic)에는 별도의 파일과 Info.plist에 별도의 항목이 필요합니다.
// 1. Info.plist: UIAppFonts → "Montserrat-Regular.ttf"
// 2. 코드에서 폰트 로드
guard let customFont = UIFont(name: "Montserrat-Regular", size: 16) else {
// 시스템 폰트로 폴백
label.font = UIFont.systemFont(ofSize: 16)
return
}
label.font = customFont
// 3. NSAttributedString에서 사용
let attributes = [NSAttributedString.Key.font: customFont]
let attributed = NSAttributedString(string: "Text", attributes: attributes)
폰트가 로드되지 않는 경우(UIFont가 nil 반환)를 처리하는 것은 필수입니다. 애플리케이션이 충돌하는 대신 시스템 폰트를 폴백으로 사용하세요. 이는 네트워크에서 로드되거나 선택적 모듈에 추가된 폰트의 경우 특히 중요합니다.
주문형으로 다운로드되는 폰트의 경우 CTFontManagerRegisterGraphicsFont가 사용됩니다. 이 메서드는 메모리의 데이터(Data)에서 폰트를 등록하고 UIFont에서 사용할 수 있도록 합니다. 사용 후에는 CTFontManagerUnregisterGraphicsFont를 통해 등록을 취소할 수 있습니다. 동적 로딩은 모든 폰트가 즉시 필요하지 않은 대규모 폰트 세트가 있는 애플리케이션에 유용합니다.
Android에서는 res/font 리소스 시스템을 통해 커스텀 폰트가 추가됩니다. TTF 또는 OTF 파일은 res/font/에 배치된 후 웨이트를 그룹화하기 위해 XML Font Family 리소스가 생성됩니다. Android 8.0(API 26)부터 이것이 유일하게 권장되는 방법입니다. 이전 버전의 경우 assets과 Typeface.createFromAsset이 사용됩니다.
XML Font Family는 웨이트(regular, bold, italic)와 폰트 파일 간의 매핑을 설명합니다. XML 레이아웃에서 android:fontFamily="@font/my_font" 속성은 해당 스타일로 setTypeface가 호출될 때 자동으로 올바른 파일을 선택합니다. 이렇게 하면 타이포그래피가 간소화됩니다. 테마에서 패밀리만 지정하면 setTypeface(textView, Typeface.BOLD)가 호출될 때 Android가 자동으로 bold 버전을 선택합니다.
res/font/my_font.xml에 다양한 웨이트에 대한 모든 폰트 파일을 나열하는 XML 파일을 만듭니다. 각 요소에는 fontStyle(normal/italic) 및 fontWeight(100~900) 속성이 있습니다. 그런 다음 레이아웃에서 android:fontFamily="@font/my_font"를 지정합니다. textStyle="bold"가 사용되면 Android는 자동으로 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>
Font Family를 사용할 때 리소스 접근 방식은 자동으로 폰트를 캐시하고 올바른 웨이트를 선택합니다. 필요한 웨이트가 패밀리에 없으면 Android는 가장 가까운 사용 가능한 웨이트에서 합성합니다. 폰트를 정밀하게 제어하려면 명시적 파일 참조와 함께 Typeface.create()를 사용하세요.
크로스 플랫폼 프레임워크에서는 폰트 통합이 통일되어 있습니다. Flutter는 pubspec.yaml을 사용하여 폰트를 선언합니다: 파일은 프로젝트의 fonts/ 폴더에 배치된 후 패밀리 및 웨이트와 함께 fonts 섹션에 나열됩니다. 그런 다음 폰트는 TextStyle(fontFamily: 'Montserrat') 또는 앱 테마를 통해 적용됩니다. Flutter는 TTF와 OTF를 지원합니다.
React Native는 네이티브(Info.plist 및 res/font를 통해) 또는 react-native-vector-icons 및 @expo-google-fonts와 같은 라이브러리를 통해 두 가지 접근 방식을 사용합니다. Expo는 프로세스를 간소화합니다. 폰트는 expo-font를 통해 로드됩니다: Font.loadAsync({ 'Montserrat': require('./assets/fonts/Montserrat.ttf') }). React Navigation 및 테마를 사용하면 폰트를 전역적으로 설정할 수 있습니다.
pubspec.yaml에서 폰트를 선언하고 MaterialApp 테마에 적용합니다. Montserrat-Regular.ttf 및 Montserrat-Bold.ttf 파일은 fonts/에 배치됩니다. 선언 후 폰트는 애플리케이션 전체에서 TextStyle을 통해 액세스할 수 있습니다. fontWeight: FontWeight.bold가 지정되면 Flutter가 자동으로 bold 파일을 사용합니다.
// pubspec.yaml
flutter:
fonts:
- family: Montserrat
fonts:
- asset: fonts/Montserrat-Regular.ttf
- asset: fonts/Montserrat-Bold.ttf
weight: 700
// 테마에서 사용
MaterialApp(
theme: ThemeData(
textTheme: TextTheme(
headlineLarge: TextStyle(
fontFamily: 'Montserrat',
fontWeight: FontWeight.bold,
fontSize: 28
)
)
)
)
Flutter가 지정된 경로에서 폰트를 찾지 못하면 애플리케이션은 오류 없이 시스템 폴백 폰트를 사용합니다. 디버깅을 위해 MaterialApp에서 checkConsistency를 활성화하세요. 폰트 가용성을 확인하고 폰트가 없을 때 콘솔에 경고를 출력합니다.
APK 및 IPA 크기는 포함된 폰트 수에 직접적으로 영향을 받습니다. 단일 TTF 파일은 라틴 문자 세트의 경우 50~500KB, 키릴 문자, CJK 문자 또는 기타 확장 세트를 지원하는 폰트의 경우 최대 2~5MB입니다. 10개의 폰트는 애플리케이션 크기에 10~20MB를 추가할 수 있으며, 이는 모바일 다운로드에 중요합니다.
최적화를 위해 서브세팅(폰트 파일에서 사용하지 않는 문자 제거)을 사용하세요. glyphhanger, fonttools(pyftsubset) 및 Google Webfont Optimizer와 같은 도구는 필요한 글리프(라틴 + 키릴 + 숫자 + 문장 부호)만 포함하는 폰트 버전을 만듭니다. 이렇게 하면 크기가 50~80% 줄어듭니다. iOS에서는 첫 실행 시 필요하지 않은 폰트에 주문형 리소스를 사용할 수 있습니다.
가변 폰트(Variable Font)는 Thin에서 Black, Condensed에서 Expanded까지 모든 웨이트를 포함하는 단일 파일입니다. 하나의 가변 폰트로 다양한 웨이트의 10~20개의 개별 파일을 대체할 수 있습니다. 이러한 파일의 크기는 약 1~2개의 정적 파일과 같습니다. 지원: iOS 11+, Android 10+, Flutter(FontVariation을 통해), React Native(커스텀 라이브러리를 통해).
폰트 크기를 30~50% 줄이려면 WOFF2를 사용하세요. API 29의 Android에서는 WOFF2를 직접 사용할 수 있습니다. iOS의 경우 등록 전에 WOFF2를 TTF로 변환해야 합니다. 온보딩 중이나 특정 화면에서만 사용되는 폰트는 주문형 리소스(iOS) 또는 Dynamic Feature(Android)를 통해 로드해야 합니다. 기본 APK에는 포함되지 않습니다.
커스텀 폰트는 로딩 시간과 메모리라는 두 가지 방식으로 성능에 영향을 미칩니다. 첫 번째 액세스 시 시스템은 디스크에서 파일을 읽고, 폰트 테이블을 구문 분석하고, 렌더링을 위한 내부 구조를 만듭니다. 1MB 폰트의 경우 20~50ms가 소요됩니다. 첫 로드 후 캐싱하면 후속 액세스 시 지연이 제거됩니다.
메모리: 로드된 각 폰트는 프로세스 캐시에 저장됩니다. 시스템 폰트는 미리 로드되어 있습니다. 커스텀 폰트는 처음 사용 시 로드됩니다. 메모리에서 폰트는 구문 분석된 구조(cmap, glyf, head, hmtx 테이블)로 인해 디스크보다 약 2~3배 더 많은 공간을 차지합니다. 평균 크기의 커스텀 폰트 5개(총 3MB)의 경우 약 6~9MB의 RAM이 필요합니다.
커스텀 폰트를 애플리케이션당 2~3개 패밀리로 제한하세요. 여러 웨이트를 대체하려면 가변 폰트를 사용하세요. UI 요소(버튼, 레이블, 목록)에는 시스템 폰트를 적용하고 강조 타이포그래피(제목, 배너)에만 커스텀 폰트를 사용하세요. Android에서는 Map을 통해 Typeface를 캐시하고 iOS에서는 정적 변수를 통해 UIFont를 캐시하세요.
object FontCache {
private val cache = mutableMapOf<String, Typeface>()
fun get(context: Context, fontId: Int): Typeface {
return cache.getOrPut(fontId.toString()) {
ResourcesCompat.getFont(context, fontId)
}
}
}
// RecyclerView에서 사용
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.typeface = FontCache.get(context, R.font.montserrat_regular)
}
onBindViewHolder에서 Typeface를 로드하는 것은 캐시가 있는 경우에만 허용됩니다. 캐시가 없으면 createFromAsset을 호출할 때마다 새 객체가 생성되고 디스크에서 파일을 읽습니다. RecyclerView 스크롤 중에 눈에 띄는 지연이 발생합니다. 캐시는 폰트가 한 번 로드되고 재사용되므로 문제를 해결합니다.
자주 묻는 질문
OTF가 모바일 애플리케이션에 최적의 선택입니다. TTF와 동일한 크기로 OpenType 기능(합자, 대체 글리프)을 지원합니다. 공간을 절약하려면 서브세팅이 적용된 가변 폰트(TTX 또는 OTF)를 사용하세요. WOFF2는 Android 10+에만 적합하며 네이티브 애플리케이션의 iOS에서는 지원되지 않습니다.
네. Google Fonts는 상업적 사용을 위해 OFL(Open Font License) 라이선스로 폰트를 제공합니다. 폰트는 fonts.google.com에서 다운로드하여 프로젝트에 추가할 수 있습니다. Android는 Google Play Services를 통해 다운로드 가능한 폰트를 지원하므로 폰트 파일을 APK에 포함하지 않고 첫 사용 시 로드할 수 있습니다.
가장 일반적인 원인: 파일이 target에 추가되지 않음(iOS), Info.plist에 선언되지 않음(iOS), 파일이 res/font에 없음(Android), PostScript 이름이 잘못됨, 파일이 손상됨. iOS에서 진단하려면 콘솔에 UIFont.familyNames를 출력하세요. Android에서는 FontLoader를 사용하여 assets에서 폰트 로딩을 디버그하세요.
서브세팅(pyftsubset)을 사용하여 사용하지 않는 문자를 제거하면 크기가 50~80% 줄어듭니다. 10개의 개별 파일 대신 가변 폰트를 사용하세요. Android 10+의 경우 WOFF2 압축을 사용하세요. ProGuard를 구성하여 폰트 경로를 난독화하세요. iOS의 경우 주문형 리소스를 사용하세요.
2~3개 패밀리(다양한 웨이트 포함)로 제한하는 것이 좋습니다. 각 패밀리는 애플리케이션 크기에 200KB ~ 3MB를 추가합니다. 시스템 폰트는 앱에 부담을 주지 않으므로 UI 요소에는 시스템 폰트를 사용하고 제목 및 강조 블록에는 커스텀 폰트를 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.