Насправді я просто написав якийсь код, який дозволить вам глобально відмовитися від темного режиму в коді без необхідності путцу з кожним контролером viw у вашій програмі. Це, ймовірно, може бути вдосконалено для відмови від класу за класом, керуючи списком класів. Для мене те, що я хочу, щоб мої користувачі бачили, чи подобається їм темний режим інтерфейсу для моєї програми, і якщо їм це не подобається, вони можуть вимкнути його. Це дозволить їм продовжувати використовувати темний режим для решти програм.
Вибір користувача хороший (Ахм, дивлячись на тебе Apple, саме так ти і мав це реалізувати).
Отже, як це працює, це лише категорія UIViewController. Коли він завантажується, він замінює нативний метод viewDidLoad на той, який перевіряє глобальний прапор, щоб побачити, чи не вимкнено темний режим для всього чи ні.
Оскільки він запускається під час завантаження UIViewController, він повинен автоматично запускатись та вимикати темний режим за замовчуванням. Якщо це не те, що ви хочете, тоді вам потрібно десь рано потрапити туди і встановити прапор, а також просто встановити прапор за замовчуванням.
Я ще не написав нічого, щоб відповісти на те, що користувач вмикає або вимикає прапор. Отже, це в основному приклад коду. Якщо ми хочемо, щоб користувач взаємодіяв з цим, всі контролери перегляду повинні буде перезавантажитись. Я не знаю, як це зробити напролом, але, ймовірно, відправлення якогось повідомлення збирається зробити трюк. Тож зараз цей глобальний режим увімкнення / вимкнення темного режиму працює лише при запуску або перезапуску програми.
Тепер недостатньо лише намагатися вимкнути темний режим у кожному окремому представленні MFING viewController у величезному додатку. Якщо ви використовуєте кольорові активи, то ви повністю виснажені. Ми протягом 10+ років розуміли незмінні предмети як непорушні. Кольори, які ви отримуєте з каталогу кольорових активів, говорять про те, що вони є UIColor, але вони є динамічними (змінні) кольорами і змінюватимуться під вами під час зміни системи від темного до світлого режиму. Це має бути особливістю. Але, звичайно, немає головного перемикання, щоб попросити ці речі припинити вносити ці зміни (наскільки я зараз знаю, можливо, хтось може це покращити).
Тож рішення складається з двох частин:
публічна категорія на UIViewController, яка дає деякі корисні та зручні методи ... наприклад, я не думаю, що Apple задумався над тим, що деякі з нас змішують веб-код у наші додатки. Як такі, у нас є таблиці стилів, які потрібно змінювати на основі темного або світлого режиму. Таким чином, вам або потрібно побудувати якийсь об’єкт динамічного стилю (що було б добре), або просто запитати, що таке поточний стан (поганий, але легкий).
ця категорія, коли вона завантажується, замінить метод viewDidLoad класу UIViewController і перехопить виклики. Я не знаю, чи це порушує правила магазину додатків. Якщо це так, можливо, є й інші способи, але ви можете вважати це доказом концепції. Наприклад, ви можете зробити один підклас усіх основних типів контролерів перегляду і зробити так, щоб усі ваші власні контролери перегляду успадковували їх, і тоді ви можете використовувати ідею категорії DarkMode і закликати її, щоб змусити вимкнути всі контролери перегляду. Це гірше, але порушувати правила не збирається. Я вважаю за краще використовувати час виконання, оскільки саме так було виконано час виконання. Тож у моїй версії ви просто додаєте категорію, ви встановлюєте глобальну змінну на категорію для того, чи хочете ви, щоб вона блокувала темний режим, і вона зробить це.
Ви ще не вийшли з лісу, як уже згадувалося, інша проблема - це UIColor, в основному робить все, що чорт хоче. Тож навіть якщо ваші контролери перегляду блокують темний режим, UIColor не знає, де і як ви його використовуєте, тому не може адаптуватися. Як результат, ви можете отримати це правильно, але тоді він повернеться до вас в якийсь момент майбутнього. Може, скоро, можливо, пізніше. Тож обхід цього полягає в тому, щоб виділити його двічі за допомогою CGColor і перетворити його в статичний колір. Це означає, що якщо ваш користувач повернеться і знову ввімкне темний режим на вашій сторінці налаштувань (ідея тут полягає в тому, щоб зробити цю роботу так, щоб користувач мав контроль над вашим додатком над і над усією системою), всі ці статичні кольори потребують заміни. Поки що це залишається, щоб хтось інший вирішив. Найпростіший спосіб зробити це - зробити за замовчуванням те, що ви ' вимкнувши темний режим, розділіть на нуль, щоб вийти з програми, оскільки ви не можете вийти з нього та скажіть користувачеві просто перезапустити його. Це, ймовірно, також порушує правила використання магазину додатків, але це ідея.
Категорію UIColor не потрібно виставляти, вона просто працює з викликом colorNamed: ... якщо ви не сказали класу DarkMode ViewController блокувати темний режим, він буде працювати прекрасно, як очікувалося. Намагаючись зробити щось елегантне замість стандартного коду яблучного сфагетті, що означає, що вам доведеться змінити більшість додатків, якщо ви хочете програмно вимкнути темний режим або переключити його. Тепер я не знаю, чи є кращий спосіб програмної зміни програми Info.plist для відключення темного режиму за необхідності. Наскільки я розумію, це функція часу компіляції, і після цього ви змушені.
Отже ось код, який вам потрібен. Слід запустити і просто використовувати один метод, щоб встановити стиль інтерфейсу або встановити за замовчуванням у коді. Ви можете користуватися, змінювати, робити все, що завгодно, з будь-якими цілями, і гарантії не даються, і я не знаю, чи пройде це магазин додатків. Покращення дуже вітаються.
Справедливе попередження: я не використовую ARC або будь-які інші методи тримання.
////// 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
Існує набір функцій утиліти, які використовується для заміни методів. Окремий файл. Це стандартні речі, але подібний код ви можете знайти де завгодно.
// 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;
}
}
Я копіюю та вставляю це з декількох файлів, оскільки q-runtime.h - це моя багаторазова бібліотека, і це лише частина її. Якщо щось не складається, дайте мені знати.
UIUserInterfaceStyle
дляLight
вашої info.plist. Дивіться developer.apple.com/library/archive/documentation/General/…