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

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

Введение

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 .