我的应用程序未为黑暗模式做好准备,我今天不打算对其进行处理。
是否可以禁用我的应用的暗模式更改?
答案 0 :(得分:22)
Here is where Apple discusses opting out of light or dark mode:
下面的代码将退出特定的视图控制器:
override func viewDidLoad() {
super.viewDidLoad()
// Always adopt a light interface style.
overrideUserInterfaceStyle = .light
}
或者您可以使用following key in your info.plist文件选择退出整个应用程序:
UIUserInterfaceStyle
并为其分配值Light
。
答案 1 :(得分:0)
实际上,我只是编写了一些代码,这些代码使您可以在代码中全局选择退出暗模式,而不必在应用程序中使用每个viw控制器。可以通过管理班级列表来完善此方法以逐班退出。对我来说,我要让用户查看他们是否喜欢我的应用程序的暗模式界面,如果他们不喜欢它,则可以将其关闭。这将使他们能够在其余应用程序中继续使用暗模式。
用户选择是好的(哎呀,看着苹果,这是应该实现的方式。)
所以这是如何工作的只是UIViewController的一个类别。加载时,它将替换本机的viewDidLoad方法,该方法将检查全局标志以查看是否对所有功能都禁用了暗模式。
由于它是在加载UIViewController时触发的,因此默认情况下应自动启动并禁用暗模式。如果这不是您想要的,那么您需要早点到达那里并设置标志,否则只需设置默认标志即可。
我还没有写任何东西来回应用户打开或关闭该标志。因此,这基本上是示例代码。如果我们希望用户与此交互,则所有视图控制器都需要重新加载。我不知道如何立即执行此操作,但是可能会发送一些通知来解决问题。因此,目前,暗模式下的全局打开/关闭仅在启动或重新启动应用程序时起作用。
现在,在大型应用程序的每个MFING viewController中尝试关闭暗模式还不够。如果您正在使用色彩资产,那么您将完全陷入困境。我们十多年以来一直将不可变对象理解为不可变的。从颜色资产目录中获得的颜色表示它们是UIColor,但它们是动态(可变)颜色,并且随着系统从暗到亮模式的变化,它们在您的下方也会发生变化。那应该是一个功能。但是,当然没有主开关要求这些事情停止进行更改(据我所知,也许有人可以对此进行改进)。
因此,解决方案分为两部分:
UIViewController上的公共类别,提供了一些实用和便捷的方法...例如,我不认为苹果公司曾考虑过我们中的一些人将Web代码混入我们的应用程序这一事实。因此,我们需要基于暗或亮模式切换样式表。因此,您要么需要构建某种动态样式表对象(这会很好),要么仅询问当前状态是什么(不好但很容易)。
此类别在加载时将替换UIViewController类的viewDidLoad方法并拦截调用。我不知道这是否违反了应用商店的规则。如果是这样,可能还有其他方法可以解决,但您可以认为这是概念证明。例如,您可以使所有主要视图控制器类型的一个子类,并使您自己的所有视图控制器都继承自这些视图控制器,然后可以使用DarkMode类别提示并对其进行调用以强制选择退出所有视图控制器。这很丑陋,但不会违反任何规则。我更喜欢使用运行时,因为这是运行时要做的。因此,在我的版本中,您只需添加类别,就可以在类别上设置一个全局变量,以决定是否要阻止暗模式,
如上所述,您还没有走出困境,另一个问题是UIColor基本上可以完成所需的工作。因此,即使您的视图控制器阻止了黑暗模式,UIColor也不知道您在哪里或如何使用它,因此无法适应。结果,您可以正确地获取它,但是将来它将在某个时候恢复您的状态。也许很快,也许以后。因此,解决方法是使用CGColor对其分配两次,然后将其转换为静态颜色。这意味着,如果您的用户返回并在设置页面上重新启用暗模式(此处的目的是使此项工作正常进行,以便用户可以在系统的其余部分之上控制您的应用),所有这些静态颜色需要更换。到目前为止,这还有待其他人解决。最简单的方法是将您退出黑暗模式的默认值设为默认值,除以零会使应用程序崩溃,因为您无法退出该应用程序并告诉用户仅重新启动它。这可能也违反了应用商店指南,但这是一个主意。
不需要公开UIColor类别,它只需要调用colorNamed即可:...如果您不告诉DarkMode ViewController类阻止黑暗模式,它将按预期完美地工作。尝试使某些东西变得优雅,而不是标准的苹果花粉代码,这意味着如果要以编程方式选择退出暗模式或切换它,则必须修改大部分应用程序。现在,我不知道是否有更好的方法以编程方式更改Info.plist以根据需要关闭暗模式。据我所知,这是一个编译时功能,在此之后您便会陷入困境。
这是您需要的代码。应该插入,仅使用一种方法来设置UI样式或在代码中设置默认值。您可以出于任何目的随意使用,修改,做任何您想做的事情,并且不提供任何担保,而且我不知道它是否会通过应用程序商店。改进非常受欢迎。
公平警告,我不使用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;
// this is necessary because trying to allocate a trait collection directly based on user interfaca
// style will return the "any" color out of the color assets, which is obviously what we don't want
static UIViewController *_petViewController;
+ (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);
// sadly wasted space, but if you don't care about space you can make it on the fly
_petViewController = [UIViewController new];
}
}
}
// 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,*)) {
if (_override == UIUserInterfaceStyleUnspecified) {
// nothing to do, we got it via the normal way because the bundle needs to load
value = _nativeColorNamed(self,@selector(colorNamed:),string);
} else {
[_petViewController setOverrideUserInterfaceStyle:_override];
UITraitCollection *tc = _petViewController.traitCollection;
// below is the way that would be nice to do it, but doesn't work
//[UITraitCollection traitCollectionWithUserInterfaceStyle:_override];
value = [UIColor colorNamed:string inBundle:nil compatibleWithTraitCollection:tc];
// the value we have is a dynamic color... this obviously breaks EVERYTHING we know about
// UIKit programming, where the object is basically a mutable object posing as an immutable
// one ... so we need to force it to become immutable and we can do that by making a new color
// out of the CGColor ... otherwise as the app continues to run, the color we hand out here
// is going to revert to whatever mode the operating system is in even when we specified dark
// or light to begin with
value = [UIColor colorWithCGColor:value.CGColor];
}
} 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是我的可重用库,而这只是其中的一部分。如果无法编译,请告诉我。