На самом деле я просто написал код, который позволит вам глобально отказаться от темного режима в коде без необходимости путаться с каждым отдельным контроллером viw в вашем приложении. Вероятно, это можно улучшить, чтобы отказаться от класса за классом, управляя списком классов. Что касается меня, то я хочу, чтобы мои пользователи видели, нравится ли им интерфейс темного режима для моего приложения, и, если он им не нравится, они могут его отключить. Это позволит им продолжать использовать темный режим для остальных своих приложений.
Выбор пользователя - это хорошо (кхм, глядя на вас, Apple, вот как вы должны были это реализовать).
Итак, как это работает, так это то, что это просто категория UIViewController. При загрузке он заменяет собственный метод viewDidLoad на метод, который будет проверять глобальный флаг, чтобы узнать, отключен ли темный режим для всего или нет.
Поскольку он запускается при загрузке UIViewController, он должен автоматически запускаться и отключать темный режим по умолчанию. Если это не то, что вы хотите, то вам нужно попасть туда пораньше и установить флаг, или просто установить флаг по умолчанию.
Я еще не написал ничего, чтобы ответить пользователю, включающим или выключающим флаг. Итак, это в основном пример кода. Если мы хотим, чтобы пользователь взаимодействовал с этим, необходимо перезагрузить все контроллеры представления. Я не знаю, как это сделать навскидку, но, вероятно, отправка какого-нибудь уведомления поможет. Итак, сейчас это глобальное включение / выключение темного режима будет работать только при запуске или перезапуске приложения.
Теперь недостаточно просто попытаться отключить темный режим в каждом отдельном контроллере просмотра MFING в вашем огромном приложении. Если вы используете цветные ассеты, вы полностью разочарованы. Мы уже более 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 - моя многоразовая библиотека, и это лишь ее часть. Если что-то не компилируется, дайте мне знать.
person
dbquarrel
schedule
08.07.2019
UIUserInterfaceStyle
наLight
в вашем Info.Plist. См. developer.apple.com/library/archive/documentation/General/ - person Tieme   schedule 20.08.2019