← 모든 글

SF Pro: 가변 축, 광학적 크기, 그리고 Dynamic Type 계약

SF Pro는 2015년 iOS 9 / OS X El Capitan부터 Apple의 시스템 폰트로 사용되고 있으며, 세 개의 축(weight, width, optical size)을 갖춘 가변 폰트입니다. 산세리프(SF Pro), 둥근 변형(SF Pro Rounded), 모노스페이스(SF Mono), 컴팩트 변형(watchOS에서 사용되는 SF Compact), 그리고 세리프 동반 폰트(New York)로 구성된 디자인 패밀리입니다1. 이 폰트는 Dynamic Type 계약을 중심으로 설계되었습니다. SwiftUI의 11개 텍스트 스타일(.largeTitle, .title, .body, .callout, .footnote 등)은 모두 기본적으로 SF Pro를 사용하며, 사용자가 설정에서 선호하는 텍스트 크기를 변경하면 모두 자동으로 크기가 조정됩니다.

대부분의 앱은 11개 스타일 중 하나 또는 두 개를 사용하고, 기본 동작을 넘어서 Dynamic Type을 무시하며, 가변 축을 절대 건드리지 않습니다. 그 결과로 작동은 하지만 플랫폼의 어휘를 구사하지 못하는 타이포그래피가 만들어집니다. 이 글에서는 시스템 폰트 패밀리, 가변 축, 11개의 의미론적 스타일, Dynamic Type 계약, 그리고 커스텀 폰트가 참여하기 위해 명시적인 크기 조정 작업이 필요한 사례를 살펴봅니다.

TL;DR

  • SF Pro Variable은 weight(wght), width(wdth), optical size(opsz)의 세 가지 축을 노출합니다2. 광학적 크기 조정은 포인트 크기에 따라 자동으로 이루어지며, weight와 width는 SwiftUI의 Font.WeightFont.Width를 통해 다룰 수 있습니다.
  • 시스템 패밀리에는 SF Pro(기본값), SF Pro Rounded(친근한 UI 요소), SF Mono(코드, 기술적 UI), SF Compact(watchOS, 좁은 컨텍스트), New York(편집용 읽기를 위한 세리프 동반 폰트)이 포함됩니다.
  • SwiftUI의 11개 텍스트 스타일은 자동으로 Dynamic Type을 지원합니다. .body는 안전한 기본값이며, .largeTitle부터 .caption2까지가 플랫폼의 계층 구조를 다룹니다.
  • 커스텀 폰트는 기본적으로 Dynamic Type에 따라 크기가 조정되지 않습니다. 폰트가 Dynamic Type 크기 조정에 참여하도록 하려면 Font.custom("Name", size: 16, relativeTo: .body)를 사용하세요3.
  • @Environment(\.dynamicTypeSize)를 사용하면 뷰가 현재 텍스트 크기에 맞춰 레이아웃을 적응시킬 수 있습니다. 값의 범위는 .xSmall부터 .accessibility5까지(총 12개 크기)입니다4.

시스템 폰트 패밀리

Apple은 시스템 폰트 경험에 모두 참여하는 5개의 패밀리를 제공합니다.

SF Pro

모든 Apple 플랫폼의 기본값입니다. iPhone, iPad, Mac, Vision은 본문 텍스트, 헤드라인, 그리고 대부분의 UI에 SF Pro를 사용합니다. 이 폰트는 폭넓은 언어 지원(라틴, 그리스, 키릴, 아랍, 히브리, 데바나가리 등)을 갖추고 있으며, 우측에서 좌측으로의 레이아웃을 기본적으로 지원하고, 작은 대문자, 대체 숫자(라이닝 vs. 표 형식), 양식적 대안을 위한 디자인된 변형을 포함합니다.

SwiftUI에서 시스템 폰트를 통해 접근합니다.

Text("Hello").font(.system(.body))                    // SF Pro by default
Text("Hello").font(.system(.body, weight: .semibold)) // SF Pro Semibold
Text("Hello").font(.system(.body, design: .default))  // explicit SF Pro

SF Pro Rounded

친근하고 접근하기 쉬운 UI를 위해 설계된 둥근 변형입니다. Apple Watch 컴플리케이션, Fitness 링, Maps의 일부 방향 안내, Find My, 그리고 선택된 Health 화면에서 사용됩니다. 둥근 형태는 부드러움을 전달합니다. 단순히 시각적 다양성을 위해서가 아니라 톤을 위해 사용하세요.

Text("Hello").font(.system(.body, design: .rounded))

SF Mono

코드, 터미널 UI, 그리고 문자 셀 정렬이 중요한 모든 컨텍스트를 위한 모노스페이스 패밀리입니다. SF Mono는 Xcode의 기본 폰트이고, Terminal 앱의 모노스페이스 설정에서 사용되며, .monospaced를 요청하는 모든 SwiftUI 뷰에서 사용됩니다.

Text("let x = 42").font(.system(.body, design: .monospaced))

SF Compact

watchOS, tvOS 포커스 하이라이트, 그리고 가로 공간이 제한된 일부 Mac 컨텍스트에서 사용되는 좁은 패밀리입니다. 좁은 형태는 x-높이를 유지하면서 가로 진행을 줄이며, 이는 SF Pro가 답답하게 느껴질 Apple Watch 페이스 크기에서 잘 작동하도록 합니다.

watchOS 앱은 시스템 폰트를 통해 자동으로 SF Compact를 사용하며, iOS 앱은 일반적으로 명시적으로 요청할 필요가 없습니다.

New York

편집용 읽기를 위한 동반 폰트로 설계된 세리프 패밀리입니다. New York은 Books의 장문 텍스트, Notes의 손글씨 스타일 메모, 그리고 SwiftUI에서 .serif 디자인을 통해 나타납니다.

Text("Long-form essay").font(.system(.body, design: .serif))

세리프 동반 폰트는 앱 UI에서 드뭅니다. 기본값으로 사용하지 말고 의도적으로 선택하세요(읽기 모드, 인용된 구절, 기사 본문 등).

세 가지 가변 축

SF Pro Variable은 모든 글리프를 만들기 위해 결합되는 세 가지 축을 인코딩합니다.

Weight (wght)

9개의 명명된 굵기: .ultraLight, .thin, .light, .regular, .medium, .semibold, .bold, .heavy, .black. 가변 폰트는 이들 사이를 연속적으로 보간하지만, SwiftUI의 API는 명명된 값을 노출합니다.

Text("Heading").font(.system(.title, weight: .semibold))

Weight는 강조 계층을 전달합니다. 본문에는 .regular, 헤드라인에는 .semibold 또는 .bold, 활성 툴바 항목에는 .medium, 강조하지 않는 라벨에는 .light. 의미론적 스타일(.headline, .subheadline)은 합리적인 weight 기본값을 제공하므로, 의미론적 스타일이 적절한 형태가 아닐 때만 명시적인 weight를 사용하세요.

Width (wdth)

iOS 16+ API의 4개 명명된 너비: .compressed, .condensed, .standard, .expanded. width 축은 weight나 시각적 특성을 변경하지 않고 가로 진행에 영향을 줍니다. 빡빡한 UI(많은 항목이 있는 내비게이션 바)나 시각적 질감(더 많은 가로 존재감을 원하는 디스플레이 크기 헤드라인)에 사용하세요.

Width는 SwiftUI의 .fontWidth(_:) 뷰 수정자를 통해 적용되거나(또는 .width(_:)를 통해 Font에 체이닝):

Text("Compressed")
    .font(.system(.title, weight: .bold))
    .fontWidth(.compressed)

Width는 본문 텍스트에 적합한 축은 거의 아닙니다. 타이포그래피가 디자인의 일부인 디스플레이 크기에서 잘 작동합니다.

Optical Size (opsz)

기술적으로 야심찬 축입니다. SF Pro의 옛 SF Text와 SF Display 변형은 이제 가변 폰트에 인코딩된 연속적인 그라디언트입니다. 20포인트 미만의 크기에서는 시스템이 SF Text 광학 조정(더 넓은 자간, 약간 더 두꺼운 획, 더 열린 카운터)을 적용합니다. 20포인트 이상에서는 SF Display가 인계받습니다(더 좁은 자간, 정제된 비율). 전환은 부드럽습니다.

opsz 축은 자동입니다. 앱은 이를 명시적으로 다루지 않습니다. 시스템이 요청된 포인트 크기를 읽고 적절한 광학 크기로 해결합니다. 함의: 작은 UI 크기의 타이포그래피는 동일한 폰트의 헤드라인 크기와 다른 비율을 가지며, 이는 의도된 설계입니다. 광학적 크기 조정이 없는 커스텀 폰트는 한 크기에서는 괜찮아 보이지만 다른 크기에서는 잘못 보입니다. SF Pro의 자동 처리는 그 함정을 완전히 피합니다.

11개의 텍스트 스타일

SwiftUI의 Font.TextStyle은 모두 Dynamic Type에 참여하는 11개의 의미론적 스타일을 정의합니다4:

스타일 기본 크기 (Large) 일반적 사용
.largeTitle 34 pt 최상위 헤더, 히어로 텍스트
.title 28 pt 섹션 헤더
.title2 22 pt 하위 섹션 헤더
.title3 20 pt 작은 헤딩
.headline 17 pt 강조된 본문, 목록 행 제목
.body 17 pt 기본 본문 텍스트
.callout 16 pt 보조 본문, 컨텍스트 내 캡션
.subheadline 15 pt 보조 헤더, 메타 텍스트
.footnote 13 pt 작은 보조 텍스트
.caption 12 pt 이미지 캡션, 작은 글씨
.caption2 11 pt 최소 가독 텍스트

“기본 크기” 열은 사용자의 “Large” Dynamic Type 설정(시스템 기본값)에서의 크기를 보여줍니다. 각 스타일은 사용자가 선호하는 텍스트 크기를 조정함에 따라 위아래로 크기가 조정되며, 상대적 계층 구조는 그대로 유지됩니다.

올바른 채택 방법은 의미론적 스타일을 직접 사용하는 것입니다.

VStack(alignment: .leading) {
    Text("Title").font(.title)
    Text("Subtitle").font(.subheadline).foregroundStyle(.secondary)
    Text("Body content here.").font(.body)
}

계층 구조는 모든 Dynamic Type 설정에서 보존되고, Apple HIG 규약이 준수되며, 타이포그래피는 앱별 코드 없이 사용자의 접근성 설정에 반응합니다.

Dynamic Type 계약

Dynamic Type은 설정 > 손쉬운 사용 > 디스플레이 및 텍스트 크기 > 더 큰 텍스트의 사용자 제어 텍스트 크기 설정입니다. 값은 환경을 통해 DynamicTypeSize로 흐르며, .xSmall부터 .accessibility5까지 12개 값을 가집니다4:

  • 표준 크기: .xSmall, .small, .medium, .large(기본값), .xLarge, .xxLarge, .xxxLarge
  • 접근성 크기: .accessibility1, .accessibility2, .accessibility3, .accessibility4, .accessibility5

의미론적 텍스트 스타일을 사용하는 앱은 크기 조정을 무료로 얻습니다. 현재 크기에 맞춰 레이아웃을 적응시키고자 하는 앱은 환경을 읽습니다.

struct AdaptiveLayout: View {
    @Environment(\.dynamicTypeSize) var dynamicTypeSize

    var body: some View {
        if dynamicTypeSize.isAccessibilitySize {
            VStack { content }    // stack vertically at accessibility sizes
        } else {
            HStack { content }    // horizontal at standard sizes
        }
    }
}

isAccessibilitySize 속성은 5개의 접근성 크기를 다룹니다(텍스트가 충분히 커서 가로 레이아웃이 자주 깨지는 경우). 이 패턴은 텍스트가 가로로 맞아야 하는 모든 레이아웃에 적합합니다.

앱은 .dynamicTypeSize(_:) 수정자로 지원되는 범위를 제한할 수 있습니다.

ContentView()
    .dynamicTypeSize(.large ... .accessibility3)

이 제약은 수정된 서브트리에 대해 Dynamic Type 설정을 잘라냅니다. 뷰의 레이아웃이 진정으로 전체 범위를 수용할 수 없을 때 사용하세요. 올바른 기본값은 모든 크기를 지원하고 대신 레이아웃을 적응시키는 것입니다.

커스텀 폰트와 Dynamic Type

커스텀 폰트(Info.plistUIAppFonts 배열을 통해 제공되는 브랜드 폰트)는 기본적으로 Dynamic Type에 따라 크기가 조정되지 않습니다. 가장 간단한 해결책은 Font.customrelativeTo: 매개변수입니다.

// Doesn't scale with Dynamic Type
Text("Brand").font(.custom("MyBrandFont", size: 16))

// Scales with Dynamic Type relative to body
Text("Brand").font(.custom("MyBrandFont", size: 16, relativeTo: .body))

relativeTo: 매개변수는 SwiftUI에 body 스타일의 크기 조정 곡선을 사용하여 커스텀 폰트의 크기를 조정하도록 지시합니다. 사용자의 “Large” 설정에서 폰트 크기는 요청된 16pt이며, 더 큰 설정에서는 SwiftUI가 body 스타일이 사용할 동일한 배율을 적용합니다.

더 정교한 크기 조정(다양한 크기에서의 다른 곡선, 커스텀 광학 처리)을 위해서는 UIKit의 UIFontMetrics를 직접 사용하세요. 패턴은 더 장황하지만 커스텀 폰트가 자주 필요로 하는 크기별 조정을 지원합니다.

타이포그래피가 실패할 때

세 가지 짚어둘 만한 실패 모드가 있습니다.

모든 곳에 고정 크기 커스텀 폰트. 가장 흔한 iOS 앱 접근성 실패: Font.custom("BrandFont", size: 16)(relativeTo: 없음)으로 제공되는 브랜드 폰트는 Dynamic Type을 완전히 무시합니다. 접근성 텍스트 크기를 사용하는 사용자는 시스템 텍스트가 28pt 이상으로 확대되는 동안 브랜드 텍스트는 16pt로 봅니다. 시각적 계층이 뒤바뀝니다. 해결책은 모든 커스텀 폰트 사용에 relativeTo:를 적용하고, 최대 Dynamic Type 설정에서 AccessibilityInspector를 통해 감사하는 것입니다.

강조를 위한 하드코딩된 weight. .font(.body).fontWeight(.bold)로 스타일링된 부제목은 취약합니다. 접근성 크기에서 굵은 본문은 이미 큰 본문과 거의 구별되지 않게 됩니다. 의미론적 .headline 스타일은 Dynamic Type 범위 전체에서 강조를 올바르게 처리합니다. body+bold 대신 이를 사용하세요.

접근성 크기에서 깨지는 레이아웃. .accessibility3에서 넘쳐나는 텍스트 + 아이콘 + 텍스트의 가로 스택은 Dynamic Type이 노출하는 레이아웃 버그입니다. 해결책은 위의 dynamicTypeSize.isAccessibilitySize 적응형 레이아웃 패턴이며, 테스트는 기본 크기뿐만 아니라 QA 중에 최대 Dynamic Type에서 앱을 실행하는 것입니다.

이 패턴이 iOS 26+ 앱에 의미하는 것

세 가지 핵심 사항.

  1. 수동 조정된 포인트 크기가 아닌 의미론적 텍스트 스타일을 사용하세요. .body, .headline, .title2 등은 Dynamic Type, 광학적 크기 조정, 그리고 플랫폼에 맞는 계층을 전달합니다. 수동 조정된 Font.system(size: 17)은 모든 시스템 기능을 무력화하고 Apple이 기본 램프를 조정할 때 좋지 않게 늙어갑니다.

  2. 커스텀 폰트에 항상 relativeTo:를 전달하세요. Font.custom(_, size: _, relativeTo: .body)로 제공되는 브랜드 폰트는 Dynamic Type에 참여합니다. 그것 없이 제공되는 브랜드 폰트는 QA가 최대 텍스트 크기에서만 발견할 사용자별 접근성 회귀입니다.

  3. 접근성 Dynamic Type 크기에서 레이아웃을 테스트하세요. .accessibility3 설정은 대략 Large 기본값의 2배입니다. 표준 크기에서 괜찮아 보이는 레이아웃은 접근성 크기에서 일상적으로 깨집니다. 해결책은 .dynamicTypeSize(...) 제약을 통해 옵트아웃하는 것이 아니라 dynamicTypeSize 환경을 통한 레이아웃 수준의 적응입니다.

전체 Apple Ecosystem 클러스터: 타입화된 App Intents; MCP 서버; 라우팅 질문; Foundation Models; 런타임 vs 도구 LLM 구분; 세 개의 표면; 단일 진실 소스 패턴; 두 개의 MCP 서버; Apple 개발을 위한 hooks; Live Activities; watchOS 런타임; SwiftUI 내부; RealityKit의 공간 정신 모델; SwiftData 스키마 규율; Liquid Glass 패턴; 멀티 플랫폼 출시; 플랫폼 매트릭스; Vision 프레임워크; Symbol Effects; Core ML 추론; Writing Tools API; Swift Testing; Privacy Manifest; 플랫폼으로서의 접근성; 내가 쓰지 않기로 한 것. 허브는 Apple Ecosystem Series에 있습니다. AI 에이전트가 함께하는 더 넓은 iOS 컨텍스트는 iOS Agent Development guide를 참조하세요.

FAQ

SF Pro와 SF Pro Display / SF Pro Text의 차이는 무엇인가요?

SF가 시스템 폰트로 처음 출시된 iOS 9부터 iOS 16까지 SF는 두 개의 별도 폰트로 제공되었습니다. 20pt 미만 크기를 위한 SF Text와 20pt 이상 크기를 위한 SF Display로, 각각 크기 범위에 맞게 수동 조정된 자간과 획 두께를 갖추었습니다. SF Pro Variable은 동일한 Text/Display 분할을 연속적인 광학 크기 축(opsz)으로 통합합니다. 두 폰트는 더 이상 분리되지 않으며, 가변 폰트가 요청된 포인트 크기에 따라 전환을 자동으로 처리합니다.

본문 텍스트에서 모노스페이스 숫자를 어떻게 얻나요?

SwiftUI에서 .monospacedDigit()을 사용하세요.

Text("\(score)").font(.body).monospacedDigit()

이 수정자는 본문 폰트의 비례 숫자를 모노스페이스 숫자로 교체하면서 나머지 텍스트는 비례 형태로 유지합니다. 행 간에 숫자가 정렬되어야 하는 모든 UI(타이머, 점수판, 잔액 표시 등)에 사용하세요.

모든 UI에 SF Pro Rounded를 사용해야 하나요?

아니요. SF Pro Rounded는 일부 컨텍스트에는 어울리지만 다른 컨텍스트에는 어울리지 않는 톤(친근하고 접근하기 쉬운)을 가집니다. Watch 앱의 컴플리케이션, iPhone의 전화 번호 패드, 그리고 특정 Health 앱 화면에서 사용됩니다. 생산성 앱, 뱅킹 앱, 또는 개발자 도구는 일반적으로 사용하지 말아야 합니다. .rounded는 기본값이 아니라 의도적으로 선택하세요.

iPhone 앱의 적절한 Dynamic Type 범위는 무엇인가요?

.xSmall부터 .accessibility5까지 모든 크기를 지원하는 것이 기본입니다. 접근성 크기(.accessibility1부터 .accessibility5까지)는 저시력, 운동 장애, 또는 기타 접근성 요구가 있는 사용자가 iPhone을 사용하는 방식입니다. .dynamicTypeSize(...) 제약을 통해 옵트아웃하는 앱은 그러한 사용자에게 실패합니다. 올바른 접근법은 크기 범위에서 옵트아웃하는 것이 아니라 레이아웃 적응(isAccessibilitySize 패턴)입니다.

앱과 함께 커스텀 가변 폰트를 출시할 수 있나요?

네. 가변 폰트는 다른 커스텀 폰트와 마찬가지로 출시됩니다(Info.plistUIAppFonts에 추가하고 Font.custom으로 참조). SwiftUI에서 가변 축을 다루려면, Font.custom의 기저 CTFont API를 UIFontDescriptor.SymbolicTraits를 통해 사용하거나, 전체 축 제어를 위해 kCTFontVariationAttribute와 함께 CTFontCreateCopyWithAttributes로 내려가세요. SwiftUI에서 가변 축으로의 다리는 시스템 폰트보다 더 장황합니다. 대부분의 앱에서 시스템 폰트가 그 사례를 다룹니다.

참고문헌


  1. Apple Developer: Fonts. SF Pro, SF Pro Rounded, SF Mono, SF Compact, New York을 포함한 시스템 폰트 패밀리 개요. 

  2. Apple Developer: Meet the expanded San Francisco font family (WWDC 2022 세션 110381). SF Pro Variable의 3축 설계(weight, width, optical size) 소개. 

  3. Apple Developer Documentation: Font.custom(_:size:relativeTo:). 선택된 텍스트 스타일을 기준으로 폰트가 Dynamic Type 크기 조정에 참여하도록 하는 커스텀 폰트 이니셜라이저. 

  4. Apple Developer Documentation: DynamicTypeSize. .xSmall부터 .accessibility5까지의 12개 값 enum과 레이아웃 수준 적응을 위한 isAccessibilitySize 술어. 

관련 게시물

Liquid Glass in SwiftUI: Three Patterns From Shipping Return on iOS 26

Apple's Liquid Glass is a one-line SwiftUI API. Three patterns from Return go beyond .glassEffect(): glass on text via C…

19 분 소요

Accessibility As Platform: Personal Voice, Live Speech, Eye Tracking, Music Haptics

Personal Voice, Live Speech, Eye Tracking, Music Haptics, Vocal Shortcuts: accessibility as platform features, not app r…

14 분 소요

The Design Engineer's Agent Stack

Design engineers need agent infrastructure that enforces visual consistency, typography discipline, color compliance, an…

14 분 소요