SF Pro: osie zmienne, optyczne skalowanie i kontrakt Dynamic Type
SF Pro, systemowy font firmy Apple od czasów iOS 9 / OS X El Capitan z 2015 roku, jest fontem zmiennym z trzema osiami (waga, szerokość, rozmiar optyczny), zaprojektowaną rodziną obejmującą wariant bezszeryfowy (SF Pro), zaokrąglony (SF Pro Rounded), monospace (SF Mono), wariant kompaktowy (SF Compact, używany w watchOS) oraz szeryfowego towarzysza (New York)1. Font został zaprojektowany wokół kontraktu Dynamic Type: jedenaście stylów tekstowych SwiftUI (.largeTitle, .title, .body, .callout, .footnote itd.) używa domyślnie SF Pro i wszystkie skalują się automatycznie, gdy użytkownik zmienia preferowany rozmiar tekstu w Ustawieniach.
Większość aplikacji wykorzystuje jeden lub dwa z tych jedenastu stylów, ignoruje Dynamic Type poza domyślnym zachowaniem i nigdy nie sięga po osie zmienne. Rezultatem jest typografia, która działa, ale nie posługuje się słownikiem platformy. Niniejszy wpis omawia rodzinę systemowego fontu, osie zmienne, jedenaście stylów semantycznych, kontrakt Dynamic Type oraz przypadki, w których fonty niestandardowe wymagają jawnej pracy nad skalowaniem, by w nim uczestniczyć.
TL;DR
- SF Pro Variable udostępnia trzy osie: wagę (
wght), szerokość (wdth) i rozmiar optyczny (opsz)2. Skalowanie optyczne jest automatyczne w oparciu o rozmiar w punktach; waga i szerokość są dostępne poprzezFont.WeightiFont.Widthw SwiftUI. - Rodzina systemowa obejmuje SF Pro (domyślny), SF Pro Rounded (przyjazne elementy interfejsu), SF Mono (kod, techniczne UI), SF Compact (watchOS, wąskie konteksty) oraz New York (szeryfowy towarzysz do czytania edytorialnego).
- Jedenaście stylów tekstowych SwiftUI automatycznie wspiera Dynamic Type.
.bodyto bezpieczny domyślny wybór;.largeTitleaż po.caption2pokrywają hierarchię platformy. - Fonty niestandardowe domyślnie nie skalują się wraz z Dynamic Type. Należy użyć
Font.custom("Name", size: 16, relativeTo: .body), aby włączyć font do skalowania Dynamic Type3. @Environment(\.dynamicTypeSize)umożliwia widokowi dostosowanie układu do bieżącego rozmiaru tekstu; zakres wartości obejmuje 12 rozmiarów: od.xSmalldo.accessibility54.
Rodzina fontu systemowego
Apple dostarcza pięć rodzin, które wspólnie tworzą doświadczenie fontu systemowego.
SF Pro
Domyślny font dla każdej platformy Apple. iPhone, iPad, Mac i Vision wykorzystują SF Pro do tekstu podstawowego, nagłówków i większości UI. Font posiada szerokie pokrycie językowe (alfabet łaciński, grecki, cyrylica, arabski, hebrajski, dewanagari itd.), natywnie wspiera układ od prawej do lewej i obejmuje zaprojektowane warianty kapitalików, alternatywnych cyfr (proporcjonalnych vs. tabelarycznych) oraz alternatyw stylistycznych.
Dostęp w SwiftUI poprzez font systemowy:
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
Wariant zaokrąglony, zaprojektowany z myślą o przyjaznym, przystępnym UI: komplikacje Apple Watch, pierścienie Fitness, niektóre wskaźniki kierunkowe w Mapach, Find My oraz wybrane powierzchnie aplikacji Health. Zaokrąglone formy komunikują miękkość; warto sięgać po nie ze względu na ton, a nie tylko dla wizualnej różnorodności.
Text("Hello").font(.system(.body, design: .rounded))
SF Mono
Rodzina monospace dla kodu, terminalowych UI oraz każdego kontekstu, w którym istotne jest wyrównanie komórek znakowych. SF Mono to domyślny font w Xcode, w ustawieniu monospace aplikacji Terminal oraz w każdym widoku SwiftUI, który żąda .monospaced.
Text("let x = 42").font(.system(.body, design: .monospaced))
SF Compact
Węższa rodzina używana w watchOS, w wyróżnieniach focusu na tvOS oraz w niektórych kontekstach Mac, gdzie przestrzeń pozioma jest ograniczona. Węższe formy zachowują wysokość x, redukując jednocześnie poziomy rozstaw, dzięki czemu sprawdzają się w wymiarach tarczy Apple Watch, gdzie SF Pro wydawałby się ścieśniony.
Aplikacje watchOS automatycznie używają SF Compact poprzez font systemowy; aplikacje iOS zazwyczaj nie muszą żądać go jawnie.
New York
Rodzina szeryfowa zaprojektowana jako towarzysz do czytania edytorialnego. New York pojawia się w aplikacji Books dla tekstów długoformatowych, w Notatkach dla notatek w stylu pisma odręcznego oraz w SwiftUI poprzez projekt .serif.
Text("Long-form essay").font(.system(.body, design: .serif))
Szeryfowy towarzysz rzadko pojawia się w UI aplikacji. Warto sięgać po niego z rozmysłem (tryb czytania, cytowany fragment, treść artykułu), a nie używać go jako domyślnego.
Trzy osie zmienne
SF Pro Variable koduje trzy osie, których kombinacja produkuje każdy glif:
Waga (wght)
Dziewięć nazwanych wag: .ultraLight, .thin, .light, .regular, .medium, .semibold, .bold, .heavy, .black. Font zmienny interpoluje między nimi w sposób ciągły, ale API w SwiftUI udostępnia nazwane wartości.
Text("Heading").font(.system(.title, weight: .semibold))
Waga komunikuje hierarchię akcentowania: .regular dla tekstu podstawowego, .semibold lub .bold dla nagłówków, .medium dla aktywnych elementów paska narzędzi, .light dla etykiet odsuniętych na drugi plan. Style semantyczne (.headline, .subheadline) zawierają sensowne domyślne wagi; po jawne wagi warto sięgać tylko wtedy, gdy styl semantyczny nie ma odpowiedniego kształtu.
Szerokość (wdth)
Cztery nazwane szerokości w API od iOS 16+: .compressed, .condensed, .standard, .expanded. Oś szerokości wpływa na poziomy rozstaw bez zmiany wagi czy charakteru wizualnego. Można jej użyć dla ciasnego UI (pasek nawigacyjny z wieloma elementami) lub dla wizualnej tekstury (nagłówek o wielkości display, który ma zyskać większą obecność poziomą).
Szerokość stosuje się poprzez modyfikator widoku .fontWidth(_:) w SwiftUI (lub łańcuchowo na Font poprzez .width(_:)):
Text("Compressed")
.font(.system(.title, weight: .bold))
.fontWidth(.compressed)
Szerokość rzadko jest właściwą osią dla tekstu podstawowego; dobrze sprawdza się w rozmiarach display, gdzie typografia stanowi część projektu.
Rozmiar optyczny (opsz)
Najambitniejsza technicznie oś. Dawne warianty SF Pro — SF Text i SF Display — to teraz ciągły gradient zakodowany we foncie zmiennym. W rozmiarach poniżej 20 punktów system stosuje korekty optyczne SF Text (szerszy odstęp międzyliterowy, nieco grubsze kreski, bardziej otwarte światło wewnątrz znaków). Powyżej 20 punktów przejmuje SF Display (ciaśniejszy odstęp, dopracowane proporcje). Przejście jest płynne.
Oś opsz jest automatyczna. Aplikacje nie odwołują się do niej jawnie; system odczytuje żądany rozmiar w punktach i rozstrzyga właściwe skalowanie optyczne. Implikacja: typografia w małych rozmiarach UI ma inne proporcje niż ten sam font w rozmiarach nagłówkowych — i to z założenia. Fonty niestandardowe pozbawione skalowania optycznego wyglądają dobrze w jednym rozmiarze i źle w innych; automatyczna obsługa SF Pro całkowicie omija tę pułapkę.
Jedenaście stylów tekstowych
Font.TextStyle w SwiftUI definiuje jedenaście stylów semantycznych, które wszystkie uczestniczą w Dynamic Type4:
| Styl | Domyślny rozmiar (Large) | Typowe zastosowanie |
|---|---|---|
.largeTitle |
34 pt | Nagłówki najwyższego poziomu, tekst hero |
.title |
28 pt | Nagłówki sekcji |
.title2 |
22 pt | Nagłówki podsekcji |
.title3 |
20 pt | Drobniejsze nagłówki |
.headline |
17 pt | Akcentowany tekst podstawowy, tytuły wierszy listy |
.body |
17 pt | Domyślny tekst podstawowy |
.callout |
16 pt | Pomocniczy tekst podstawowy, podpisy w kontekście |
.subheadline |
15 pt | Drugorzędne nagłówki, tekst metadanych |
.footnote |
13 pt | Drobny tekst pomocniczy |
.caption |
12 pt | Podpisy obrazków, drobny druk |
.caption2 |
11 pt | Minimalny czytelny tekst |
Kolumna „Domyślny rozmiar” pokazuje rozmiar przy ustawieniu „Large” Dynamic Type użytkownika (domyślnym dla systemu). Każdy styl skaluje się w górę lub w dół, gdy użytkownik dostosowuje preferowany rozmiar tekstu; względna hierarchia pozostaje nienaruszona.
Właściwym podejściem jest bezpośrednie używanie stylów semantycznych:
VStack(alignment: .leading) {
Text("Title").font(.title)
Text("Subtitle").font(.subheadline).foregroundStyle(.secondary)
Text("Body content here.").font(.body)
}
Hierarchia jest zachowana przy każdym ustawieniu Dynamic Type, konwencje Apple HIG są honorowane, a typografia odpowiada na preferencje dostępności użytkownika bez kodu specyficznego dla aplikacji.
Kontrakt Dynamic Type
Dynamic Type to kontrolowane przez użytkownika ustawienie rozmiaru tekstu w Ustawienia > Dostępność > Wyświetlacz i rozmiar tekstu > Większy tekst. Wartość przepływa przez środowisko jako DynamicTypeSize, z dwunastoma wartościami od .xSmall do .accessibility54:
- Standardowe rozmiary:
.xSmall,.small,.medium,.large(domyślny),.xLarge,.xxLarge,.xxxLarge - Rozmiary dostępności:
.accessibility1,.accessibility2,.accessibility3,.accessibility4,.accessibility5
Aplikacje używające semantycznych stylów tekstu otrzymują skalowanie za darmo. Aplikacje, które chcą dostosować układ do bieżącego rozmiaru, odczytują środowisko:
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
}
}
}
Właściwość isAccessibilitySize pokrywa pięć rozmiarów dostępności (gdzie tekst jest na tyle duży, że układy poziome często się rozpadają). Wzorzec jest właściwy dla każdego układu, który zależy od pomieszczenia tekstu w poziomie.
Aplikacje mogą ograniczyć obsługiwany zakres modyfikatorem .dynamicTypeSize(_:):
ContentView()
.dynamicTypeSize(.large ... .accessibility3)
Ograniczenie przycina ustawienie Dynamic Type dla zmodyfikowanego poddrzewa. Warto go użyć, gdy układ widoku rzeczywiście nie jest w stanie pomieścić pełnego zakresu; właściwym domyślnym wyborem jest wspieranie każdego rozmiaru i adaptacja układu w zamian.
Fonty niestandardowe a Dynamic Type
Fonty niestandardowe (font marki dostarczany przez tablicę UIAppFonts w Info.plist) domyślnie nie skalują się wraz z Dynamic Type. Najprostszą poprawką jest parametr relativeTo: w Font.custom:
// 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))
Parametr relativeTo: informuje SwiftUI, by skalował font niestandardowy z użyciem krzywej skalowania stylu body. Rozmiar fontu przy ustawieniu „Large” użytkownika to żądane 16pt; przy większych ustawieniach SwiftUI stosuje ten sam mnożnik, jakiego użyłby styl body.
Dla bardziej wyrafinowanego skalowania (różne krzywe w różnych rozmiarach, niestandardowa obsługa optyczna) można użyć bezpośrednio UIFontMetrics z UIKit. Wzorzec jest bardziej rozbudowany, ale wspiera korekty per-rozmiar, których fonty niestandardowe często wymagają.
Kiedy typografia zawodzi
Trzy tryby awarii warte nazwania:
Wszędzie fonty niestandardowe o stałym rozmiarze. Najczęstsza awaria dostępności w aplikacjach iOS: font marki dostarczany jako Font.custom("BrandFont", size: 16) (bez relativeTo:) całkowicie ignoruje Dynamic Type. Użytkownicy z dostępnościowymi rozmiarami tekstu widzą tekst marki w 16pt, podczas gdy tekst systemowy skaluje się do 28pt+; hierarchia wizualna ulega odwróceniu. Poprawką jest relativeTo: przy każdym użyciu fontu niestandardowego, audytowane przez AccessibilityInspector przy maksymalnym ustawieniu Dynamic Type.
Twardo zakodowane wagi dla akcentowania. Podtytuł stylizowany .font(.body).fontWeight(.bold) jest kruchy: w rozmiarach dostępności pogrubiony body staje się niemal nie do odróżnienia od body, który już jest duży. Semantyczny styl .headline poprawnie obsługuje akcentowanie w całym zakresie Dynamic Type; warto użyć go zamiast body+bold.
Układy, które rozpadają się w rozmiarach dostępności. Poziomy stos tekst + ikona + tekst, który przepełnia się przy .accessibility3, to błąd układu, który Dynamic Type ujawnia. Poprawką jest powyższy wzorzec adaptacyjnego układu dynamicTypeSize.isAccessibilitySize; testem jest uruchomienie aplikacji przy maksymalnym Dynamic Type podczas QA, a nie tylko przy domyślnym rozmiarze.
Co ten wzorzec oznacza dla aplikacji iOS 26+
Trzy wnioski.
-
Należy używać semantycznych stylów tekstu, a nie ręcznie dobranych rozmiarów w punktach.
.body,.headline,.title2itp. niosą Dynamic Type, skalowanie optyczne i hierarchię poprawną dla platformy. Ręcznie dobraneFont.system(size: 17)neutralizuje każdą funkcję systemową i źle starzeje się, gdy Apple modyfikuje domyślną drabinę. -
Zawsze należy przekazywać
relativeTo:w fontach niestandardowych. Font marki dostarczany jakoFont.custom(_, size: _, relativeTo: .body)uczestniczy w Dynamic Type. Font marki dostarczany bez tego to regresja dostępności per-użytkownik, którą QA wychwyci tylko przy maksymalnym rozmiarze tekstu. -
Należy testować układy w dostępnościowych rozmiarach Dynamic Type. Ustawienie
.accessibility3to mniej więcej 2x rozmiar domyślny Large. Układy, które wyglądają dobrze w standardowych rozmiarach, rutynowo rozpadają się w rozmiarach dostępności. Poprawką jest adaptacja na poziomie układu poprzez środowiskodynamicTypeSize, a nie wypisywanie się poprzez ograniczenia.dynamicTypeSize(...).
Pełny klaster Apple Ecosystem: typowane App Intents; serwery MCP; pytanie o routing; Foundation Models; rozróżnienie runtime vs tooling LLM; trzy powierzchnie; wzorzec single source of truth; Dwa serwery MCP; hooki dla rozwoju Apple; Live Activities; kontrakt runtime watchOS; wnętrze SwiftUI; przestrzenny model mentalny RealityKit; dyscyplina schematu SwiftData; wzorce Liquid Glass; wieloplatformowe wydanie; matryca platform; Vision framework; Symbol Effects; inferencja Core ML; Writing Tools API; Swift Testing; Privacy Manifest; Dostępność jako platforma; o czym odmawiam pisać. Hub znajduje się w serii Apple Ecosystem. Szerszy kontekst iOS-z-agentami-AI w przewodniku iOS Agent Development.
FAQ
Jaka jest różnica między SF Pro a SF Pro Display / SF Pro Text?
Od iOS 9 (kiedy SF po raz pierwszy pojawił się jako font systemowy) aż do iOS 16 SF dostarczany był jako dwa odrębne fonty: SF Text dla rozmiarów poniżej 20pt i SF Display dla rozmiarów 20pt i powyżej, każdy z ręcznie dobranymi odstępami międzyliterowymi i grubością kresek dla swojego zakresu rozmiaru. SF Pro Variable konsoliduje ten sam podział Text/Display w ciągłą oś rozmiaru optycznego (opsz). Te dwa fonty nie są już odrębne; font zmienny obsługuje przejście automatycznie na podstawie żądanego rozmiaru w punktach.
Jak uzyskać monospace cyfry w tekście podstawowym?
Należy użyć .monospacedDigit() w SwiftUI:
Text("\(score)").font(.body).monospacedDigit()
Modyfikator zamienia proporcjonalne cyfry fontu body na cyfry monospace, zachowując proporcjonalność reszty tekstu. Warto go użyć w każdym UI, gdzie cyfry muszą być wyrównane między wierszami (timery, tablice wyników, wyświetlacze sald).
Czy powinienem używać SF Pro Rounded dla całego UI?
Nie. SF Pro Rounded niesie ze sobą ton (przyjazny, przystępny), który pasuje do niektórych kontekstów, a do innych nie. Komplikacje aplikacji Watch, klawiatura numeryczna telefonu w iPhone oraz pewne powierzchnie aplikacji Health używają go. Aplikacja produktywności, aplikacja bankowa lub narzędzie deweloperskie zazwyczaj nie powinny. Po .rounded warto sięgać z rozmysłem, a nie jako po wartość domyślną.
Jaki jest właściwy zakres Dynamic Type dla aplikacji iPhone?
Domyślnie warto wspierać każdy rozmiar od .xSmall do .accessibility5. Rozmiary dostępności (.accessibility1 aż po .accessibility5) to sposób, w jaki użytkownicy z niedowidzeniem, trudnościami motorycznymi lub innymi potrzebami dostępności korzystają z iPhone’a. Aplikacja, która wypisuje się przez ograniczenia .dynamicTypeSize(...), zawodzi tych użytkowników. Właściwym ruchem jest adaptacja układu (wzorzec isAccessibilitySize), a nie wypisywanie się z zakresu rozmiarów.
Czy mogę dostarczyć niestandardowy font zmienny z moją aplikacją?
Tak. Fonty zmienne dostarcza się jak każdy inny font niestandardowy (dodać do UIAppFonts w Info.plist, odwołać się przez Font.custom). Aby adresować osie zmienne z poziomu SwiftUI, należy użyć leżących pod spodem API CTFont z Font.custom poprzez UIFontDescriptor.SymbolicTraits lub, dla pełnej kontroli osi, zejść do CTFontCreateCopyWithAttributes z kCTFontVariationAttribute. Most ze SwiftUI do osi zmiennych jest bardziej rozbudowany niż dla fontów systemowych; dla większości aplikacji fonty systemowe pokrywają potrzeby.
Bibliografia
-
Apple Developer: Fonts. Przegląd rodziny fontu systemowego obejmujący SF Pro, SF Pro Rounded, SF Mono, SF Compact i New York. ↩
-
Apple Developer: Meet the expanded San Francisco font family (sesja WWDC 2022 nr 110381). Wprowadzenie trójosiowego projektu SF Pro Variable (waga, szerokość, rozmiar optyczny). ↩
-
Apple Developer Documentation:
Font.custom(_:size:relativeTo:). Inicjalizator fontu niestandardowego, który włącza font do skalowania Dynamic Type względem wybranego stylu tekstu. ↩ -
Apple Developer Documentation:
DynamicTypeSize. Dwunastowartościowy enum od.xSmalldo.accessibility5plus predykatisAccessibilitySizedo adaptacji na poziomie układu. ↩↩↩