在iOS 13上可以选择退出暗模式吗?


295

我的应用程序很大一部分由Web视图组成,以提供本机实现尚未提供的功能。Web团队没有计划为网站实施深色主题。因此,在iOS 13上使用暗模式支持时,我的应用看上去会有点一半。

是否可以选择不支持暗模式,以便我们的应用程序始终显示亮模式以匹配网站主题?


70
在您的Info.Plist中设置UIUserInterfaceStyleLight。参见 developer.apple.com/library/archive/documentation/General/…–
Tieme,

1
感谢您提出要求-我们所有人。很多应用程序都需要经历。这是保持应用程序正常运行直到切换准备就绪的必要条件。
user3741598

导入Foundation导入UIKit扩展UIViewController {重写open func awakeFromNib(){super.awakeFromNib()如果#available(iOS 13.0,*){//始终采用浅色界面样式。OverrideUserInterfaceStyle = .light}}}
Mohammad Razipour

1
只需在plist中添加UIUserInterfaceStyle。就是这么简单
Fattie

在将应用程序提交到appstore时,由于UIUserInterfaceStyle在Light模式下,Apple接受了。
kiran,

Answers:


680

首先,这是与退出暗模式相关的Apple条目此链接的内容适用于Xcode 11和iOS 13

本部分适用于Xcode 11的用法


如果您希望退出整个申请

方法1

在info.plist文件中使用以下密钥

UIUserInterfaceStyle

并为其分配值Light

XMLUIUserInterfaceStyle分配:

<key>UIUserInterfaceStyle</key>
<string>Light</string>

方法#2

您可以overrideUserInterfaceStyle针对应用程序的window变量进行设置。

根据项目的创建方式,该AppDelegate文件可能位于文件中,也可能位于中SceneDelegate

if #available(iOS 13.0, *) {
    window?.overrideUserInterfaceStyle = .light
}


如果您希望逐个退出UIViewController

override func viewDidLoad() {
    super.viewDidLoad()
    // overrideUserInterfaceStyle is available with iOS 13
    if #available(iOS 13.0, *) {
        // Always adopt a light interface style.
        overrideUserInterfaceStyle = .light
    }
}

Apple文档overrideUserInterfaceStyle

上面的代码在Xcode 11中的样子:

在此处输入图片说明

本部分适用于Xcode 10.x的用法


如果您使用Xcode 11进行提交,则可以放心忽略此行下的所有内容。

由于相关的API在iOS 12中不存在,因此在尝试使用上面提供的值时会出现错误:

对于设置overrideUserInterfaceStyleUIViewController

在此处输入图片说明

如果您希望逐个退出UIViewController

这可以通过测试编译器版本和iOS版本在Xcode 10中进行处理:

#if compiler(>=5.1)
if #available(iOS 13.0, *) {
    // Always adopt a light interface style.
    overrideUserInterfaceStyle = .light
}
#endif

如果您希望退出整个申请

通过将以下代码添加到AppDelegate文件中,您可以修改上述代码段以使其适用于Xcode 10的整个应用程序。

#if compiler(>=5.1)
if #available(iOS 13.0, *) {
    // Always adopt a light interface style.
    window?.overrideUserInterfaceStyle = .light
}
#endif

但是,使用Xcode版本10.x时,plist设置将失败:

在此处输入图片说明

感谢@Aron尼尔森@Raimundas Sakalauskas@NSLeaderrmaddy为改善这样的回答与他们的反馈。


2
立即更新/上传应用程序时,UIUserInterfaceStyle指示灯被阻止。它被标记为无效的plist条目。(无效的plist密钥)
Aron Nelson

2
无法针对iOS SDK 12(当前最新的稳定SDK)进行编译。有关也适用于iOS 12 SDK的解决方案,请参见stackoverflow.com/a/57521901/2249485
Raimundas Sakalauskas

这是不公平的,以至于具有比“原始问题”更多视图的问题被锁定以提供答案。:(
Raimundas Sakalauskas

7
您无需overrideUserInterfaceStyleviewDidLoad每个视图控制器中进行设置,而是可以在应用程序的主窗口中对其进行一次设置。如果您想让整个应用程序表现为一种方式,那就容易得多。
rmaddy19

2
使用#if compiler(>=5.1)替代responds(to:)setValue
NSLeader

162

根据苹果的“实施暗模式在iOS”(会话https://developer.apple.com/videos/play/wwdc2019/214/开始于31:13),可以集overrideUserInterfaceStyleUIUserInterfaceStyleLightUIUserInterfaceStyleDark任何视图控制器或视图上,将用于traitCollection用于任何子视图或视图控制器。

正如SeanR已经提到的,您可以设置UIUserInterfaceStyleLightDark应用程式的plist文件来改变这种为您的整个应用程序。


17
如果您设置UIUserInterfaceStyle密钥,则您的应用将在应用商店中被拒绝
Sonius

2
苹果拒绝使用ITMS-90190错误代码forums.developer.apple.com/thread/121028
PRASAD1240

11
拒绝最有可能发生,因为iOS 13 SDK尚未超出Beta版。我认为这应该在Xcode 11 GM可用后立即生效。
dorbeetle

2
@dorbeetle这不是真的,我像1个月前用Xcode 10一样成功地用此密钥上传了我的应用程序。拒绝是最近发生的。苹果似乎采取了一些新的策略。
史蒂芬

1
它仍在发生。Xcode GM2返回了应用程序签名错误。Xcode 10.3返回:“无效的Info.plist密钥。有效载荷/Galileo.appInfo.plist文件中的密钥'UIUserInterfaceStyle'无效。
Evgen Bodunov

64

如果您未使用Xcode 11或更高版本(即iOS 13或更高版本的SDK),则您的应用尚未自动选择支持暗模式。因此,无需选择退出黑暗模式。

如果您使用的是Xcode 11或更高版本,则系统已自动为您的应用启用暗模式。有两种方法可以根据您的喜好禁用暗模式。您可以完全禁用它,也可以对任何特定的窗口,视图或视图控制器禁用它。

完全为您的应用禁用暗模式

您可以通过在UIUserInterfaceStyle键中包含与Light应用程序的Info.plist文件中相同的值来禁用暗模式。 这会忽略用户的偏好,并始终为您的应用程序添加浅色外观。
UIUserInterfaceStyle为浅色

对窗口,视图或视图控制器禁用暗模式

您可以通过设置界面来强制界面始终以浅色或深色显示。 overrideUserInterfaceStyle适当的窗口,视图或视图控制器属性,显示。

查看控制器:

override func viewDidLoad() {
    super.viewDidLoad()
    /* view controller’s views and child view controllers 
     always adopt a light interface style. */
    overrideUserInterfaceStyle = .light
}

观看次数:

// The view and all of its subviews always adopt light style.
youView.overrideUserInterfaceStyle = .light

窗口:

/* Everything in the window adopts the style, 
 including the root view controller and all presentation controllers that 
 display content in that window.*/
window.overrideUserInterfaceStyle = .light

注意:Apple强烈建议您在应用中支持暗模式。因此,您只能暂时禁用黑暗模式。

在此处了解更多信息:为iOS应用选择特定的界面样式


34

********** Xcode 11及更高版本的最简单方法***********

将此添加到info.plist之前 </dict></plist>

<key>UIUserInterfaceStyle</key>
<string>Light</string>

从Xcode 10.x上提交应用程序时,此解决方案将失败
Tawfik Bouabid

27

我想我已经找到了解决方案。我最初是从UIUserInterfaceStyle-信息属性列表UIUserInterfaceStyle-UIKit拼凑而成的,但是现在发现它确实记录在为iOS应用选择特定界面样式中

在中info.plist,将UIUserInterfaceStyle用户界面样式)设置为1UIUserInterfaceStyle.light)。

编辑:根据dorbeetle的回答,UIUserInterfaceStyle可能更合适的设置是Light


通过将值设置为2来强制使用暗模式无法正常工作:[UIInterfaceStyle] '2' is not a recognized value for UIUserInterfaceStyle. Defaulting to Light.
funkenstrahlen

3
在plist中拥有此密钥将导致App Store拒绝。
何塞”

1
AppStore不再在plist.info中拒绝此属性。我把“ Dark”(大写)放进去,因为我们的应用程序已经很暗了。没问题。这可以让我们正确使用系统控件。
nickdnk

@nickdnk我认为您使用Apple推荐的Xcode 11构建了您的应用程序。
DawnSong

1
是的,我做到了。Apple确实在plist中接受了此参数,这一事实并没有改变,这就是我试图阐明的事实。
nickdnk

23

如果您想退出整个应用程序,则上述答案有效。如果您使用的是具有UI的lib,并且您对.plist的编辑并不奢侈,那么也可以通过代码来实现。

如果您要针对iOS 13 SDK进行编译,则只需使用以下代码即可:

迅速:

if #available(iOS 13.0, *) {
    self.overrideUserInterfaceStyle = .light
}

对象:

if (@available(iOS 13.0, *)) {
    self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}

但是,如果您也希望代码也可以针对iOS 12 SDK进行编译(目前仍是最新的稳定SDK),则应使用选择器。带选择器的代码:

Swift(XCode将显示此代码的警告,但这是目前唯一的方法,因为SDK 12中不存在该属性,因此无法编译):

if #available(iOS 13.0, *) {
    if self.responds(to: Selector("overrideUserInterfaceStyle")) {
        self.setValue(UIUserInterfaceStyle.light.rawValue, forKey: "overrideUserInterfaceStyle")
    }
}

对象:

if (@available(iOS 13.0, *)) {
    if ([self respondsToSelector:NSSelectorFromString(@"overrideUserInterfaceStyle")]) {
        [self setValue:@(UIUserInterfaceStyleLight) forKey:@"overrideUserInterfaceStyle"];
    }
}

如果指定属性overrideUserInterfaceStyle属于哪个属性会更好。
DawnSong

12

最近更新-

如果您使用的是Xcode 10.x,则默认UIUserInterfaceStyle值为lightiOS13.x。在iOS 13设备上运行时,它将仅在灯光模式下工作。

无需UIUserInterfaceStyle在Info.plist文件中显式添加密钥,添加密钥将在验证应用程序时出现错误,提示:

无效的Info.plist密钥。Payload / AppName.appInfo.plist文件中的键“ UIUserInterfaceStyle”无效。

UIUserInterfaceStyle使用Xcode 11.x时,仅将密钥添加到Info.plist文件中。


1
这与Xcode 10或11无关。如果用户部署Xcode 10形式的应用程序并且不处理暗模式,则该应用程序安装在iPhone 11,Pro或Pro Max中时将出现暗模式问题。您需要更新到Xcode 11并解决此问题。
Niranjan Molkeri

3
@NiranjanMolkeri这与较新的iPhone无关。这与iOS 13上的暗模式有关。在以前的iOS 13 Beta版本中,如果未明确处理,则UI会出现暗模式问题。但在最新版本中,此问题已修复。如果您使用的是XCode 10,则iOS13的默认UIUserInterfaceStyle是light。如果您使用的是Xode11,则需要进行处理。
kumarsiddharth123

如果使用Xcode 10.3将应用程序上载到TestFligth,并且plist包含键UIUserInterfaceStyle,则会遇到问题。它会说这是一个无效的plist文件。如果要在Xcode 10中进行构建,则必须将其删除,或者使用Xcode 11进行上传
eharo2

9

如果将UIUserInterfaceStyle密钥添加到plist文件,则Apple可能会拒绝此处所述的发行版本:https : //stackoverflow.com/a/56546554/7524146 无论如何,明确告诉每个ViewController都是 很烦的self.overrideUserInterfaceStyle = .light。但是,您可以对根window对象使用一次这种安全的代码:

if #available(iOS 13.0, *) {
    if window.responds(to: Selector(("overrideUserInterfaceStyle"))) {
        window.setValue(UIUserInterfaceStyle.light.rawValue, forKey: "overrideUserInterfaceStyle")
    }
}

只是请注意,您无法在内部执行此操作,application(application: didFinishLaunchingWithOptions:)因为对于该选择器true,它在早期不会做出响应。但是您以后可以做。如果您在应用程序中使用自定义AppPresenterAppRouter类,而不是自动在AppDelegate中启动UI,则超级简单。


9

您可以在Xcode 11中的整个应用程序中关闭“ 暗模式”

  1. 前往Info.plist
  2. 像下面一样添加波纹管

    <key>UIUserInterfaceStyle</key>
    <string>Light</string>

Info.plist如下所示...

在此处输入图片说明


1
由于某些原因不适用于Xcode 11.3.1(11C504)
Andrew

7

-对于整个应用程序(窗口):

window!.overrideUserInterfaceStyle = .light

您可以从获取窗口 SceneDelegate

-对于单个ViewController:

viewController.overrideUserInterfaceStyle = .light

您可以设置任何viewController,甚至连它的viewController内部自我

-对于单个视图:

view.overrideUserInterfaceStyle = .light

您可以设置任何view,甚至里面的观点是自我

if #available(iOS 13.0, *) { ,,, }如果支持较早的iOS版本,则可能需要使用。


6

除了其他响应之外,据我所知,您仅需要在针对iOS 13 SDK(使用XCode 11)进行编译时为Dark模式做好准备。

系统假定与iOS 13或更高版本的SDK链接的应用程序支持亮外观和暗外观。在iOS中,您可以通过为窗口,视图或视图控制器分配特定的界面样式来指定所需的特定外观。您也可以使用Info.plist键完全禁用对黑暗模式的支持。

链接


2

是的,您可以通过在viewDidLoad中添加以下代码来跳过:

if #available(iOS 13.0, *) {
        // Always adopt a light interface style.
        overrideUserInterfaceStyle = .light
    }

2

我的应用程序目前不支持深色模式,并使用浅色应用程序栏颜色。通过向我添加以下键,我可以将状态栏内容强制为深色文本和图标Info.plist

<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDarkContent</string>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>

在此处找到其他可能的值:https : //developer.apple.com/documentation/uikit/uistatusbarstyle


2

Objective-C版本

 if (@available(iOS 13.0, *)) {
        _window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    }

1

您可以在应用中使用以下提示和技巧来支持或绕过黑暗模式。

第一个技巧:重写ViewController样式

您可以通过重写UIViewController的界面样式

1:overrideUserInterfaceStyle = .dark //用于黑暗模式

2:overrideUserInterfaceStyle = .light //用于灯光模式

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        overrideUserInterfaceStyle = .light    
    }
}

第二个技巧:在info.plist中添加密钥

只需添加一个新密钥

UIUserInterfaceStyle

在您的应用程序info.plist中,并将其值设置为Light或Dark。这会将应用默认样式覆盖为您提供的值。您不必在每个viewController中添加overrideUserInterfaceStyle = .light,只需在info.plist中添加一行即可。



1
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }

您能否解释一下此答案将如何解决问题,而不是发布仅代码的答案。
阿伦·维诺斯

是的,肯定@ArunVinoth在IOS 13中引入了暗模式,因此,如果您的部署目标低于13,则使用上面的代码,否则您可以使用if块中编写的简单语句。
Talha Rasool

0

由于窗口属性可能会在应用程序生命周期中更改,因此我将使用此解决方案。因此,需要重复分配“ overrideUserInterfaceStyle = .light”。UIWindow.appearance()使我们能够设置默认值,该默认值将用于新创建的UIWindow对象。

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

      if #available(iOS 13.0, *) {
          UIWindow.appearance().overrideUserInterfaceStyle = .light
      }

      return true
    }
}

0

只需将这些行添加到info.plist文件中:

<key>UIUserInterfaceStyle</key>
<string>light</string>

这将强制应用仅在轻模式下运行。


这已经被评论并回答了很多次。即使是公认的答案也暗示了这一点。因此,此注释不会添加任何新信息。
JeroenJK


0

您可以执行以下操作:将这个新键UIUserInterfaceStyle添加到Info.plist并将其值设置为Light。并检查警报控制器以灯光模式出现。

UIUserInterfaceStyle浅色如果通过在用户Info.plist文件中添加键UIUserInterfaceStyle并将其值设置为Light或Dark,则在整个应用程序中强制亮/暗模式(无论用户设置如何)。


0

这个问题有很多答案,info.plist您可以AppDelegate像这样使用它来设置它:

#if compiler(>=5.1)
        if #available(iOS 13.0, *) {
            self.window?.overrideUserInterfaceStyle = .light
        }
        #endif

在Xcode 11.3,iOS 13.3上测试


0

斯威夫特5

有两种将暗模式切换为亮模式的方法:

1- info.plist

    <key>UIUserInterfaceStyle</key>
    <string>Light</string>

2-以编程方式

 UIApplication.shared.windows.forEach { window in
     window.overrideUserInterfaceStyle = .light
  } 

-8

实际上,我只是编写了一些代码,使您可以在代码中全局选择退出暗模式,而不必在应用程序中使用每个viw控制器。通过管理课程列表,可以将其细化为逐课程退出。对我来说,我想要的是让用户查看他们是否喜欢我的应用程序的暗模式界面,如果他们不喜欢它,则可以将其关闭。这将使他们能够在其余应用程序中继续使用暗模式。

用户的选择很好(哎呀,看着苹果,这是您应该如何实现的)。

因此,这是UIViewController的一种。加载时,它将替换本机的viewDidLoad方法,该方法将检查全局标志以查看是否对所有内容都禁用了暗模式。

由于它是在加载UIViewController时触发的,因此默认情况下应会自动启动并禁用暗模式。如果这不是您想要的,那么您需要及早到达某处并设置标志,否则只需设置默认标志即可。

我还没有写任何内容来回应用户打开或关闭该标志。因此,这基本上是示例代码。如果我们希望用户与此交互,则所有视图控制器都需要重新加载。我不知道如何立即执行此操作,但可能会发送一些通知来解决问题。因此,目前,暗模式下的全局打开/关闭仅在启动或重新启动应用程序时起作用。

现在,仅在大型应用程序中的每个MFING viewController中尝试关闭暗模式是不够的。如果您正在使用色彩资产,那么您将完全陷入困境。我们十多年以来一直将不可变对象理解为不可变的。从颜色资产目录中获得的颜色表示它们是UIColor,但它们是动态(可变)颜色,并且随着系统从暗到亮模式的变化,它们在您的下方也会发生变化。那应该是一个功能。但是,当然没有主开关要求这些事情停止进行更改(据我目前所知,也许有人可以对此进行改进)。

因此,解决方案分为两部分:

  1. UIViewController上的一个公共类别,提供了一些实用和便捷的方法……例如,我不认为苹果公司曾考虑过我们中的一些人将Web代码混入我们的应用程序这一事实。因此,我们需要基于暗或亮模式切换样式表。因此,您要么需要构建某种动态样式表对象(这会很好),要么仅询问当前状态是什么(不好但很容易)。

  2. 加载时此类别将替换UIViewController类的viewDidLoad方法并拦截调用。我不知道这是否违反了应用商店规则。如果是这样,可能还有其他方法可以解决,但您可以认为这是概念证明。例如,您可以使所有主要视图控制器类型的一个子类,并使您自己的所有视图控制器都继承自这些视图控制器,然后您可以使用DarkMode类别提示并对其进行调用以强制选择退出所有视图控制器。这很丑陋,但不会违反任何规则。我更喜欢使用运行时,因为这是运行时要做的。因此,在我的版本中,您只需添加类别,然后在该类别上设置一个全局变量即可确定是否要阻止暗模式,并且它将执行此操作。

  3. 如前所述,您还没有走出困境,另一个问题是UIColor基本上会做任何想要的事情。因此,即使您的视图控制器阻止了暗模式,UIColor也不知道您在哪里或如何使用它,因此无法适应。结果,您可以正确地获取它,但是将来它会在将来的某个时候恢复。也许很快,也许以后。因此,解决方法是使用CGColor对其分配两次,然后将其转换为静态颜色。这意味着,如果您的用户返回并在设置页面上重新启用了暗模式(此处的目的是使此功能起作用,以便用户可以在系统其余部分之上控制您的应用),所有这些静态颜色需要更换。到目前为止,这还有待其他人解决。最简单的方法是将您的 如果您退出黑暗模式,请除以零,以使应用程序崩溃,因为您无法退出该应用程序,并告诉用户重新启动它。这可能也违反了应用商店指南,但这是一个主意。

UIColor类别不需要公开,只需要调用colorNamed即可:...如果您不告诉DarkMode ViewController类阻止暗模式,它将按预期完美地工作。尝试制作出精美的东西,而不是标准的Apple sphaghetti代码,这意味着如果要以编程方式选择退出暗模式或切换它,则必须修改大部分应用程序。现在,我不知道是否有更好的方法以编程方式更改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;
+ (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是我的可重用库,而这只是其中的一部分。如果无法编译,请告诉我。


正如这个问题所讨论的,在控制UIColor行为方面,您并非不走运:stackoverflow.com/questions/56487679/…–
raven_raven
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.