Eigentlich habe ich gerade einen Code geschrieben, mit dem Sie den Dunkelmodus im Code global deaktivieren können, ohne mit jedem einzelnen viw-Controller in Ihrer Anwendung putzeln zu müssen. Dies kann wahrscheinlich verfeinert werden, um Klasse für Klasse durch Deaktivieren einer Klassenliste zu deaktivieren. Ich möchte, dass meine Benutzer sehen, ob ihnen die Dunkelmodus-Oberfläche für meine App gefällt, und wenn sie sie nicht mögen, können sie sie deaktivieren. Dadurch können sie den Dunkelmodus für den Rest ihrer Anwendungen weiterhin verwenden.
Die Auswahl der Benutzer ist gut (Ahem, wenn Sie sich Apple ansehen, sollten Sie es so implementiert haben).
Das funktioniert also so, dass es nur eine Kategorie von UIViewController ist. Beim Laden wird die native viewDidLoad-Methode durch eine ersetzt, die ein globales Flag überprüft, um festzustellen, ob der Dunkelmodus für alles deaktiviert ist oder nicht.
Da es beim Laden von UIViewController ausgelöst wird, sollte es automatisch gestartet und der Dunkelmodus standardmäßig deaktiviert werden. Wenn dies nicht das ist, was Sie wollen, müssen Sie irgendwo früh einsteigen und das Flag setzen, oder setzen Sie einfach das Standard-Flag.
Ich habe noch nichts geschrieben, um dem Benutzer zu antworten, der die Flagge ein- oder ausschaltet. Das ist also im Grunde ein Beispielcode. Wenn der Benutzer damit interagieren soll, müssen alle Ansichts-Controller neu geladen werden. Ich weiß nicht, wie ich das ohne weiteres machen soll, aber wahrscheinlich reicht es aus, eine Benachrichtigung zu senden. Im Moment funktioniert dieses globale Ein / Aus für den Dunkelmodus nur beim Start oder Neustart der App.
Jetzt reicht es nicht mehr aus, den Dunkelmodus in jedem einzelnen MFING viewController in Ihrer riesigen App auszuschalten. Wenn Sie Farbressourcen verwenden, sind Sie vollständig entbeint. Wir haben seit mehr als 10 Jahren unveränderliche Objekte als unveränderlich verstanden. Farben, die Sie aus dem Farb-Asset-Katalog erhalten, sind UIColor-Farben, aber dynamische (veränderbare) Farben, die sich unter Ihnen ändern, wenn das System vom Dunkel- in den Hell-Modus wechselt. Das soll ein Feature sein. Aber natürlich gibt es keinen Master-Schalter, der diese Dinge auffordert, diese Änderung zu beenden (soweit ich jetzt weiß, kann vielleicht jemand dies verbessern).
Die Lösung besteht also aus zwei Teilen:
Eine öffentliche Kategorie auf UIViewController, die einige nützliche und bequeme Methoden bietet. Ich glaube zum Beispiel nicht, dass Apple darüber nachgedacht hat, dass einige von uns Webcode in ihre Apps einmischen. Als solche haben wir Stylesheets, die basierend auf dem Dunkel- oder Hellmodus umgeschaltet werden müssen. Daher müssen Sie entweder eine Art dynamisches Stylesheet-Objekt erstellen (was gut wäre) oder einfach nach dem aktuellen Status fragen (schlecht, aber einfach).
Diese Kategorie ersetzt beim Laden die viewDidLoad-Methode der UIViewController-Klasse und fängt Aufrufe ab. Ich weiß nicht, ob das gegen die App Store-Regeln verstößt. Wenn dies der Fall ist, gibt es wahrscheinlich andere Möglichkeiten, aber Sie können es als Proof of Concept betrachten. Sie können beispielsweise eine Unterklasse aller Haupttypen von Ansichtscontrollern erstellen und alle Ihre eigenen Ansichtscontroller von diesen erben lassen. Anschließend können Sie die DarkMode-Kategorieidee verwenden und sie aufrufen, um das Deaktivieren aller Ihrer Ansichtscontroller zu erzwingen. Es ist hässlicher, aber es wird keine Regeln brechen. Ich bevorzuge die Verwendung der Laufzeit, da dies die Aufgabe der Laufzeit ist. In meiner Version fügen Sie einfach die Kategorie hinzu. Sie legen eine globale Variable für die Kategorie fest, ob sie den Dunkelmodus blockieren soll oder nicht.
Sie sind noch nicht aus dem Wald, wie bereits erwähnt, das andere Problem ist, dass UIColor im Grunde alles tut, was zum Teufel es will. Selbst wenn Ihre View Controller den Dunkelmodus blockieren, weiß UIColor nicht, wo oder wie Sie ihn verwenden, und kann sich daher nicht anpassen. Infolgedessen können Sie es korrekt abrufen, aber dann wird es irgendwann in der Zukunft auf Sie zurückgreifen. Vielleicht bald, vielleicht später. Um dies zu umgehen, müssen Sie es zweimal mit einer CGColor zuweisen und in eine statische Farbe umwandeln. Dies bedeutet, wenn Ihr Benutzer zurückkehrt und den Dunkelmodus auf Ihrer Einstellungsseite wieder aktiviert (die Idee hier ist, dass dies funktioniert, damit der Benutzer über den Rest des Systems hinaus die Kontrolle über Ihre App hat), all diese statischen Farben müssen ersetzt werden. Bisher muss dies jemand anderes lösen. Der einfachste Weg, dies zu tun, besteht darin, einen Standardwert festzulegen, den Sie ' Wenn Sie den Dunkelmodus wieder deaktivieren, teilen Sie ihn durch Null, um die App zum Absturz zu bringen, da Sie sie nicht beenden können, und weisen Sie den Benutzer an, sie einfach neu zu starten. Das verstößt wahrscheinlich auch gegen die Richtlinien des App Stores, aber es ist eine Idee.
Die UIColor-Kategorie muss nicht verfügbar gemacht werden. Sie ruft lediglich colorNamed auf: ... Wenn Sie der DarkMode ViewController-Klasse nicht mitgeteilt haben, dass sie den Dunkelmodus blockieren soll, funktioniert sie wie erwartet einwandfrei. Der Versuch, etwas Elegantes anstelle des Standard-Apple-Sphaghetti-Codes zu erstellen, bedeutet, dass Sie den größten Teil Ihrer App ändern müssen, wenn Sie den Dunkelmodus programmgesteuert deaktivieren oder umschalten möchten. Jetzt weiß ich nicht, ob es eine bessere Möglichkeit gibt, die Info.plist programmgesteuert zu ändern, um den Dunkelmodus nach Bedarf auszuschalten. Soweit ich weiß, ist dies eine Funktion zur Kompilierungszeit, und danach sind Sie entbeint.
Hier ist der Code, den Sie benötigen. Sollte vorbeischauen und nur die eine Methode verwenden, um den UI-Stil festzulegen oder den Standard im Code festzulegen. Es steht Ihnen frei, dies für jeden Zweck zu verwenden, zu ändern und zu tun, was immer Sie wollen, und es wird keine Garantie gegeben, und ich weiß nicht, ob es den App Store passieren wird. Verbesserungen sind sehr willkommen.
Faire Warnung Ich verwende kein ARC oder andere Handholding-Methoden.
////// 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
Es gibt eine Reihe von Dienstprogrammfunktionen, mit denen Methoden ausgetauscht werden. Separate Datei. Dies ist jedoch Standard und Sie können ähnlichen Code überall finden.
// 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;
}
}
Ich kopiere und füge dies aus ein paar Dateien ein, da q-runtime.h meine wiederverwendbare Bibliothek ist und dies nur ein Teil davon ist. Wenn etwas nicht kompiliert werden kann, lass es mich wissen.
UIUserInterfaceStyle
aufLight
Ihrem info.plist. Siehe developer.apple.com/library/archive/documentation/General/…