Добавляем темную тему в iOS

от автора

Всем привет!

Меня зовут Андрей, я из команды «Мой Брокер». Я рассĸажу Вам ĸаĸ добавлял поддержĸу темной темы в iOS.

Apple в iOS 13 добавила темную тему для всей системы, пользователи могут выбрать светлое или темное оформление на настройках iOS. В темном режиме система использует более темную цветовую палитру для всех экранов, видов, меню и элементов управления.

image

Кому интересно — заходите под кат.

Поддержка темного оформления

Приложение созданное в Xcode 11 по-умолчанию поддерживает темное оформление в iOS 13. Но для полноценной реализации темного режима, необходимо внести дополнительные правки:

  • Цвета должны поддерживать светлое и темное оформление
  • Изображения должны поддерживать светлое и темное оформление

Apple добавила несколько системных цветов, которые поддерживают светлое и темное оформление.

image

В iOS 13 был представлен новый инициализатор UIColor:

init (dynamicProvider: @escaping (UITraitCollection) -> UIColor)

Добавим статическую функцию для создания цвета с поддержкой переключения между светлым и темным оформлением:

extension UIColor {          static func color(light: UIColor, dark: UIColor) -> UIColor {         if #available(iOS 13, *) {             return UIColor.init { traitCollection in                 return traitCollection.userInterfaceStyle == .dark ? dark : light             }         } else {             return light         }     } }

CGColor не поддерживает автоматическое переключение между светлым и темным оформлением. 
Необходимо вручную менять CGColor после изменения оформления.

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {     super.traitCollectionDidChange(previousTraitCollection)              layer.borderColor = UIColor.Pallete.black.cgColor }

UIColor.Pallete

extension UIColor {          struct Pallete {          static let white = UIColor.color(light: .white, dark: .black)         static let black = UIColor.color(light: .black, dark: .white)          static let background = UIColor.color(light: .white, dark: .hex("1b1b1d"))         static let secondaryBackground = UIColor.color(light: .hex("e1e1e1"), dark: .hex("232323"))          static let gray = UIColor.color(light: .lightGray, dark: .hex("8e8e92"))      } }

Для изображений достаточно добавить вариант изображения для темного оформления прям в ресурсах.

image

Сделаем небольшое приложение для примера.

Приложение будет содержать два окна и три экрана.

Первое окно: экран авторизации.

Второе окно: экран ленты и экран профиля пользователя.

Скриншоты в светлом и темном оформлении

image image image
image image image

Переключение светлой и темной темы

Создаем enum для темы:


enum Theme: Int, CaseIterable {     case light = 0     case dark }

Добавляем возможность хранения текущей темы, чтобы восстановить её после перезапуска приложения.

extension Theme {          // Обертка для UserDefaults     @Persist(key: "app_theme", defaultValue: Theme.light.rawValue)     private static var appTheme: Int          // Сохранение темы в UserDefaults     func save() {         Theme.appTheme = self.rawValue     }          // Текущая тема приложения     static var current: Theme {         Theme(rawValue: appTheme) ?? .light     } }

Persist

@propertyWrapper struct Persist<T> {     let key: String     let defaultValue: T          var wrappedValue: T {         get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }         set { UserDefaults.standard.set(newValue, forKey: key) }     }          init(key: String, defaultValue: T) {         self.key = key         self.defaultValue = defaultValue     } }

Чтобы принудительно установить оформление нужно изменить стиль всех окон приложения.

Реализуем переключение темы в приложении.


extension Theme {          @available(iOS 13.0, *)     var userInterfaceStyle: UIUserInterfaceStyle {         switch self {         case .light: return .light         case .dark: return .dark         }     }          func setActive() {         // Сохраняем активную тему         save()                  guard #available(iOS 13.0, *) else { return }                  // Устанавливаем активную тему для всех окон приложения         UIApplication.shared.windows             .forEach { $0.overrideUserInterfaceStyle = userInterfaceStyle }     } }

Так же необходимо менять стиль окна на текущую тему перед показом окна.

extension UIWindow {          // Устанавливаем текущую тему для окна     // Необходимо вызывать перед показом окна     func initTheme() {         guard #available(iOS 13.0, *) else { return }                  overrideUserInterfaceStyle = Theme.current.userInterfaceStyle     } }

Скриншоты выбора светлой или темной темы

image image

Добавляем переключение на системной тему

Добавляем системную тему в enum темы.

enum Theme: Int, CaseIterable {     case system = 0     case light     case dark }

После принудительной установки светлой или темной темы, нельзя определить какое оформление включено в системе. Чтобы узнавать системное оформление добавляем окно в приложение, у которого не будем принудительно менять оформление. Так же необходимо реализовать изменение оформления, когда в приложении установлена системная тема и пользователь меняет оформление в iOS.

final class ThemeWindow: UIWindow {          override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {          // Если текущая тема системная и поменяли оформление в iOS, опять меняем тему на системную.         // Например: Пользователь поменял светлое оформление на темное.         if Theme.current == .system {             Theme.system.setActive()         }     } }  let themeWindow = ThemeWindow()  class AppDelegate: UIResponder, UIApplicationDelegate {      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {         ...         // Добавляем окно к приложению, но не показываем его         // Необходимо вызывать до установки главного окна приложения         themeWindow.makeKey()         ...         return true     } }  extension Theme {          @available(iOS 13.0, *)     var userInterfaceStyle: UIUserInterfaceStyle {         switch self {         case .light: return .light         case .dark: return .dark         case .system: return themeWindow.traitCollection.userInterfaceStyle         }     }          func setActive() {         // Сохраняем активную тему         save()                  guard #available(iOS 13.0, *) else { return }                  // Устанавливаем активную тему для всех окон приложения         // Не красим это окно чтобы узнавать системную тему         UIApplication.shared.windows             .filter { $0 != themeWindow }              .forEach { $0.overrideUserInterfaceStyle = userInterfaceStyle }     } }

Скриншоты выбора системной, светлой или темной темы

image image

Результат

Поддержка темного оформления и переключение между системной, светлой и темной темой.

Скринвидео

Ссылка на весь проект

ссылка на оригинал статьи https://habr.com/ru/company/bcs_company/blog/493096/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *