438

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

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

4

31 回答 31

997

首先,这是Apple与选择退出暗模式相关的条目。 此链接中的内容是为 Xcode 11 和 iOS 13 编写的

通过 info.plist 文件(Xcode 12)的整个应用程序

在 info.plist 文件中使用以下键:

UIUserInterfaceStyle

并为其赋值Light

作业的XMLUIUserInterfaceStyle

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

UIUserInterfaceStyle 的 Apple 文档


整个应用程序通过构建设置中的 info.plist (Xcode 13)

在此处输入图像描述


通过 window 属性打开整个应用程序窗口

您可以overrideUserInterfaceStyle针对应用程序的window变量进行设置。这将适用于出现在窗口中的所有视图。这在 iOS 13 中可用,因此对于支持以前版本的应用程序,您必须包括可用性检查。

根据您的项目的创建方式,这可能在AppDelegateorSceneDelegate文件中。

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

单独的 UIViewController 或 UIView

您可以overrideUserInterfaceStyle针对UIViewControllers 或UIView'overrideUserInterfaceStyle变量进行设置。这在 iOS 13 中可用,因此对于支持以前版本的应用程序,您必须包括可用性检查。

迅速

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

对于 Objective-C 中那些可怜的灵魂

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

当针对 设置时UIViewController,视图控制器及其子项采用定义的模式。

当针对 设置时UIView,视图及其子项采用定义的模式。

关于 overrideUserInterfaceStyle 的 Apple 文档


通过 SwiftUI 视图的单个视图

您可以设置preferredColorSchemelightdark。提供的值将设置演示文稿的配色方案。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Light Only")
            .preferredColorScheme(.light)
    }
}

对于preferredColorScheme 的Apple 文档


感谢@Aron Nelson@Raimundas Sakalauskas@NSLeader@rmaddy用他们的反馈改进了这个答案。

于 2019-06-11T14:58:27.407 回答
165

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

正如 SeanR 已经提到的,您可以设置UIUserInterfaceStyleLightDark在您的应用程序的 plist 文件中为您的整个应用程序更改此设置。

于 2019-06-11T14:11:28.527 回答
75

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

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

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

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

禁用 Window、View 或 View Controller 的暗模式

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 应用程序选择特定的界面样式

于 2019-09-23T12:05:41.693 回答
43

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

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

<key>UIUserInterfaceStyle</key>
<string>Light</string>
于 2019-10-08T05:16:17.160 回答
37

Xcode 12 和 iOS 14 更新。我已经尝试了以前的选项来选择退出暗模式,而 info.plist 文件中的这句话对我不起作用:

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

现在改名为:

<key>Appearance</key>
<string>Light</string>

此设置将阻止整个应用程序中的所有暗模式。

编辑:

修正错字谢谢@sarah

于 2020-09-22T17:52:29.497 回答
31

我想我已经找到了解决方案。我最初将它从UIUserInterfaceStyle-Information Property ListUIUserInterfaceStyle-UIKit拼凑在一起,但现在发现它实际上记录在为您的 iOS 应用程序选择特定的界面样式中

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

编辑:根据多甲虫的回答,更合适的设置UIUserInterfaceStyle可能是Light.

于 2019-06-11T08:59:56.717 回答
26

如果您想退出整个应用程序,上述答案有效。如果您正在使用具有 UI 的库,并且您没有编辑 .plist 的奢侈,您也可以通过代码来完成。

如果您正在针对 iOS 13 SDK 进行编译,您可以简单地使用以下代码:

迅速:

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

对象-C:

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")
    }
}

对象-C:

if (@available(iOS 13.0, *)) {
    if ([self respondsToSelector:NSSelectorFromString(@"overrideUserInterfaceStyle")]) {
        [self setValue:@(UIUserInterfaceStyleLight) forKey:@"overrideUserInterfaceStyle"];
    }
}
于 2019-08-16T09:12:55.847 回答
17

对于整个应用程序:(在info.plist文件中):

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

列表


窗口(通常是整个应用程序):

window!.overrideUserInterfaceStyle = .light

你可以从SceneDelegate


UIViewController:

viewController.overrideUserInterfaceStyle = .light

你可以设置任何viewController,甚至在它自己的 viewController 中


界面视图:

view.overrideUserInterfaceStyle = .light

您可以设置任何view,甚至在它自己的视图内

if #available(iOS 13.0, *) { ,,, }如果您支持早期的 iO​​S 版本,您可能需要使用。


SwiftUI 视图:

.preferredColorScheme(.light) <- This Modifier

或者

.environment(\.colorScheme, .light) <- This Modifier
于 2019-09-20T19:36:25.000 回答
15

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

  1. 转到信息.plist
  2. 添加波纹管喜欢

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

Info.plist 将如下所示...

在此处输入图像描述

于 2019-11-26T02:15:03.437 回答
12

最近更新-

如果您使用的是 Xcode 10.x,则默认UIUserInterfaceStylelightiOS 13.x。在 iOS 13 设备上运行时,它将仅在 Light Mode 下运行。

无需UIUserInterfaceStyle在 Info.plist 文件中显式添加密钥,添加它会在您验证应用程序时出错,说:

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

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

于 2019-09-13T07:03:39.213 回答
11

iOS 14.3 和 Xcode 12.3 更新

在 info.plist 文件中添加Appearance作为Light

<key>Appearance</key>
<string>Light</string>
于 2020-12-21T23:35:28.157 回答
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,这非常容易。

于 2019-08-21T09:23:06.550 回答
6

除了其他回应,根据我对以下内容的理解,您只需要在针对 iOS 13 SDK(使用 XCode 11)进行编译时准备暗模式。

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

关联

于 2019-09-10T11:02:53.157 回答
6

斯威夫特 5

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

1- info.plist

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

2- 以编程方式或运行时

  @IBAction private func switchToDark(_ sender: UIButton){
        UIApplication.shared.windows.forEach { window in
            //here you can switch between the dark and light
            window.overrideUserInterfaceStyle = .dark
        }
    }
于 2020-05-05T06:26:07.870 回答
5

我的应用程序目前不支持深色模式,并使用浅色应用栏颜色。通过将以下键添加到 my ,我能够将状态栏内容强制为深色文本和图标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

颤振用户

不要忘记在 Flutter 应用栏上设置应用栏亮度属性,如下所示:

AppBar(
    backgroundColor: Colors.grey[100],
    brightness: Brightness.light, // <---------
    title: const Text('Hi there'),
),
于 2019-09-30T13:07:46.343 回答
5

在 Xcode 12 中,您可以将添加更改为“外观”。这会奏效!!

于 2020-11-05T08:08:01.740 回答
3

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

if #available(iOS 13.0, *) {
        // Always adopt a light interface style.
        overrideUserInterfaceStyle = .light
    }
于 2019-09-26T13:53:17.523 回答
3

Objective-c 版本

 if (@available(iOS 13.0, *)) {
        _window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    }
于 2020-02-21T14:33:27.860 回答
3

将此添加到info.plist

<key>UIUserInterfaceStyle</key>
    <string>Light</string>
于 2020-12-21T12:15:32.657 回答
2
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }
于 2019-12-26T10:56:42.930 回答
2

是的..您可以在 iOS 项目中添加以下设置。

在 info.plist 文件中将 UIUserInterfaceStyle 添加到 Light。

如果您的项目在 IONIC 中。您可以在配置文件中添加以下设置

<platform name="ios">
  <edit-config file="*-Info.plist" mode="merge" target="UIUserInterfaceStyle">
  <string>Light</string>
 </edit-config>
</platform>

使用这些设置,设备暗模式不会影响您的应用。

于 2021-03-18T12:31:47.327 回答
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 中添加一行即可。

于 2019-11-12T13:05:02.570 回答
1

只需在info.plist文件中添加以下密钥:

<key>UIUserInterfaceStyle</key>
    <string>Light</string>
于 2019-11-25T13:13:28.027 回答
1

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

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

这将强制应用程序仅在光照模式下运行。

于 2019-12-18T12:20:28.827 回答
1

添加overrideUserInterfaceStyle = .light ViewController.swift 文件或在 info.plist 文件中将外观更改为“light”

于 2021-08-07T01:00:31.483 回答
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
    }
}
于 2019-11-12T11:59:11.127 回答
0
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}
于 2020-04-06T21:57:12.623 回答
0

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

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

于 2020-04-10T22:50:17.357 回答
0

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

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

在 Xcode 11.3、iOS 13.3 上测试

于 2020-04-13T12:54:38.303 回答
0

您可以在您的 plist 中设置它

项目的 info.plist 文件

于 2022-03-05T19:09:32.360 回答
-8

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

用户的选择很好(咳咳,看看你的苹果,这就是你应该实现的方式)。

所以它的工作原理是它只是 UIViewController 的一个类别。当它加载时,它会用一个检查全局标志的方法替换本机 viewDidLoad 方法,以查看是否对所有内容都禁用了暗模式。

因为它是在加载 UIViewController 时触发的,所以默认情况下它应该自动启动并禁用暗模式。如果这不是您想要的,那么您需要尽早进入那里并设置标志,否则只需设置默认标志。

我还没有写任何东西来响应用户打开或关闭标志。所以这基本上是示例代码。如果我们希望用户与之交互,所有的视图控制器都需要重新加载。我不知道该怎么做,但可能发送一些通知就可以了。所以现在,这个暗模式的全局开/关只会在应用程序启动或重启时起作用。

现在,仅仅尝试关闭大型应用程序中每个 MFING viewController 中的暗模式是不够的。如果您使用的是颜色资源,您将完全被束缚。10 多年以来,我们一直将不可变对象理解为不可变。您从颜色资产目录中获得的颜色表示它们是 UIColor,但它们是动态(可变)颜色,并且会随着系统从暗模式变为亮模式而在您下方发生变化。这应该是一个特点。但是当然没有主切换来要求这些事情停止进行这种改变(据我现在所知,也许有人可以改进这一点)。

所以解决方案分为两部分:

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

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

  3. 如前所述,您还没有走出困境,另一个问题是 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;
+ (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 是我的可重用库,这只是其中的一部分,因此我从几个文件中复制并粘贴了它。如果有些东西没有编译,请告诉我。

于 2019-07-08T14:39:24.937 回答