Всем привет!
Меня зовут Андрей, я из команды «Мой Брокер». Я рассĸажу Вам ĸаĸ добавлял поддержĸу темной темы в iOS.
Apple в iOS 13 добавила темную тему для всей системы, пользователи могут выбрать светлое или темное оформление на настройках iOS. В темном режиме система использует более темную цветовую палитру для всех экранов, видов, меню и элементов управления.
![image](https://habrastorage.org/r/w780q1/webt/fs/b0/_u/fsb0_u2dcdho-ziux7xsgnm-ij8.jpeg)
Кому интересно — заходите под кат.
Приложение созданное в Xcode 11 по-умолчанию поддерживает темное оформление в iOS 13. Но для полноценной реализации темного режима, необходимо внести дополнительные правки:
В iOS 13 был представлен новый инициализатор UIColor:
Добавим статическую функцию для создания цвета с поддержкой переключения между светлым и темным оформлением:
CGColor не поддерживает автоматическое переключение между светлым и темным оформлением. Необходимо вручную менять CGColor после изменения оформления.
Так же есть возможность добавить цвет для темного оформления в ресурсах.
![image](https://habrastorage.org/r/w1560/webt/10/c8/cv/10c8cvagr8cryl52al61y-6rayi.png)
Но я предпочитаю добавлять цвета в коде.
Для изображений достаточно добавить вариант изображения для темного оформления прям в ресурсах.
![image](https://habrastorage.org/r/w780q1/webt/2v/fv/1x/2vfv1xyui4bl8jmvpjq69n8yjzg.jpeg)
Приложение будет содержать два окна и три экрана.
Первое окно: экран авторизации.
Второе окно: экран ленты и экран профиля пользователя.
Создаем enum для темы:
Добавляем возможность хранения текущей темы, чтобы восстановить её после перезапуска приложения.
Чтобы принудительно установить оформление нужно изменить стиль всех окон приложения.
Реализуем переключение темы в приложении.
Так же необходимо менять стиль окна на текущую тему перед показом окна.
Добавляем системную тему в enum темы.
После принудительной установки светлой или темной темы, нельзя определить какое оформление включено в системе. Чтобы узнавать системное оформление добавляем окно в приложение, у которого не будем принудительно менять оформление. Так же необходимо реализовать изменение оформления, когда в приложении установлена системная тема и пользователь меняет оформление в iOS.
Поддержка темного оформления и переключение между системной, светлой и темной темой.
Ссылка на весь проект
Меня зовут Андрей, я из команды «Мой Брокер». Я рассĸажу Вам ĸаĸ добавлял поддержĸу темной темы в iOS.
Apple в iOS 13 добавила темную тему для всей системы, пользователи могут выбрать светлое или темное оформление на настройках iOS. В темном режиме система использует более темную цветовую палитру для всех экранов, видов, меню и элементов управления.
![image](https://habrastorage.org/webt/fs/b0/_u/fsb0_u2dcdho-ziux7xsgnm-ij8.jpeg)
Кому интересно — заходите под кат.
Поддержка темного оформления
Приложение созданное в Xcode 11 по-умолчанию поддерживает темное оформление в iOS 13. Но для полноценной реализации темного режима, необходимо внести дополнительные правки:
- Цвета должны поддерживать светлое и темное оформление
- Изображения должны поддерживать светлое и темное оформление
Apple добавила несколько системных цветов, которые поддерживают светлое и темное оформление.![image](https://habrastorage.org/r/w780q1/webt/cb/3q/uk/cb3quk8-7m4s8po0ey2xebo5mqm.jpeg)
![image](https://habrastorage.org/webt/cb/3q/uk/cb3quk8-7m4s8po0ey2xebo5mqm.jpeg)
В 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
}
Так же есть возможность добавить цвет для темного оформления в ресурсах.
![image](https://habrastorage.org/webt/10/c8/cv/10c8cvagr8cryl52al61y-6rayi.png)
Но я предпочитаю добавлять цвета в коде.
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(named: "secondaryBackground") ?? .black
static let gray = UIColor.color(light: .lightGray, dark: .hex("8e8e92"))
}
}
Для изображений достаточно добавить вариант изображения для темного оформления прям в ресурсах.
![image](https://habrastorage.org/webt/2v/fv/1x/2vfv1xyui4bl8jmvpjq69n8yjzg.jpeg)
Сделаем небольшое приложение для примера.
Приложение будет содержать два окна и три экрана.
Первое окно: экран авторизации.
Второе окно: экран ленты и экран профиля пользователя.
Скриншоты в светлом и темном оформлении
![image](https://habrastorage.org/r/w780q1/webt/wa/qo/gn/waqognt2sinwmvls1stzker5jdg.jpeg)
![image](https://habrastorage.org/r/w780q1/webt/7d/cp/ib/7dcpib0cvnl7ojyvca-jkcmseia.jpeg)
![image](https://habrastorage.org/webt/sw/ye/ov/swyeovhm3bzzjxslg33ni8ocy9y.jpeg)
![image](https://habrastorage.org/webt/r8/ex/k5/r8exk5e4j026fuf3c41kkigfpzg.jpeg)
![image](https://habrastorage.org/webt/wa/qo/gn/waqognt2sinwmvls1stzker5jdg.jpeg)
![image](https://habrastorage.org/webt/pv/ue/nt/pvuentazkjtvmh5d9oc_1ovpqzw.jpeg)
![image](https://habrastorage.org/webt/ec/rx/ii/ecrxiitek-jkkwhys46fodx37wi.jpeg)
![image](https://habrastorage.org/webt/7d/cp/ib/7dcpib0cvnl7ojyvca-jkcmseia.jpeg)
Переключение светлой и темной темы
Создаем 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](https://habrastorage.org/r/w780q1/webt/9t/wg/aw/9twgawpoitnjnnyetutogq7aio8.jpeg)
![image](https://habrastorage.org/webt/5s/7i/2g/5s7i2gkukjr6wsezvz9mvw1sigw.jpeg)
![image](https://habrastorage.org/webt/9t/wg/aw/9twgawpoitnjnnyetutogq7aio8.jpeg)
Добавляем переключение на системной тему
Добавляем системную тему в 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](https://habrastorage.org/r/w780q1/webt/h4/3q/1t/h43q1tuqjt6gli84u46kpdwbi4m.jpeg)
![image](https://habrastorage.org/webt/fw/of/ou/fwofouiwrgdm0wlfrjvqymg4zlg.jpeg)
![image](https://habrastorage.org/webt/h4/3q/1t/h43q1tuqjt6gli84u46kpdwbi4m.jpeg)
Результат
Поддержка темного оформления и переключение между системной, светлой и темной темой.
Скринвидео![](https://habrastorage.org/webt/fd/2a/ey/fd2aeyn6fm5abzqtrdtw9sruvwk.gif)
![](https://habrastorage.org/webt/fd/2a/ey/fd2aeyn6fm5abzqtrdtw9sruvwk.gif)
Ссылка на весь проект