132. Переключатель для поддержки темной темы
Возьмем рабочий проект, который поддерживает только светлую тему, и добавим туда возможность включить/выключить поддержку темной темы - не пикер темы, а именно переключатель для поддержки фичи.
Больше деталей
Есть iOS-приложение с жизненным циклом на UIKit (AppDelegate), которое никто раньше не адаптировал для темной темы.
В Info.plist есть ключ UIUserInterfaceStyle со значением Light, что по умолчанию включает всегда светлую тему, даже если на девайсе активна темная тема.
Пользователи давно просят добавить поддержку темной темы, и пришло время это реализовать. Процесс займет неопределенное время, в ходе которого будет сделано несколько сборок для тестирования.
Чтобы было удобно переключаться между светлой и темной темами, добавим переключатель в дебаг-меню для тестовых сборок, куда нет доступа у пользователей - вот об этом и будет статья.
План
- Удаляем из
Info.plistнеактуальный ключUIUserInterfaceStyle - Добавляем тоггл в дебаг-меню
- Складываем логику в отдельный сервис
- Дорабатываем
AppDelegate
Первые 2 пункта не очень интересные: удалить ключ из Info.plist очень легко, а добавить тоггл в дебаг-меню - по-разному, в зависимости от проекта.
В нашем случае добавить тоггл было легко, поэтому перехожу к третьему шагу.
Складываем логику в отдельный сервис
Управление поддержкой темной темы - это новая фича, которую хочется покрыть тестами, поэтому сделаем небольшой сервис, который будет заниматься этой логикой, и позже напишем тесты.
Для управления поддержкой темной темы нам нужна ссылка на window, которая в нашем случае есть в AppDelegate, и значение тоггла из дебаг-меню:
import UIKit
enum DarkModeSwitcher {
@discardableResult
static func applyIfNeeded(window: UIWindow?, isDarkModeEnabled: Bool) -> Bool {
guard let window else {
return false
}
let desired: UIUserInterfaceStyle = isDarkModeEnabled ? .unspecified : .light
guard window.overrideUserInterfaceStyle != desired else {
return false
}
window.overrideUserInterfaceStyle = desired
return true
}
}
- Включение
darkModeвключает.unspecified, если ранее было.lightи возвращаетtrue - Отключение
darkModeустанавливает.light, если ранее было.unspecifiedи возвращаетtrue - Повторный вызов с тем же значением не меняет стиль и возвращает
false - Вызов с
nilокном безопасен и возвращаетfalse
Дорабатываем AppDelegate
Первым делом дорабатываем метод application(_:didFinishLaunchingWithOptions:), который вызывается при запуске приложения:
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// ...
DarkModeSwitcher.applyIfNeeded(
window: window,
isDarkModeEnabled: isDarkModeEnabled // <- ссылка на тоггл в дебаг-меню
)
// ...
return true
}
Чтобы тестировать было удобнее, вызовем DarkModeSwitcher еще и при "разворачивании" приложения, т.е. когда оно переходит в активное состояние - таким образом можно будет сразу после переключения тоггла в дебаг-меню свернуть и развернуть приложение и сразу же увидеть темную тему (если она активна на устройстве):
func applicationDidBecomeActive(_: UIApplication) {
DarkModeSwitcher.applyIfNeeded(
window: window,
isDarkModeEnabled: isDarkModeEnabled
)
}
Готово! Теперь можно продолжать процесс переезда на темную тему и не волноваться о том, что пользователи могут увидеть промежуточный результат, ну и не "солить" код в отдельной ветке.
Бонус: SwiftUI
Как уже можно было догадаться, если жизненный цикл вашего приложения использует SwiftUI, то достаточно модификатора preferredColorScheme - в него нужно передать значение тоггла из дебаг-меню и ... готово!
Заключение
Код сервиса-переключателя для темной темы вместе с тестами можно посмотреть в гитхабе. Если интересно почитать про план адаптации темной темы в этом приложении с нуля, ставьте лайк 👍