Введение и проблематика
Шрифты — фундаментальная часть интерфейса любого iOS-приложения. Каждый UILabel, UIButton, UITextField отображает текст с определенным шрифтом. В UIKit за работу со шрифтами отвечает класс UIFont. Системные шрифты — это шрифты, встроенные в iOS (семейство San Francisco), которые доступны без подключения дополнительных файлов.
На iOS системным шрифтом является San Francisco (SF Pro для основного текста, SF Compact для Apple Watch). Apple не позволяет обращаться к нему по имени напрямую — для получения системного шрифта используются специальные фабричные методы класса UIFont.
Базовая теория
UIFont.systemFont(ofSize:)
Основной способ получить системный шрифт — метод класса UIFont.systemFont(ofSize:). Он возвращает шрифт с обычным начертанием (regular) указанного размера в пунктах (points).
❓
Code Example 1: Как создать системный шрифт и применить его к UILabel?
let label = UILabel()
label.frame = CGRect(x: 20, y: 100, width: 300, height: 40)
// Системный шрифт размером 17 пунктов (regular weight)
label.font = UIFont.systemFont(ofSize: 17)
label.text = "Привет, мир!"
view.addSubview(label)
UIFont.systemFont(ofSize:weight:)
Для задания начертания (толщины) шрифта используется метод systemFont(ofSize:weight:). UIFont.Weight — это структура с предопределенными константами, охватывающими весь диапазон от ультратонкого до сверхжирного.
❓
Code Example 2: Как создать системные шрифты с разными начертаниями?
let titleLabel = UILabel()
titleLabel.frame = CGRect(x: 20, y: 100, width: 300, height: 40)
// Жирный шрифт для заголовка
titleLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)
titleLabel.text = "Заголовок"
view.addSubview(titleLabel)
let subtitleLabel = UILabel()
subtitleLabel.frame = CGRect(x: 20, y: 150, width: 300, height: 30)
// Средний шрифт для подзаголовка
subtitleLabel.font = UIFont.systemFont(ofSize: 17, weight: .medium)
subtitleLabel.text = "Подзаголовок"
view.addSubview(subtitleLabel)
let bodyLabel = UILabel()
bodyLabel.frame = CGRect(x: 20, y: 190, width: 300, height: 30)
// Легкий шрифт для второстепенного текста
bodyLabel.font = UIFont.systemFont(ofSize: 15, weight: .light)
bodyLabel.text = "Описание"
view.addSubview(bodyLabel)
Все варианты UIFont.Weight
- .ultraLight — ультратонкий
- .thin — тонкий
- .light — легкий
- .regular — обычный (по умолчанию)
- .medium — средний
- .semibold — полужирный
- .bold — жирный
- .heavy — тяжелый
- .black — сверхжирный
UIFont.boldSystemFont(ofSize:)
Для удобства UIKit предоставляет shortcut-метод для получения жирного системного шрифта. Он эквивалентен вызову systemFont(ofSize:weight: .bold).
❓
Code Example 3: Как использовать boldSystemFont и italicSystemFont?
let boldLabel = UILabel()
boldLabel.frame = CGRect(x: 20, y: 100, width: 300, height: 40)
// Жирный системный шрифт — shortcut для systemFont(ofSize:weight: .bold)
boldLabel.font = UIFont.boldSystemFont(ofSize: 20)
boldLabel.text = "Жирный текст"
view.addSubview(boldLabel)
let italicLabel = UILabel()
italicLabel.frame = CGRect(x: 20, y: 150, width: 300, height: 40)
// Курсивный системный шрифт
italicLabel.font = UIFont.italicSystemFont(ofSize: 17)
italicLabel.text = "Курсивный текст"
view.addSubview(italicLabel)
Текстовые стили и Dynamic Type
UIFont.preferredFont(forTextStyle:)
Apple рекомендует использовать текстовые стили (UIFont.TextStyle) вместо фиксированных размеров шрифтов. Текстовые стили автоматически масштабируются в зависимости от настроек Dynamic Type, которые пользователь выбирает в Настройках > Экран и яркость > Размер текста.
Dynamic Type — это системная функция iOS, позволяющая пользователю глобально изменить размер текста. Приложения, использующие preferredFont, автоматически реагируют на эти изменения, обеспечивая доступность для людей с нарушениями зрения.
❓
Code Example 4: Как использовать текстовые стили с preferredFont?
let largeTitleLabel = UILabel()
largeTitleLabel.frame = CGRect(x: 20, y: 80, width: 350, height: 50)
// Крупный заголовок — масштабируется с Dynamic Type
largeTitleLabel.font = UIFont.preferredFont(forTextStyle: .largeTitle)
largeTitleLabel.text = "Крупный заголовок"
view.addSubview(largeTitleLabel)
let headlineLabel = UILabel()
headlineLabel.frame = CGRect(x: 20, y: 140, width: 350, height: 30)
// Заголовок — полужирный, масштабируется
headlineLabel.font = UIFont.preferredFont(forTextStyle: .headline)
headlineLabel.text = "Headline стиль"
view.addSubview(headlineLabel)
let bodyLabel = UILabel()
bodyLabel.frame = CGRect(x: 20, y: 180, width: 350, height: 30)
// Основной текст — стандартный размер для чтения
bodyLabel.font = UIFont.preferredFont(forTextStyle: .body)
bodyLabel.text = "Body стиль для основного текста"
view.addSubview(bodyLabel)
let captionLabel = UILabel()
captionLabel.frame = CGRect(x: 20, y: 220, width: 350, height: 25)
// Подпись — мелкий текст
captionLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
captionLabel.text = "Caption стиль для подписей"
view.addSubview(captionLabel)
Основные текстовые стили
- .largeTitle — крупный заголовок (34pt при стандартных настройках)
- .title1 — заголовок первого уровня (28pt)
- .title2 — заголовок второго уровня (22pt)
- .title3 — заголовок третьего уровня (20pt)
- .headline — заголовок секции, полужирный (17pt)
- .body — основной текст (17pt)
- .callout — выноска (16pt)
- .subheadline — подзаголовок (15pt)
- .footnote — сноска (13pt)
- .caption1 — подпись первого уровня (12pt)
- .caption2 — подпись второго уровня (11pt)
adjustsFontForContentSizeCategory
Для автоматического обновления шрифта при изменении настроек Dynamic Type в реальном времени (без перезапуска приложения) необходимо установить свойство adjustsFontForContentSizeCategory.
❓
Code Example 5: Как включить автоматическое обновление шрифта при изменении Dynamic Type?
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
// Текстовый стиль с поддержкой Dynamic Type
label.font = UIFont.preferredFont(forTextStyle: .body)
// Включаем автоматическое обновление при смене размера текста в настройках
label.adjustsFontForContentSizeCategory = true
label.text = "Этот текст масштабируется автоматически"
label.numberOfLines = 0
view.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16)
])
⚠️
Без adjustsFontForContentSizeCategory = true шрифт, созданный через preferredFont, будет корректным только на момент создания label. Если пользователь изменит размер текста в настройках и вернется в приложение, шрифт не обновится автоматически.
Практические примеры
Типичный экран с иерархией шрифтов
❓
Code Example 6: Как построить типовой экран с правильной иерархией системных шрифтов?
class ProfileViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let nameLabel = UILabel()
nameLabel.font = UIFont.systemFont(ofSize: 24, weight: .bold)
nameLabel.text = "Иван Петров"
let roleLabel = UILabel()
roleLabel.font = UIFont.systemFont(ofSize: 17, weight: .regular)
roleLabel.textColor = .secondaryLabel
roleLabel.text = "iOS-разработчик"
let bioLabel = UILabel()
bioLabel.font = UIFont.preferredFont(forTextStyle: .body)
bioLabel.adjustsFontForContentSizeCategory = true
bioLabel.numberOfLines = 0
bioLabel.text = "Разрабатываю мобильные приложения на Swift и UIKit."
let joinDateLabel = UILabel()
joinDateLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
joinDateLabel.adjustsFontForContentSizeCategory = true
joinDateLabel.textColor = .tertiaryLabel
joinDateLabel.text = "Присоединился в январе 2024"
let stackView = UIStackView(arrangedSubviews: [
nameLabel, roleLabel, bioLabel, joinDateLabel
])
stackView.axis = .vertical
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20
),
stackView.leadingAnchor.constraint(
equalTo: view.leadingAnchor, constant: 16
),
stackView.trailingAnchor.constraint(
equalTo: view.trailingAnchor, constant: -16
)
])
}
}
❓
Code Example 7: Как задать системный шрифт для UIButton?
let primaryButton = UIButton(type: .system)
primaryButton.frame = CGRect(x: 20, y: 100, width: 280, height: 50)
// Полужирный шрифт для главной кнопки
primaryButton.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
primaryButton.setTitle("Продолжить", for: .normal)
primaryButton.backgroundColor = .systemBlue
primaryButton.setTitleColor(.white, for: .normal)
primaryButton.layer.cornerRadius = 12
view.addSubview(primaryButton)
let secondaryButton = UIButton(type: .system)
secondaryButton.frame = CGRect(x: 20, y: 170, width: 280, height: 44)
// Обычный шрифт для второстепенной кнопки
secondaryButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .regular)
secondaryButton.setTitle("Пропустить", for: .normal)
view.addSubview(secondaryButton)
Визуализация
graph TD
A[UIFont] --> B["systemFont(ofSize:)"]
A --> C["systemFont(ofSize:weight:)"]
A --> D["boldSystemFont(ofSize:)"]
A --> E["italicSystemFont(ofSize:)"]
A --> F["preferredFont(forTextStyle:)"]
B --> G["Regular weight, фиксированный размер"]
C --> H["Любой weight, фиксированный размер"]
D --> I["Bold weight, фиксированный размер"]
E --> J["Italic, фиксированный размер"]
F --> K["Dynamic Type, масштабируемый размер"]
K --> L["adjustsFontForContentSizeCategory = true"]
L --> M["Автообновление при смене настроек"]
Пограничные кейсы
systemFont vs preferredFont
- systemFont(ofSize:) — создает шрифт с фиксированным размером. Если пользователь увеличит размер текста в настройках, шрифт не изменится. Подходит для элементов с жестким дизайном (например, навигационная панель).
- preferredFont(forTextStyle:) — создает шрифт с размером, зависящим от настроек Dynamic Type. Подходит для основного контента, который должен быть доступен пользователям с нарушениями зрения.
Моноширинный системный шрифт
❓
Code Example 8: Как получить моноширинный системный шрифт?
let codeLabel = UILabel()
codeLabel.frame = CGRect(x: 20, y: 100, width: 350, height: 40)
// Моноширинный системный шрифт (SF Mono)
codeLabel.font = UIFont.monospacedSystemFont(ofSize: 14, weight: .regular)
codeLabel.text = "let x = 42"
view.addSubview(codeLabel)
let digitsLabel = UILabel()
digitsLabel.frame = CGRect(x: 20, y: 150, width: 350, height: 40)
// Моноширинные цифры в обычном шрифте (для таймеров, счетчиков)
digitsLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 32, weight: .bold)
digitsLabel.text = "12:34:56"
view.addSubview(digitsLabel)
monospacedDigitSystemFont создает обычный системный шрифт, но с моноширинными цифрами. Это удобно для таймеров и счетчиков, где цифры не должны «прыгать» при смене значений. Все буквы при этом остаются пропорциональными.
Свойства шрифта
❓
Code Example 9: Как получить информацию о шрифте?
let font = UIFont.systemFont(ofSize: 17, weight: .bold)
// Имя семейства шрифта
print(font.familyName) // ".AppleSystemUIFont"
// Полное имя шрифта
print(font.fontName) // ".SFUI-Bold"
// Размер шрифта в пунктах
print(font.pointSize) // 17.0
// Высота строки (ascender + descender + leading)
print(font.lineHeight) // ~20.29
// Расстояние от базовой линии до верхней части символа
print(font.ascender) // ~16.17
// Расстояние от базовой линии до нижней части символа
print(font.descender) // ~-4.12
Плюсы и минусы
| Аспект | Описание |
|---|
| Доступность | Системные шрифты доступны «из коробки» без файлов |
| Dynamic Type | preferredFont поддерживает масштабирование текста |
| Начертания | 9 вариантов weight от ultraLight до black |
| Моноширинность | Есть monospacedSystemFont и monospacedDigitSystemFont |
| Оптимизация | Оптимизированы для рендеринга на устройствах Apple |
| Ограниченность | Только San Francisco, для других шрифтов нужны custom fonts |
| Dynamic Type вручную | systemFont(ofSize:) не масштабируется — нужен preferredFont |
| Шорткаты | boldSystemFont не поддерживает промежуточные weight |
Вопросы интервьюера
Q: Чем отличается UIFont.systemFont(ofSize:) от UIFont.preferredFont(forTextStyle:)?
systemFont(ofSize:) создает шрифт с фиксированным размером в пунктах — он не меняется при изменении настроек Dynamic Type. preferredFont(forTextStyle:) создает шрифт, размер которого зависит от выбранного пользователем размера текста в настройках системы. Apple рекомендует использовать preferredFont для основного контента приложения.
Q: Что такое Dynamic Type и как его поддержать?
Dynamic Type — системная функция iOS, позволяющая пользователю глобально менять размер текста в Настройках. Для поддержки нужно: 1) использовать UIFont.preferredFont(forTextStyle:) для шрифтов, 2) установить adjustsFontForContentSizeCategory = true на UILabel/UITextField для автоматического обновления при смене настроек, 3) использовать Auto Layout с numberOfLines = 0, чтобы текст мог переноситься.
Q: Какие варианты UIFont.Weight существуют?
Девять вариантов от самого тонкого к самому жирному: .ultraLight, .thin, .light, .regular, .medium, .semibold, .bold, .heavy, .black. Значение .regular используется по умолчанию в systemFont(ofSize:).
Q: Чем отличается monospacedSystemFont от monospacedDigitSystemFont?
monospacedSystemFont(ofSize:weight:) возвращает полностью моноширинный шрифт (SF Mono), где все символы имеют одинаковую ширину — подходит для отображения кода. monospacedDigitSystemFont(ofSize:weight:) возвращает обычный пропорциональный шрифт, но с моноширинными цифрами — идеален для таймеров и счетчиков, где цифры не должны сдвигать текст при обновлении.
Q: Зачем нужен adjustsFontForContentSizeCategory?
Без этого свойства шрифт, созданный через preferredFont, корректен только в момент создания view. Если пользователь изменит размер текста в настройках и вернется в приложение, label сохранит старый размер шрифта. Установка adjustsFontForContentSizeCategory = true заставляет UILabel автоматически обновлять шрифт при изменении настроек Dynamic Type.
Источники