Agregue un tema oscuro en iOS

¡Hola a todos!

Mi nombre es Andrey, soy del equipo de My Broker. Te diré que agregué soporte para un tema oscuro en iOS.

Apple en iOS 13 agregó un tema oscuro para todo el sistema, los usuarios pueden elegir una apariencia clara u oscura en la configuración de iOS. En modo oscuro, el sistema utiliza una paleta de colores más oscuros para todas las pantallas, vistas, menús y controles.

imagen

A quién le importa: ve debajo del gato.

Soporte de diseño oscuro


La aplicación creada en Xcode 11 por defecto admite el diseño oscuro en iOS 13. Pero para la implementación completa del modo oscuro, debe realizar cambios adicionales:

  • Los colores deben ser compatibles con el diseño claro y oscuro.
  • Las imágenes deben ser compatibles con el diseño claro y oscuro.

Apple ha agregado varios colores de sistema que admiten diseño claro y oscuro.
imagen

En iOS 13, se introdujo el nuevo inicializador UIColor :

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

Agregue una función estática para crear color con soporte para cambiar entre diseño claro y oscuro:

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 no admite el cambio automático entre claro y oscuro. Debe cambiar manualmente el CGColor después de cambiar el diseño.

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

También es posible agregar color para la decoración oscura en los recursos.

imagen

Pero prefiero agregar colores en el código.

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"))

    }
}


Para las imágenes, solo agregue una variante de la imagen para un diseño oscuro directamente en los recursos.

imagen

Hagamos una pequeña aplicación como ejemplo.


La aplicación contendrá dos ventanas y tres pantallas.

Primera ventana: pantalla de autorización.

Segunda ventana: pantalla de cinta y pantalla de perfil de usuario.

Capturas de pantalla en diseño claro y oscuro.
imagen imagen imagen
imagen imagen imagen

Cambiar temas claros y oscuros


Crea una enumeración para el tema:


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

Agregamos la capacidad de almacenar el tema actual para restaurarlo después de reiniciar la aplicación.

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
    }
}

Persistir
@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
    }
}


Para forzar el diseño, debe cambiar el estilo de todas las ventanas de la aplicación.

Implementamos el cambio de tema en la aplicación.


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 }
    }
}

También es necesario cambiar el estilo de la ventana al tema actual antes de mostrar la ventana.

extension UIWindow {
    
    //     
    //     
    func initTheme() {
        guard #available(iOS 13.0, *) else { return }
        
        overrideUserInterfaceStyle = Theme.current.userInterfaceStyle
    }
}

Capturas de pantalla de elegir un tema claro u oscuro
imagen imagen

Agregar un interruptor al tema del sistema


Agregue un tema del sistema al tema enum.
enum Theme: Int, CaseIterable {
    case system = 0
    case light
    case dark
}

Después de la instalación forzada de un tema claro u oscuro, es imposible determinar qué diseño se incluye en el sistema. Para reconocer el diseño del sistema, agregamos una ventana a la aplicación, en la cual no forzaremos el cambio de diseño. También es necesario implementar un cambio de diseño cuando la aplicación tiene instalado un tema del sistema y el usuario cambia el diseño en 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 }
    }
}

Capturas de pantalla de elegir un sistema, tema claro u oscuro
imagen imagen

Resultado


Soporte para diseño oscuro y cambio entre sistema, temas claros y oscuros.
Pantalla de video


Enlace a todo el proyecto

All Articles