Ajouter un thème sombre dans iOS

Bonjour à tous!

Je m'appelle Andrey, je fais partie de l'équipe My Broker. Je vais vous dire que vous avez ajouté la prise en charge d'un thème sombre dans iOS.

Apple dans iOS 13 a ajouté un thème sombre pour l'ensemble du système, les utilisateurs peuvent choisir une apparence claire ou sombre dans les paramètres iOS. En mode sombre, le système utilise une palette de couleurs plus foncées pour tous les écrans, vues, menus et commandes.

image

Peu importe - passez sous le chat.

Prise en charge de la conception sombre


L'application créée par défaut dans Xcode 11 prend en charge la conception sombre dans iOS 13. Mais pour la mise en œuvre complète du mode sombre, vous devez apporter des modifications supplémentaires:

  • Les couleurs doivent prendre en charge le design clair et foncé
  • Les images doivent prendre en charge la conception claire et sombre

Apple a ajouté plusieurs couleurs de système qui prennent en charge le design clair et sombre.
image

Dans iOS 13, le nouvel initialiseur UIColor a été introduit :

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

Ajoutez une fonction statique pour créer des couleurs avec prise en charge de la commutation entre le design clair et sombre:

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 ne prend pas en charge la commutation automatique entre la lumière et l'obscurité. Vous devez modifier manuellement le CGColor après avoir modifié la conception.

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

Il est également possible d'ajouter de la couleur pour la décoration sombre dans les ressources.

image

Mais je préfère ajouter des couleurs dans le code.

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

    }
}


Pour les images, ajoutez simplement une variante de l'image pour un design sombre directement dans les ressources.

image

Faisons une petite application pour un exemple.


L'application contiendra deux fenêtres et trois écrans.

Première fenêtre: écran d'autorisation.

Deuxième fenêtre: écran ruban et écran de profil utilisateur.

Captures d'écran au design clair et sombre
image image image
image image image

Changer de thèmes clairs et sombres


Créez une énumération pour le sujet:


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

Nous ajoutons la possibilité de stocker le thème actuel afin de le restaurer après le redémarrage de l'application.

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

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


Pour forcer la conception, vous devez modifier le style de toutes les fenêtres d'application.

Nous implémentons le changement de thème dans l'application.


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

Il est également nécessaire de changer le style de la fenêtre pour le thème actuel avant d'afficher la fenêtre.

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

Captures d'écran du choix d'un thème clair ou foncé
image image

Ajouter un commutateur au thème système


Ajoutez un thème système au thème enum.
enum Theme: Int, CaseIterable {
    case system = 0
    case light
    case dark
}

Après l'installation forcée d'un thème clair ou sombre, il est impossible de déterminer quel design est inclus dans le système. Pour reconnaître la conception du système, nous ajoutons une fenêtre à l'application, dans laquelle nous ne forcerons pas le changement de conception. Il est également nécessaire d'implémenter une modification de conception lorsque l'application a un thème système installé et que l'utilisateur modifie la conception dans 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 }
    }
}

Captures d'écran du choix d'un système, d'un thème clair ou sombre
image image

Résultat


Prise en charge de la conception sombre et de la commutation entre les thèmes système, clair et sombre.
Vidéo écran


Lien vers l'ensemble du projet

All Articles