Est-il possible de désactiver le mode sombre sur iOS 13?

296

Une grande partie de mon application se compose de vues Web pour fournir des fonctionnalités qui ne sont pas encore disponibles via les implémentations natives. L'équipe Web n'a pas l'intention de mettre en œuvre un thème sombre pour le site Web. En tant que tel, mon application sera un peu à moitié avec la prise en charge du mode sombre sur iOS 13.

Est-il possible de désactiver la prise en charge du mode sombre de telle sorte que notre application affiche toujours le mode clair pour correspondre au thème du site Web?

SeanR
la source
70
Réglez UIUserInterfaceStylesur Lightdans votre Info.Plist. Voir developer.apple.com/library/archive/documentation/General/…
Tieme
1
Merci d'avoir demandé - pour nous tous. Beaucoup d'applications à parcourir. Cela est nécessaire pour que les applications fonctionnent jusqu'à ce que la bascule soit prête.
user3741598
import Foundation importation extension UIKit UIViewController {remplacer open func awakeFromNib () {super.awakeFromNib () if #available (iOS 13.0, *) {// Toujours adopter un style d'interface léger. overrideUserInterfaceStyle = .light}}}
Mohammad Razipour
1
ajoutez simplement UIUserInterfaceStyle dans plist. c'est aussi simple que ça
Fattie
Lors de la soumission d'une application à l'appstore, Apple accepte-t-il en raison de UIUserInterfaceStyle en mode Light.
kiran

Réponses:

684

Tout d'abord, voici l'entrée d'Apple relative à la désactivation du mode sombre. Le contenu de ce lien est écrit pour Xcode 11 et iOS 13 :

Cette section s'applique à l'utilisation de Xcode 11


Si vous souhaitez retirer votre candidature ENTIÈREMENT

Approche n ° 1

Utilisez la clé suivante dans votre fichier info.plist :

UIUserInterfaceStyle

Et affectez-lui une valeur de Light.

Le XML de l' UIUserInterfaceStyleaffectation:

<key>UIUserInterfaceStyle</key>
<string>Light</string>

Approche n ° 2

Vous pouvez définir overrideUserInterfaceStylela windowvariable de l'application .

Selon la façon dont votre projet a été créé, cela peut être dans le AppDelegatefichier ou le SceneDelegate.

if #available(iOS 13.0, *) {
    window?.overrideUserInterfaceStyle = .light
}


Si vous souhaitez désactiver votre UIViewController sur une base individuelle

override func viewDidLoad() {
    super.viewDidLoad()
    // overrideUserInterfaceStyle is available with iOS 13
    if #available(iOS 13.0, *) {
        // Always adopt a light interface style.
        overrideUserInterfaceStyle = .light
    }
}

Documentation Apple pour overrideUserInterfaceStyle

A quoi ressemblera le code ci-dessus dans Xcode 11:

entrez la description de l'image ici

Cette section s'applique à l'utilisation de Xcode 10.x


Si vous utilisez Xcode 11 pour votre soumission, vous pouvez ignorer en toute sécurité tout ce qui se trouve en dessous de cette ligne.

Étant donné que l'API appropriée n'existe pas dans iOS 12, vous obtiendrez des erreurs lorsque vous tenterez d'utiliser les valeurs fournies ci-dessus:

Pour installer overrideUserInterfaceStylevotreUIViewController

entrez la description de l'image ici

Si vous souhaitez désactiver votre UIViewController sur une base individuelle

Cela peut être géré dans Xcode 10 en testant la version du compilateur et la version iOS:

#if compiler(>=5.1)
if #available(iOS 13.0, *) {
    // Always adopt a light interface style.
    overrideUserInterfaceStyle = .light
}
#endif

Si vous souhaitez retirer votre candidature ENTIÈREMENT

Vous pouvez modifier l'extrait ci-dessus pour qu'il fonctionne avec l'ensemble de l'application pour Xcode 10, en ajoutant le code suivant à votre AppDelegatefichier.

#if compiler(>=5.1)
if #available(iOS 13.0, *) {
    // Always adopt a light interface style.
    window?.overrideUserInterfaceStyle = .light
}
#endif

Cependant, le paramètre plist échouera lors de l'utilisation de Xcode version 10.x:

entrez la description de l'image ici

Crédit @Aron Nelson , @Raimundas Sakalauskas , @NSLeader et rmaddy pour améliorer cette réponse avec leurs commentaires.

CodeBender
la source
2
La lumière UIUserInterfaceStyle est bloquée lors de la mise à jour / téléchargement de votre application maintenant. Il est signalé comme une entrée de liste non valide. (Clé de plist invalide)
Aron Nelson
2
Cela ne se compilera pas avec iOS SDK 12 (actuellement le dernier SDK stable). Voir stackoverflow.com/a/57521901/2249485 pour une solution qui fonctionnera également avec le SDK iOS 12.
Raimundas Sakalauskas
C'est tellement injuste que la question qui a beaucoup plus de vues que la "question d'origine" est verrouillée pour fournir des réponses. :(
Raimundas Sakalauskas
7
Au lieu de mettre overrideUserInterfaceStyleen viewDidLoadde chaque contrôleur de vue, vous pouvez le régler une fois sur la fenêtre principale de l'application. C'est beaucoup plus facile si vous voulez que l'application se comporte dans un sens.
rmaddy
2
Utilisez #if compiler(>=5.1)plutôt responds(to:)etsetValue
NSLeader
162

Selon la session d'Apple sur "Implémentation du mode sombre sur iOS" ( https://developer.apple.com/videos/play/wwdc2019/214/ à partir de 31:13), il est possible de définir overrideUserInterfaceStylesur UIUserInterfaceStyleLightou UIUserInterfaceStyleDarksur n'importe quel contrôleur de vue ou vue , qui sera utilisé dans le traitCollectionpour toute sous-vue ou contrôleur de vue.

Comme déjà mentionné par SeanR, vous pouvez définir UIUserInterfaceStylesur Lightou Darkdans le fichier plist de votre application pour modifier cela pour l'ensemble de votre application.

dorbeetle
la source
17
Si vous définissez la clé UIUserInterfaceStyle, votre application sera rejetée dans l'App Store
Sonius
2
Apple a rejeté avec le code d'erreur ITMS-90190 forums.developer.apple.com/thread/121028
PRASAD1240
11
Le rejet est plus susceptible de se produire car le SDK iOS 13 n'est pas encore sorti de la version bêta. Je pense que cela devrait fonctionner dès que le Xcode 11 GM sera disponible.
dorbeetle
2
@dorbeetle ce n'est pas vrai, j'ai téléchargé avec succès mon application avec cette clé il y a 1 mois avec Xcode 10. Les rejets se produisent récemment. Il semble que certains types de nouvelle stratégie d'Apple.
Steven
1
Ça continue. Xcode GM2 a renvoyé une erreur de signature d'application. Xcode 10.3 a renvoyé: "Clé Info.plist non valide. La clé" UIUserInterfaceStyle "dans le fichier Payload / Galileo.appInfo.plist n'est pas valide."
Evgen Bodunov
64

Si vous n'utilisez pas Xcode 11 ou une version ultérieure (i, e iOS 13 ou une version ultérieure du SDK), votre application n'a pas automatiquement choisi de prendre en charge le mode sombre. Il n'est donc pas nécessaire de désactiver le mode sombre.

Si vous utilisez Xcode 11 ou une version ultérieure, le système a automatiquement activé le mode sombre pour votre application. Il existe deux approches pour désactiver le mode sombre en fonction de vos préférences. Vous pouvez le désactiver entièrement ou le désactiver pour une fenêtre, une vue ou un contrôleur de vue spécifique.

Désactiver le mode sombre entièrement pour votre application

Vous pouvez désactiver le mode sombre en incluant la UIUserInterfaceStyleclé avec une valeur comme Lightdans le fichier Info.plist de votre application. Cela ignore la préférence de l'utilisateur et applique toujours une apparence claire à votre application.
UIUserInterfaceStyle as Light

Désactiver le mode sombre pour Window, View ou View Controller

Vous pouvez forcer votre interface à toujours apparaître dans un style clair ou sombre en définissant overrideUserInterfaceStyle propriété de la fenêtre, de la vue ou du contrôleur de vue approprié.

Afficher les contrôleurs:

override func viewDidLoad() {
    super.viewDidLoad()
    /* view controller’s views and child view controllers 
     always adopt a light interface style. */
    overrideUserInterfaceStyle = .light
}

Vues:

// The view and all of its subviews always adopt light style.
youView.overrideUserInterfaceStyle = .light

La fenêtre:

/* Everything in the window adopts the style, 
 including the root view controller and all presentation controllers that 
 display content in that window.*/
window.overrideUserInterfaceStyle = .light

Remarque: Apple encourage fortement à prendre en charge le mode sombre dans votre application. Vous ne pouvez donc désactiver le mode sombre que temporairement.

En savoir plus ici: Choisir un style d'interface spécifique pour votre application iOS

Ajith R Nayak
la source
34

********** Moyen le plus simple pour Xcode 11 et supérieur ***********

Ajoutez ceci à info.plist avant </dict></plist>

<key>UIUserInterfaceStyle</key>
<string>Light</string>
Kingsley Mitchell
la source
cette solution échouera lors de la soumission de l'application sur Xcode 10.x
Tawfik Bouabid
27

Je pense avoir trouvé la solution. Je l'ai initialement reconstitué à partir de UIUserInterfaceStyle - Information Property List et UIUserInterfaceStyle - UIKit , mais je l'ai maintenant trouvé réellement documenté à Choisir un style d'interface spécifique pour votre application iOS .

Dans votre info.plist, définissez UIUserInterfaceStyle( Style d'interface utilisateur ) sur 1 ( UIUserInterfaceStyle.light).

EDIT: Selon la réponse de dorbeetle, un réglage plus approprié UIUserInterfaceStylepourrait être Light.

SeanR
la source
L'application du mode sombre en définissant la valeur sur 2 ne fonctionne pas cependant:[UIInterfaceStyle] '2' is not a recognized value for UIUserInterfaceStyle. Defaulting to Light.
funkenstrahlen
3
Le fait d'avoir cette clé dans le plist entraînera un rejet de l'App Store.
José
1
AppStore ne rejette plus cette propriété sur plist.info. Je mets "Dark" (en majuscule) car notre application est déjà sombre. Pas de problème. Cela nous permet d'utiliser correctement les commandes du système.
nickdnk
@nickdnk Je pense que vous avez créé votre application avec Xcode 11, qui est recommandé par Apple.
DawnSong
1
Oui je l'ai fait. Cela ne change pas le fait qu'Apple accepte ce paramètre dans la liste, ce que j'essayais de clarifier.
nickdnk
23

La réponse ci-dessus fonctionne si vous souhaitez désactiver l'application entière. Si vous travaillez sur la bibliothèque qui a une interface utilisateur et que vous n'avez pas le luxe d'éditer .plist, vous pouvez également le faire via du code.

Si vous compilez avec le SDK iOS 13, vous pouvez simplement utiliser le code suivant:

Rapide:

if #available(iOS 13.0, *) {
    self.overrideUserInterfaceStyle = .light
}

Obj-C:

if (@available(iOS 13.0, *)) {
    self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}

CEPENDANT , si vous voulez que votre code soit compilé avec le SDK iOS 12 (qui est actuellement le dernier SDK stable), vous devez recourir à des sélecteurs. Code avec sélecteurs:

Swift (XCode affichera des avertissements pour ce code, mais c'est la seule façon de le faire pour l'instant car la propriété n'existe pas dans le SDK 12 ne compilera donc pas):

if #available(iOS 13.0, *) {
    if self.responds(to: Selector("overrideUserInterfaceStyle")) {
        self.setValue(UIUserInterfaceStyle.light.rawValue, forKey: "overrideUserInterfaceStyle")
    }
}

Obj-C:

if (@available(iOS 13.0, *)) {
    if ([self respondsToSelector:NSSelectorFromString(@"overrideUserInterfaceStyle")]) {
        [self setValue:@(UIUserInterfaceStyleLight) forKey:@"overrideUserInterfaceStyle"];
    }
}
Raimundas Sakalauskas
la source
Ce sera mieux si vous spécifiez à quoi overrideUserInterfaceStyleappartient la propriété .
DawnSong
12

Dernière mise à jour-

Si vous utilisez Xcode 10.x, la valeur par défaut UIUserInterfaceStyleest lightpour iOS 13.x. Lorsqu'il est exécuté sur un appareil iOS 13, il ne fonctionnera qu'en mode léger.

Pas besoin d'ajouter explicitement la UIUserInterfaceStyleclé dans le fichier Info.plist, l'ajouter donnera une erreur lorsque vous validerez votre application, en disant:

Clé Info.plist non valide. La clé «UIUserInterfaceStyle» dans le fichier Payload / AppName.appInfo.plist n'est pas valide.

UIUserInterfaceStyleN'ajoutez la clé dans le fichier Info.plist que lorsque vous utilisez Xcode 11.x.

kumarsiddharth123
la source
1
Cela n'a rien à voir avec Xcode 10 ou 11. Si l'utilisateur déploie l'application sous Xcode 10 et ne prend pas en charge le mode sombre, l'application installée sur iPhone 11, Pro ou Pro Max aura des problèmes de mode sombre. vous devez mettre à jour vers Xcode 11 et résoudre ce problème.
Niranjan Molkeri
3
@NiranjanMolkeri Cela n'a rien à voir avec les nouveaux iPhones. Il s'agit du mode sombre sur iOS 13. Dans les applications précédentes de la version bêta d'iOS 13, l'interface utilisateur avait des problèmes de mode sombre si elle n'était pas traitée explicitement. Mais dans la dernière version, cela est corrigé. Si vous utilisez XCode 10, alors le UIUserInterfaceStyle par défaut est léger pour iOS13. Si vous utilisez Xode11, vous devez le gérer.
kumarsiddharth123
Vous aurez des problèmes si vous téléchargez une application sur TestFligth à l'aide de Xcode 10.3 et que le plist inclut la clé UIUserInterfaceStyle. Il dira qu'il s'agit d'un fichier plist invalide. Vous devez soit le supprimer si vous construisez dans Xcode 10, soit le télécharger en utilisant Xcode 11
eharo2
9

Si vous ajoutez une UIUserInterfaceStyleclé au fichier plist, Apple rejettera peut-être la version de la version comme indiqué ici: https://stackoverflow.com/a/56546554/7524146 Quoi qu'il en soit, il est ennuyeux de dire explicitement à chaque ViewController self.overrideUserInterfaceStyle = .light . Mais vous pouvez utiliser cette paix de code une fois pour votre windowobjet racine :

if #available(iOS 13.0, *) {
    if window.responds(to: Selector(("overrideUserInterfaceStyle"))) {
        window.setValue(UIUserInterfaceStyle.light.rawValue, forKey: "overrideUserInterfaceStyle")
    }
}

Notez juste que vous ne pouvez pas faire cela à l'intérieur application(application: didFinishLaunchingWithOptions:)car ce sélecteur ne répondra pas trueà ce stade précoce. Mais vous pouvez le faire plus tard. C'est très facile si vous utilisez une classe personnalisée AppPresenterou AppRouterdans votre application au lieu de démarrer automatiquement l'interface utilisateur dans AppDelegate.

SerhiiK
la source
9

Vous pouvez désactiver le mode sombre dans toute l'application dans Xcode 11:

  1. Aller à Info.plist
  2. Ajouter ci-dessous comme

    <key>UIUserInterfaceStyle</key>
    <string>Light</string>

Info.plist ressemblera à ci-dessous ...

entrez la description de l'image ici

Enamul Haque
la source
1
ne fonctionne pas pour Xcode version 11.3.1 (11C504) pour une raison quelconque
Andrew
7

- Pour toute l'application (fenêtre):

window!.overrideUserInterfaceStyle = .light

Vous pouvez obtenir la fenêtre de SceneDelegate

- Pour un seul ViewController:

viewController.overrideUserInterfaceStyle = .light

Vous pouvez définir tout viewController, même à l' intérieur du viewController il auto

- Pour une seule vue:

view.overrideUserInterfaceStyle = .light

Vous pouvez définir tout view, même à l' intérieur de la vue , il auto

Vous devrez peut-être utiliser if #available(iOS 13.0, *) { ,,, }si vous prenez en charge les versions antérieures d'iOS.

Mojtaba Hosseini
la source
6

Mis à part les autres réponses, d'après ma compréhension des éléments suivants, il vous suffit de vous préparer au mode sombre lors de la compilation avec le SDK iOS 13 (en utilisant XCode 11).

Le système suppose que les applications liées au SDK iOS 13 ou version ultérieure prennent en charge les apparences claires et sombres. Dans iOS, vous spécifiez l'apparence spécifique que vous souhaitez en attribuant un style d'interface spécifique à votre fenêtre, vue ou contrôleur de vue. Vous pouvez également désactiver entièrement la prise en charge du mode sombre à l'aide d'une clé Info.plist.

Lien

Claudio
la source
2

Oui, vous pouvez ignorer en ajoutant le code suivant dans viewDidLoad:

if #available(iOS 13.0, *) {
        // Always adopt a light interface style.
        overrideUserInterfaceStyle = .light
    }
Muhammad Naeem Paracha
la source
2

Mon application ne prend pas en charge le mode sombre à partir de maintenant et utilise une couleur de barre d'application claire. J'ai pu forcer le contenu de la barre d'état en texte et icônes sombres en ajoutant la clé suivante à mon Info.plist:

<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDarkContent</string>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>

Trouvez les autres valeurs possibles ici: https://developer.apple.com/documentation/uikit/uistatusbarstyle

ToniTornado
la source
2

Version Objective-C

 if (@available(iOS 13.0, *)) {
        _window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    }
ahbou
la source
1

Voici quelques trucs et astuces que vous pouvez utiliser dans votre application pour prendre en charge ou contourner le mode sombre.

Premier conseil: pour remplacer le style ViewController

vous pouvez remplacer le style d'interface de UIViewController en

1: overrideUserInterfaceStyle = .dark // Pour le mode sombre

2: overrideUserInterfaceStyle = .light // Pour le mode d'éclairage

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        overrideUserInterfaceStyle = .light    
    }
}

Deuxième conseil: ajouter une clé dans info.plist

Vous pouvez simplement ajouter une nouvelle clé

UIUserInterfaceStyle

dans votre application info.plist et définissez sa valeur sur Clair ou Foncé. cela remplacera le style par défaut de l'application par la valeur que vous fournissez. Vous n'avez pas besoin d'ajouter overrideUserInterfaceStyle = .light cette ligne dans chaque viewController, juste une ligne dans info.plist c'est tout.

Mohammed Ebrahim
la source
1

Ajoutez simplement la clé suivante dans votre info.plistfichier:

<key>UIUserInterfaceStyle</key>
    <string>Light</string>
Moeen Ahmad
la source
1
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }
Talha Rasool
la source
Pouvez-vous expliquer un peu comment cette réponse résoudra le problème, au lieu de publier une réponse en code uniquement.
Arun Vinoth
Oui, bien sûr @ArunVinoth Dans l'IOS 13, le mode sombre est introduit, donc si votre cible de déploiement est inférieure à 13, utilisez le code ci-dessus, vous pouvez utiliser une simple instruction écrite en bloc if.
Talha Rasool
1

Swift 5

Deux façons de passer du mode sombre au mode clair:

1- info.plist

    <key>UIUserInterfaceStyle</key>
    <string>Light</string>

2- Par programme

 UIApplication.shared.windows.forEach { window in
     window.overrideUserInterfaceStyle = .light
  } 
Rashid Latif
la source
0

J'utiliserais cette solution car la propriété de la fenêtre peut être modifiée pendant le cycle de vie de l'application. L'affectation de "overrideUserInterfaceStyle = .light" doit donc être répétée. UIWindow.appearance () nous permet de définir la valeur par défaut qui sera utilisée pour les objets UIWindow nouvellement créés.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

      if #available(iOS 13.0, *) {
          UIWindow.appearance().overrideUserInterfaceStyle = .light
      }

      return true
    }
}
Dmitry
la source
0

Ajoutez simplement ces lignes dans le fichier info.plist:

<key>UIUserInterfaceStyle</key>
<string>light</string>

Cela forcera l'application à fonctionner en mode léger uniquement.

Rahul Gusain
la source
Cela a déjà été commenté et répondu à plusieurs reprises. Même la réponse acceptée le suggère. Par conséquent, ce commentaire n'ajoute aucune nouvelle information.
JeroenJK
0
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}
Mohammad Razipour
la source
2
Veuillez ajouter quelques explications à votre réponse en la modifiant, de sorte que d'autres puissent en tirer des enseignements
Nico Haase
0

Vous pouvez faire: ajoutez cette nouvelle clé UIUserInterfaceStyle à Info.plist et définissez sa valeur sur Light. et vérifier que le contrôleur d'alerte apparaît avec le mode d'éclairage.

UIUserInterfaceStyle Light Si vous forcez le mode clair / sombre dans l'ensemble de votre application, quels que soient les paramètres de l'utilisateur, ajoutez la clé UIUserInterfaceStyle à votre fichier Info.plist et définissez sa valeur sur Clair ou Foncé.

Hominda
la source
0

Cette question a tellement de réponses, plutôt en l'utilisant, info.plistvous pouvez la définir AppDelegatecomme ceci:

#if compiler(>=5.1)
        if #available(iOS 13.0, *) {
            self.window?.overrideUserInterfaceStyle = .light
        }
        #endif

Test sur Xcode 11.3, iOS 13.3

Niraj
la source
-8

En fait, je viens d'écrire du code qui vous permettra de désactiver globalement le mode sombre dans le code sans avoir à mettre en place tous les contrôleurs viw de votre application. Cela peut probablement être affiné pour se retirer classe par classe en gérant une liste de classes. Pour moi, ce que je veux, c'est que mes utilisateurs voient s'ils aiment l'interface en mode sombre de mon application, et s'ils ne l'aiment pas, ils peuvent la désactiver. Cela leur permettra de continuer à utiliser le mode sombre pour le reste de leurs applications.

Le choix de l'utilisateur est bon (Ahem, en te regardant Apple, c'est comme ça que tu aurais dû l'implémenter).

Donc, comment cela fonctionne, c'est que c'est juste une catégorie de UIViewController. Lorsqu'il se charge, il remplace la méthode native viewDidLoad par une qui vérifiera un indicateur global pour voir si le mode sombre est désactivé pour tout ou non.

Parce qu'il est déclenché lors du chargement de UIViewController, il devrait démarrer automatiquement et désactiver le mode sombre par défaut. Si ce n'est pas ce que vous voulez, vous devez y arriver tôt et définir le drapeau, ou bien simplement définir le drapeau par défaut.

Je n'ai encore rien écrit pour répondre à l'utilisateur qui active ou désactive le drapeau. Il s'agit donc essentiellement d'un exemple de code. Si nous voulons que l'utilisateur interagisse avec cela, tous les contrôleurs de vue devront être rechargés. Je ne sais pas comment faire cela à la volée mais probablement envoyer une notification va faire l'affaire. Donc, pour le moment, cette activation / désactivation globale du mode sombre ne fonctionnera qu'au démarrage ou au redémarrage de l'application.

Maintenant, il ne suffit plus d'essayer de désactiver le mode sombre dans chaque viewController MFING de votre énorme application. Si vous utilisez des actifs de couleur, vous êtes complètement désossé. Depuis plus de 10 ans, nous comprenons que les objets immuables sont immuables. Les couleurs que vous obtenez dans le catalogue des ressources de couleur disent qu'elles sont UIColor mais ce sont des couleurs dynamiques (modifiables) et changeront sous vous lorsque le système passera du mode sombre au mode clair. C'est censé être une fonctionnalité. Mais bien sûr, il n'y a pas de bascule principale pour demander à ces choses de cesser de faire ce changement (à ma connaissance, peut-être que quelqu'un peut améliorer cela).

La solution est donc en deux parties:

  1. une catégorie publique sur UIViewController qui donne des méthodes d'utilité et de commodité ... par exemple, je ne pense pas qu'Apple ait pensé au fait que certains d'entre nous mélangent du code Web dans nos applications. En tant que tel, nous avons des feuilles de style qui doivent être basculées en fonction du mode sombre ou clair. Ainsi, vous devez soit créer une sorte d'objet de feuille de style dynamique (ce qui serait bien), soit simplement demander quel est l'état actuel (mauvais mais facile).

  2. cette catégorie lors du chargement remplacera la méthode viewDidLoad de la classe UIViewController et interceptera les appels. Je ne sais pas si cela enfreint les règles de l'App Store. Si c'est le cas, il existe probablement d'autres moyens de contourner cela, mais vous pouvez le considérer comme une preuve de concept. Vous pouvez par exemple créer une sous-classe de tous les principaux types de contrôleurs de vue et faire hériter tous vos propres contrôleurs de vue de ceux-ci, puis vous pouvez utiliser l'idée de catégorie DarkMode et l'appeler pour forcer la désactivation de tous vos contrôleurs de vue. C'est plus laid mais ça ne va enfreindre aucune règle. Je préfère utiliser le runtime car c'est pour cela que le runtime a été conçu. Donc, dans ma version, vous ajoutez simplement la catégorie, vous définissez une variable globale sur la catégorie pour savoir si vous souhaitez ou non qu'elle bloque le mode sombre, et elle le fera.

  3. Vous n'êtes pas encore sorti du bois, comme mentionné, l'autre problème est que UIColor fait essentiellement ce qu'il veut. Donc, même si vos contrôleurs de vue bloquent le mode sombre, UIColor ne sait pas où ni comment vous l'utilisez, donc ne peut pas s'adapter. En conséquence, vous pouvez le récupérer correctement, mais cela reviendra sur vous à un moment donné dans le futur. Peut-être bientôt peut-être plus tard. Donc, la solution consiste à l'allouer deux fois à l'aide d'un CGColor et à le transformer en une couleur statique. Cela signifie que si votre utilisateur revient en arrière et réactive le mode sombre sur votre page de paramètres (l'idée est de faire en sorte que cela fonctionne afin que l'utilisateur ait le contrôle sur votre application au-delà du reste du système), toutes ces couleurs statiques besoin de remplacement. Jusqu'à présent, cela reste à résoudre par quelqu'un d'autre. Le moyen le plus simple de le faire est de faire un défaut que vous ' en désactivant le mode sombre, divisez par zéro pour planter l'application car vous ne pouvez pas la quitter et dire à l'utilisateur de simplement la redémarrer. Cela viole probablement également les directives de l'App Store, mais c'est une idée.

La catégorie UIColor n'a pas besoin d'être exposée, elle fonctionne simplement en appelant colorNamed: ... si vous n'avez pas dit à la classe DarkMode ViewController de bloquer le mode sombre, cela fonctionnera parfaitement comme prévu. Essayer de créer quelque chose d'élégant au lieu du code standard des sphaghetti aux pommes, ce qui signifie que vous devrez modifier la plupart de votre application si vous souhaitez désactiver par programme le mode sombre ou le basculer. Maintenant, je ne sais pas s'il existe un meilleur moyen de modifier par programmation l'Info.plist pour désactiver le mode sombre si nécessaire. Pour autant que je sache, c'est une fonction de compilation et après cela, vous êtes désossé.

Voici donc le code dont vous avez besoin. Doit être déposé et utiliser la seule méthode pour définir le style d'interface utilisateur ou définir la valeur par défaut dans le code. Vous êtes libre d'utiliser, de modifier, de faire tout ce que vous voulez avec cela pour n'importe quel but et aucune garantie n'est donnée et je ne sais pas si cela passera sur l'App Store. Améliorations très bienvenues.

Juste avertissement Je n'utilise pas ARC ou toute autre méthode de prise en main.

////// H file

#import <UIKit/UIKit.h>

@interface UIViewController(DarkMode)

// if you want to globally opt out of dark mode you call these before any view controllers load
// at the moment they will only take effect for future loaded view controllers, rather than currently
// loaded view controllers

// we are doing it like this so you don't have to fill your code with @availables() when you include this
typedef enum {
    QOverrideUserInterfaceStyleUnspecified,
    QOverrideUserInterfaceStyleLight,
    QOverrideUserInterfaceStyleDark,
} QOverrideUserInterfaceStyle;

// the opposite condition is light interface mode
+ (void)setOverrideUserInterfaceMode:(QOverrideUserInterfaceStyle)override;
+ (QOverrideUserInterfaceStyle)overrideUserInterfaceMode;

// utility methods
// this will tell you if any particular view controller is operating in dark mode
- (BOOL)isUsingDarkInterfaceStyle;
// this will tell you if any particular view controller is operating in light mode mode
- (BOOL)isUsingLightInterfaceStyle;

// this is called automatically during all view controller loads to enforce a single style
- (void)tryToOverrideUserInterfaceStyle;

@end


////// M file


//
//  QDarkMode.m

#import "UIViewController+DarkMode.h"
#import "q-runtime.h"


@implementation UIViewController(DarkMode)

typedef void (*void_method_imp_t) (id self, SEL cmd);
static void_method_imp_t _nativeViewDidLoad = NULL;
// we can't @available here because we're not in a method context
static long _override = -1;

+ (void)load;
{
#define DEFAULT_UI_STYLE UIUserInterfaceStyleLight
    // we won't mess around with anything that is not iOS 13 dark mode capable
    if (@available(iOS 13,*)) {
        // default setting is to override into light style
        _override = DEFAULT_UI_STYLE;
        /*
         This doesn't work...
        NSUserDefaults *d = NSUserDefaults.standardUserDefaults;
        [d setObject:@"Light" forKey:@"UIUserInterfaceStyle"];
        id uiStyle = [d objectForKey:@"UIUserInterfaceStyle"];
        NSLog(@"%@",uiStyle);
         */
        if (!_nativeViewDidLoad) {
            Class targetClass = UIViewController.class;
            SEL targetSelector = @selector(viewDidLoad);
            SEL replacementSelector = @selector(_overrideModeViewDidLoad);
            _nativeViewDidLoad = (void_method_imp_t)QMethodImplementationForSEL(targetClass,targetSelector);
            QInstanceMethodOverrideFromClass(targetClass, targetSelector, targetClass, replacementSelector);
        }
    }
}

// we do it like this because it's not going to be set often, and it will be tested often
// so we can cache the value that we want to hand to the OS
+ (void)setOverrideUserInterfaceMode:(QOverrideUserInterfaceStyle)style;
{
    if (@available(iOS 13,*)){
        switch(style) {
            case QOverrideUserInterfaceStyleLight: {
                _override = UIUserInterfaceStyleLight;
            } break;
            case QOverrideUserInterfaceStyleDark: {
                _override = UIUserInterfaceStyleDark;
            } break;
            default:
                /* FALLTHROUGH - more modes can go here*/
            case QOverrideUserInterfaceStyleUnspecified: {
                _override = UIUserInterfaceStyleUnspecified;
            } break;
        }
    }
}
+ (QOverrideUserInterfaceStyle)overrideUserInterfaceMode;
{
    if (@available(iOS 13,*)){
        switch(_override) {
            case UIUserInterfaceStyleLight: {
                return QOverrideUserInterfaceStyleLight;
            } break;
            case UIUserInterfaceStyleDark: {
                return QOverrideUserInterfaceStyleDark;
            } break;
            default:
                /* FALLTHROUGH */
            case UIUserInterfaceStyleUnspecified: {
                return QOverrideUserInterfaceStyleUnspecified;
            } break;
        }
    } else {
        // we can't override anything below iOS 12
        return QOverrideUserInterfaceStyleUnspecified;
    }
}

- (BOOL)isUsingDarkInterfaceStyle;
{
    if (@available(iOS 13,*)) {
        if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark){
            return YES;
        }
    }
    return NO;
}

- (BOOL)isUsingLightInterfaceStyle;
{
    if (@available(iOS 13,*)) {
        if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight){
            return YES;
        }
        // if it's unspecified we should probably assume light mode, esp. iOS 12
    }
    return YES;
}

- (void)tryToOverrideUserInterfaceStyle;
{
    // we have to check again or the compile will bitch
    if (@available(iOS 13,*)) {
        [self setOverrideUserInterfaceStyle:(UIUserInterfaceStyle)_override];
    }
}

// this method will be called via the viewDidLoad chain as we will patch it into the
// UIViewController class
- (void)_overrideModeViewDidLoad;
{
    if (_nativeViewDidLoad) {
        _nativeViewDidLoad(self,@selector(viewDidLoad));
    }
    [self tryToOverrideUserInterfaceStyle];
}


@end

// keep this in the same file, hidden away as it needs to switch on the global ... yeah global variables, I know, but viewDidLoad and colorNamed: are going to get called a ton and already it's adding some inefficiency to an already inefficient system ... you can change if you want to make it a class variable. 

// this is necessary because UIColor will also check the current trait collection when using asset catalogs
// so we need to repair colorNamed: and possibly other methods
@interface UIColor(DarkMode)
@end

@implementation UIColor (DarkMode)

typedef UIColor *(*color_method_imp_t) (id self, SEL cmd, NSString *name);
static color_method_imp_t _nativeColorNamed = NULL;
+ (void)load;
{
    // we won't mess around with anything that is not iOS 13 dark mode capable
    if (@available(iOS 13,*)) {
        // default setting is to override into light style
        if (!_nativeColorNamed) {
            // we need to call it once to force the color assets to load
            Class targetClass = UIColor.class;
            SEL targetSelector = @selector(colorNamed:);
            SEL replacementSelector = @selector(_overrideColorNamed:);
            _nativeColorNamed = (color_method_imp_t)QClassMethodImplementationForSEL(targetClass,targetSelector);
            QClassMethodOverrideFromClass(targetClass, targetSelector, targetClass, replacementSelector);
        }
    }
}


// basically the colors you get
// out of colorNamed: are dynamic colors... as the system traits change underneath you, the UIColor object you
// have will also change since we can't force override the system traits all we can do is force the UIColor
// that's requested to be allocated out of the trait collection, and then stripped of the dynamic info
// unfortunately that means that all colors throughout the app will be static and that is either a bug or
// a good thing since they won't respond to the system going in and out of dark mode
+ (UIColor *)_overrideColorNamed:(NSString *)string;
{
    UIColor *value = nil;
    if (@available(iOS 13,*)) {
        value = _nativeColorNamed(self,@selector(colorNamed:),string);
        if (_override != UIUserInterfaceStyleUnspecified) {
            // the value we have is a dynamic color... we need to resolve against a chosen trait collection
            UITraitCollection *tc = [UITraitCollection traitCollectionWithUserInterfaceStyle:_override];
            value = [value resolvedColorWithTraitCollection:tc];
        }
    } else {
        // this is unreachable code since the method won't get patched in below iOS 13, so this
        // is left blank on purpose
    }
    return value;
}
@end

Il existe un ensemble de fonctions utilitaires que cela utilise pour effectuer un échange de méthode. Fichier séparé. Il s'agit cependant de choses standard et vous pouvez trouver du code similaire n'importe où.

// q-runtime.h

#import <Foundation/Foundation.h>
#import <objc/message.h>
#import <stdatomic.h>

// returns the method implementation for the selector
extern IMP
QMethodImplementationForSEL(Class aClass, SEL aSelector);

// as above but gets class method
extern IMP
QClassMethodImplementationForSEL(Class aClass, SEL aSelector);


extern BOOL
QClassMethodOverrideFromClass(Class targetClass, SEL targetSelector,
                              Class replacementClass, SEL replacementSelector);

extern BOOL
QInstanceMethodOverrideFromClass(Class targetClass, SEL targetSelector,
                                 Class replacementClass, SEL replacementSelector);


// q-runtime.m

static BOOL
_QMethodOverride(Class targetClass, SEL targetSelector, Method original, Method replacement)
{
    BOOL flag = NO;
    IMP imp = method_getImplementation(replacement);
    // we need something to work with
    if (replacement) {
        // if something was sitting on the SEL already
        if (original) {
            flag = method_setImplementation(original, imp) ? YES : NO;
            // if we're swapping, use this
            //method_exchangeImplementations(om, rm);
        } else {
            // not sure this works with class methods...
            // if it's not there we want to add it
            flag = YES;
            const char *types = method_getTypeEncoding(replacement);
            class_addMethod(targetClass,targetSelector,imp,types);
            XLog_FB(red,black,@"Not sure this works...");
        }
    }
    return flag;
}

BOOL
QInstanceMethodOverrideFromClass(Class targetClass, SEL targetSelector,
                                 Class replacementClass, SEL replacementSelector)
{
    BOOL flag = NO;
    if (targetClass && replacementClass) {
        Method om = class_getInstanceMethod(targetClass,targetSelector);
        Method rm = class_getInstanceMethod(replacementClass,replacementSelector);
        flag = _QMethodOverride(targetClass,targetSelector,om,rm);
    }
    return flag;
}


BOOL
QClassMethodOverrideFromClass(Class targetClass, SEL targetSelector,
                              Class replacementClass, SEL replacementSelector)
{
    BOOL flag = NO;
    if (targetClass && replacementClass) {
        Method om = class_getClassMethod(targetClass,targetSelector);
        Method rm = class_getClassMethod(replacementClass,replacementSelector);
        flag = _QMethodOverride(targetClass,targetSelector,om,rm);
    }
    return flag;
}

IMP
QMethodImplementationForSEL(Class aClass, SEL aSelector)
{
    Method method = class_getInstanceMethod(aClass,aSelector);
    if (method) {
        return method_getImplementation(method);
    } else {
        return NULL;
    }
}

IMP
QClassMethodImplementationForSEL(Class aClass, SEL aSelector)
{
    Method method = class_getClassMethod(aClass,aSelector);
    if (method) {
        return method_getImplementation(method);
    } else {
        return NULL;
    }
}

Je copie et colle ceci à partir de quelques fichiers depuis que q-runtime.h est ma bibliothèque réutilisable et ceci n'en est qu'une partie. Si quelque chose ne se compile pas, faites le moi savoir.

dbquarrel
la source
Vous n'êtes pas malchanceux en ce qui concerne le contrôle du comportement UIColor, comme indiqué dans cette question: stackoverflow.com/questions/56487679/…
raven_raven