In realtà ho appena scritto del codice che ti permetterà di disattivare globalmente la modalità dark nel codice senza dover putz con ogni singolo controller viw nella tua applicazione. Questo può probabilmente essere perfezionato per rinunciare a una classe per classe gestendo un elenco di classi. Per me, ciò che voglio è che i miei utenti vedano se a loro piace l'interfaccia in modalità oscura per la mia app e, se non gli piace, possono disattivarla. Ciò consentirà loro di continuare a utilizzare la modalità oscura per il resto delle loro applicazioni.
La scelta dell'utente è buona (Ahem, guardandoti Apple, è così che dovresti averlo implementato).
Quindi, come funziona è che è solo una categoria di UIViewController. Quando viene caricato, sostituisce il metodo viewDidLoad nativo con uno che controllerà un flag globale per vedere se la modalità dark è disabilitata per tutto o no.
Poiché viene attivato al caricamento di UIViewController, dovrebbe avviarsi automaticamente e disabilitare la modalità scura per impostazione predefinita. Se questo non è quello che vuoi, allora devi entrare lì da qualche parte in anticipo e impostare il flag, oppure semplicemente impostare il flag predefinito.
Non ho ancora scritto nulla per rispondere all'utente attivando o disattivando la bandiera. Quindi questo è fondamentalmente un codice di esempio. Se vogliamo che l'utente interagisca con questo, tutti i controller di visualizzazione dovranno essere ricaricati. Non so come farlo a mano, ma probabilmente l'invio di una notifica farà il trucco. Quindi in questo momento, questo on / off globale per la modalità dark funzionerà solo all'avvio o al riavvio dell'app.
Ora, non è sufficiente provare a disattivare la modalità oscura in ogni singolo controller MFING view nella tua enorme app. Se stai utilizzando le risorse di colore, sei completamente disossato. Per oltre 10 anni abbiamo capito che gli oggetti immutabili sono immutabili. I colori che ottieni dal catalogo delle risorse colore dicono che sono UIColor ma sono colori dinamici (mutabili) e cambieranno sotto di te quando il sistema passa dalla modalità scura a quella chiara. Questa dovrebbe essere una caratteristica. Ma ovviamente non c'è nessun maestro a chiedere a queste cose di smettere di fare questo cambiamento (per quanto ne so adesso, forse qualcuno può migliorarlo).
Quindi la soluzione è in due parti:
una categoria pubblica su UIViewController che offre alcuni metodi di utilità e convenienza ... ad esempio, non credo che apple abbia pensato al fatto che alcuni di noi si mescolano nel codice Web nelle nostre app. Pertanto, abbiamo fogli di stile che devono essere attivati in base alla modalità scuro o chiaro. Quindi, o hai bisogno di costruire un qualche tipo di oggetto di foglio di stile dinamico (che sarebbe buono) o semplicemente chiedere quale sia lo stato attuale (cattivo ma facile).
questa categoria durante il caricamento sostituirà il metodo viewDidLoad della classe UIViewController e intercetterà le chiamate. Non so se questo infrange le regole dell'app store. Se lo fa, ci sono altri modi per aggirare questo probabilmente, ma puoi considerarlo una prova di concetto. Ad esempio, puoi creare una sottoclasse di tutti i tipi di controller di visualizzazione principali e far ereditare tutti i controller di visualizzazione da quelli, quindi puoi utilizzare l'idea della categoria DarkMode e chiamarci per forzare la disattivazione di tutti i controller di visualizzazione. È più brutto ma non infrange alcuna regola. Preferisco usare il runtime perché è quello che ha fatto il runtime. Quindi nella mia versione aggiungi semplicemente la categoria, imposti una variabile globale sulla categoria per decidere se bloccare o meno la modalità oscura, e lo farà.
Non sei ancora fuori dal bosco, come già detto, l'altro problema è che UIColor sta praticamente facendo quello che diavolo vuole. Quindi, anche se i controller della vista bloccano la modalità dark UIColor non sa dove o come lo stai usando, quindi non può adattarsi. Di conseguenza, puoi recuperarlo correttamente, ma poi ti verrà ripristinato in futuro. Forse presto, forse dopo. Quindi il modo per aggirare questo è allocandolo due volte usando un CGColor e trasformandolo in un colore statico. Ciò significa che se l'utente torna indietro e riattiva la modalità oscura nella pagina delle impostazioni (l'idea qui è di far funzionare questo in modo che l'utente abbia il controllo sulla tua app sopra e sopra il resto del sistema), tutti quei colori statici è necessario sostituire. Finora questo è lasciato a qualcun altro da risolvere. Il modo più semplice per farlo è quello di fare un default che tu ' disattivando la modalità oscura, dividi per zero per arrestare l'app in quanto non puoi uscire e dire all'utente di riavviarla. Ciò probabilmente viola anche le linee guida dell'App Store, ma è un'idea.
La categoria UIColor non ha bisogno di essere esposta, funziona solo chiamando colorNamed: ... se non hai detto alla classe DarkMode ViewController di bloccare la modalità oscura, funzionerà perfettamente come previsto. Cercare di creare qualcosa di elegante al posto del codice standard di Apple Sphaghetti, il che significa che dovrai modificare la maggior parte della tua app se vuoi disattivare programmativamente la modalità oscura o attivarla. Ora non so se esiste un modo migliore di modificare programmaticamente Info.plist per disattivare la modalità oscura, se necessario. Per quanto riguarda la mia comprensione, questa è una funzione di compilazione del tempo e, successivamente, vieni disossato.
Quindi ecco il codice che ti serve. Dovrebbe essere inserito e usare solo un metodo per impostare lo stile dell'interfaccia utente o impostare il valore predefinito nel codice. Sei libero di usare, modificare, fare ciò che vuoi con questo per qualsiasi scopo e non viene data alcuna garanzia e non so se passerà all'app store. Miglioramenti molto graditi.
Avviso equo Non utilizzo ARC o altri metodi di gestione.
////// 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
Esiste una serie di funzioni di utilità che utilizza per eseguire lo scambio di metodi. File separato. Questa è roba standard e puoi trovare codice simile ovunque.
// 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;
}
}
Sto copiando e incollando questo da un paio di file poiché q-runtime.h è la mia libreria riutilizzabile e questa è solo una parte di essa. Se qualcosa non si compila fammi sapere.
UIUserInterfaceStyle
suLight
nel tuo Info.Plist. Vedi developer.apple.com/library/archive/documentation/General/…