Добавление динамической пользовательской типизации в ваш проект iOS — Nimble

Введение
Dynamic Type
— это функция специальных возможностей на iOS и macOS. Эта функция позволяет пользователю установить масштаб текста, который применяется ко всему устройству и обновляется автоматически. Масштаб может быть больше, чем по умолчанию, чтобы помочь пользователям с плохим зрением, или меньше, чтобы заполнить экран большим количеством данных одновременно.

Шрифт UIKit и SwiftUI по умолчанию включает эту функцию внутри, все, что нужно сделать разработчику, это установить шрифт на этикетке со стилем шрифта.
textLabel.font = .preferredFont(forTextStyle: .body)

Добавление пользовательского шрифта
Начните с выбора шрифта для использования в проекте. Рекомендуется использовать шрифт с несколькими весами для передачи различных значений в приложении.
Шрифты можно загрузить с бесплатной платформы, такой как Google Fonts .
Перетащите файлы шрифтов в проект Xcode. Лучше всего создать папку Font
и подпапку для каждого семейства шрифтов.

Откройте основную цель Info.plist
и добавьте ключ UIAppFonts
в виде массива. Внутри массива добавьте имя файла и расширение для каждого из включенных шрифтов.

Файлы шрифтов теперь импортированы в проект, и их можно использовать, создав экземпляр UIFont.
textLabel.font = UIFont(name: "Font-Name", size: UIFont.labelFontSize)
for family in UIFont.familyNames.sorted() {
let names = UIFont.fontNames(forFamilyName: family)
print("Family: \(family) Font names: \(names)")
}
// Family: Zen Old Mincho Font names: ["ZenOldMincho-Regular", "ZenOldMincho-Bold"]
Самый простой способ использовать пользовательский шрифт в проекте Swift — назначить шрифт непосредственно метке.
textLabel.font = UIFont(name: "ZenOldMincho-Regular", size: UIFont.labelFontSize)
Настройка размера текста на iOS
Настройки специальных возможностей
Запустите приложение «Настройки» на устройстве или симуляторе. Перейдите к Accessibility > Display & Text Size > Larger Text
. Ползунок изменит размер шрифта для системы.

Изменение размера текста не приведет к запуску какого-либо приложения, но макет будет переделан.
⚙️ Это для iOS 15, другие версии iOS будут иметь небольшую разницу, но опция должна быть доступна для поиска с помощью Text Size
или Larger Text
.
Переопределение среды Xcode
В Xcode после успешного запуска приложения на симуляторе найдите файл Debug Area
. Содержит кнопку Debug Area
с символом переключателей. Эта кнопка должна вызывать Environment Overrides
меню симулятора. Размер текста можно настроить с помощью ползунка для Dynamic Type
.

Environment Overrides
переопределит настройки симулятора и вернет самые последние настройки симулятора при выключении.
При повторном запуске приложения с измененными настройками устройства должен отображаться новый текст в новом масштабе. Однако изменить размер шрифта во время работы приложения невозможно. Чего не хватает, так это инструмента масштабирования. Измените код для установки текста метки следующим образом:
let customFont = UIFont(name: "ZenOldMincho-Regular", size: UIFont.labelFontSize)!
label.font = UIFontMetrics.default.scaledFont(for: customFont)
label.adjustsFontForContentSizeCategory = true
Чтобы использовать UIFontMetrics
возможности, мы вводим помощника:
func customFont(
_ font: DynamicFont,
forTextStyle style: UIFont.TextStyle,
overrideFontSize: UIContentSizeCategory? = nil
) -> UIFont?
Помимо добавления еще одного уровня в UIFontMetrics
, помощник служит шлюзом для функций, которые может выполнять приложение. В перечень требований входят:
- Все пользовательские шрифты могут использоваться в рамках одной функции.
- Размеры текста различаются по стилю текста (т. е. основной части, заголовку, подписи и т. д.).
- Размер текста можно изменить в настройках приложения.
Сначала создадим протокол выбора шрифта: DynamicFont
.
protocol DynamicFont {
func fontName() -> String
func fontSize(style: UIFont.TextStyle) -> CGFloat
}
Пример DynamicFont
для нашей демонстрации выглядит следующим образом.
enum ZenOldMincho: DynamicFont {
case regular
case bold
func fontName() -> String {
switch self {
case .regular: return "ZenOldMincho-Regular"
case .bold: return "ZenOldMincho-Bold"
}
}
func fontSize(style: UIFont.TextStyle) -> CGFloat {
switch style {
case .largeTitle: return 34.0
case .title1: return 28.0
case .title2: return 22.0
case .title3: return 20.0
case .headline: return 18.0
case .body: return 17.0
case .callout: return 16.0
case .subheadline: return 15.0
case .footnote: return 13.0
case .caption1: return 12.0
case .caption2: return 11.0
default: return 17.0
}
}
}
extension UIFont {
static func customFont(
_ font: DynamicFont,
forTextStyle style: UIFont.TextStyle,
overrideFontSize: UIContentSizeCategory? = nil
) -> UIFont? {
guard let customFont = UIFont(name: font.fontName(), size: font.fontSize(style: style)) else { return nil }
let scaledFont: UIFont
let metrics = UIFontMetrics(forTextStyle: style)
return scaledFont = metrics.scaledFont(
for: customFont, compatibleWith: UITraitCollection(
preferredContentSizeCategory: overrideFontSize ?? .unspecified
)
)
}
}
textLabel.font = .customFont(ZenOldMincho.regular, forTextStyle: .body)
overridedLabel.font = .customFont(ZenOldMincho.bold, forTextStyle: .headline, overrideFontSize: .small)
Вышеупомянутую версию помощника можно сразу использовать на iOS 11. UILabel
Должна быть adjustsFontForContentSizeCategory
установлена true
возможность автоматического обновления, когда система меняет размер текста.
label.adjustsFontForContentSizeCategory = true
label.font = .customFont(ZenOldMincho.regular, forTextStyle: .body)
label.adjustsFontForContentSizeCategory = false
label.font = .customFont(ZenOldMincho.regular, forTextStyle: .body, overrideFontSize: .small)
iOS 10
UIFontMetrics.scaledFont
доступно только на iOS 11 и более поздних версиях. Для iOS 10 масштаб шрифта необходимо указать вручную для каждого стиля текста и категории размера, а также обновить шрифт при изменении размера текста в системе.
Масштаб шрифта
Ввести новый протокол:
protocol DynamicFontIOS10 {
func font(for style: UIFont.TextStyle, sizeCategory: UIContentSizeCategory) -> UIFont?
}
extension ZenOldMincho: DynamicFontIOS10 {
func font(for style: UIFont.TextStyle, sizeCategory: UIContentSizeCategory) -> UIFont? {
guard let style = ZenOldMincho.fontSizeTable[style],
let size = style[sizeCategory]
else { return nil }
return UIFont(name: fontName(), size: size)
}
}
extension ZenOldMincho {
static let fontSizeTable: [UIFont.TextStyle: [UIContentSizeCategory: CGFloat]] = [
.headline: [
.accessibilityExtraExtraExtraLarge: 23.0,
.accessibilityExtraExtraLarge: 23.0,
.accessibilityExtraLarge: 23.0,
.accessibilityLarge: 23.0,
.accessibilityMedium: 23.0,
.extraExtraExtraLarge: 23.0,
.extraExtraLarge: 21.0,
.extraLarge: 19.0,
.large: 17.0,
.medium: 16.0,
.small: 15.0,
.extraSmall: 14.0
],
.body: [
.accessibilityExtraExtraExtraLarge: 53.0,
.accessibilityExtraExtraLarge: 47.0,
.accessibilityExtraLarge: 40.0,
.accessibilityLarge: 33.0,
.accessibilityMedium: 28.0,
.extraExtraExtraLarge: 23.0,
.extraExtraLarge: 21.0,
.extraLarge: 19.0,
.large: 17.0,
.medium: 16.0,
.small: 15.0,
.extraSmall: 14.0
]
// Fill with all text style
}
Функцию customFont
необходимо будет изменить, чтобы она соответствовала масштабированию iOS 10.
static func customFont(
_ font: DynamicFont,
forTextStyle style: UIFont.TextStyle,
overrideFontSize: UIContentSizeCategory? = nil
) -> UIFont? {
guard let customFont = UIFont(name: font.fontName(), size: font.fontSize(style: style)) else { return nil }
let scaledFont: UIFont
if #available(iOS 11.0, *) {
let metrics = UIFontMetrics(forTextStyle: style)
scaledFont = metrics.scaledFont(
for: customFont, compatibleWith: UITraitCollection(
preferredContentSizeCategory: overrideFontSize ?? .unspecified
)
)
} else {
let sizeCategory = overrideFontSize ?? UIApplication.shared.preferredContentSizeCategory
guard let fontIOS10 = font as? DynamicFontIOS10,
let customFontIOS10 = fontIOS10.font(for: style, sizeCategory: sizeCategory)
else { return customFont }
scaledFont = customFontIOS10
}
return scaledFont
}
Автоматически обновлять размер текста
В настоящее время приложение будет отображать правильный шрифт только при первой загрузке. Страница должна будет устанавливать шрифт для каждого компонента каждый раз, когда изменяется размер текста. Это можно сделать, прослушав NotificationCenter
уведомление пользователя UIContentSizeCategory.didChangeNotification
.
protocol DynamicFontController {
func setUpContentSizeNotification(disposeBag: DisposeBag)
func updateFonts(notification: Notification)
}
extension DynamicFontController where Self: UIViewController {
func setUpContentSizeNotification(disposeBag: DisposeBag) {
NotificationCenter.default.rx.notification(UIContentSizeCategory.didChangeNotification, object: nil)
.withUnretained(self)
.take(until: rx.deallocated)
.subscribe { owner, value in
owner.updateFonts(notification: value)
}
.disposed(by: disposeBag)
}
}
Для не RxSwift
не забудьте позвонить NotificationCenter.removeObserver
в deinit
.
Размер шрифта будет обновляться автоматически по мере изменения размера текста в системе.
SwiftUI
SwiftUI имеет модификатор представления специально для изменения шрифта.
.font(.custom(name, size: size))
Text("Hello").font(.custom("ZenOldMincho-Regular", size: 16.0))
Обновление размера шрифта
Чтобы разрешить обновление размера шрифта, будет использоваться новый модификатор представления для учета изменения размера текста в системе.
Если проект чисто SwiftUI, существующее объявление customFont(_ font: DynamicFont, forTextStyle style: UIFont.TextStyle, overrideFontSize: UIContentSizeCategory? = nil) -> UIFont?
можно игнорировать.
Объявление эквивалентного метода SwiftUI:
extension View {
func scaledFont(
font: DynamicFont,
forTextStyle style: UIFont.TextStyle,
overrideFontSize: ContentSizeCategory? = nil
) -> some View {
return modifier(
ScaledFont(
name: font.fontName(),
size: font.fontSize(style: style),
overrideFontSize: overrideFontSize
)
)
}
}
Декларация ScaledFont
:
struct ScaledFont: ViewModifier {
@Environment(\.sizeCategory) var sizeCategory
var name: String
var size: CGFloat
var overrideFontSize: ContentSizeCategory?
func body(content: Content) -> some View {
let scaledSize = UIFontMetrics.default.scaledValue(for: size)
return content.font(.custom(name, size: scaledSize))
}
}
С помощью модификатора scaledFont
мы можем изменить шрифт на любом представлении с похожей подписью. Результирующее представление будет обновлять размер шрифта по мере изменения настроек системы.
Text("Hello")
.scaledFont(font: ZenOldMincho.bold, forTextStyle: .headline)
Текущая реализация ScaledFont: ViewModifier
не включает переопределение размера шрифта в приложении.
Чтобы разрешить переопределение, UIFontMetrics.scaledValue(for:, compatibleWith:)
будет использоваться. Это то же самое в UIKit
реализации; однако преобразование из ContentSizeCategory
в UIContentSizeCategory
, чтобы удовлетворить scaledValue
ввод, возможно только для iOS 14 и выше.
Измените код для ScaledFont.body
:
func body(content: Content) -> some View {
let scaledSize = UIFontMetrics.default.scaledValue(for: size, compatibleWith: UITraitCollection(
preferredContentSizeCategory: UIContentSizeCategory(overrideFontSize)
))
return content.font(.custom(name, size: scaledSize))
}
Теперь с помощью модификатора scaledFont
мы можем переопределить размер текста.
Text("Hello")
.scaledFont(font: ZenOldMincho.bold, forTextStyle: .headline, overrideFontSize: .extraLarge)
Swift постоянно развивается, чтобы упростить реализацию специальных возможностей для улучшения качества жизни пользователей. Вспомогательные функции, представленные в этом блоге, можно удобно интегрировать в существующие и новые проекты.
Динамический шрифт позволит опытным пользователям с первого взгляда увидеть больше данных, а также поможет пользователям с проблемами зрения без проблем использовать приложение. Динамический шрифт — одна из многих специальных возможностей, которую разработчики могут использовать для улучшения своих приложений.
Исходный код этого проекта доступен в нашем репозитории .
использованная литература
Автоматическое масштабирование пользовательских шрифтов с помощью Dynamic Type Как использовать Dynamic Type с пользовательским шрифтом Практика Dynamic Type
Первоначально опубликовано на https://nimblehq.co .