简体   繁体   English

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

[英]Is it possible to opt-out of dark mode on iOS 13?

A large part of my app consists of web views to provide functionality not yet available through native implementations.我的应用程序的很大一部分由 Web 视图组成,以提供本机实现尚不可用的功能。 The web team has no plans to implement a dark theme for the website.网络团队没有计划为网站实施深色主题。 As such, my app will look a bit half/half with Dark Mode support on iOS 13.因此,我的应用程序在 iOS 13 上支持深色模式时看起来有点半/半。

Is it possible to opt out of Dark Mode support such that our app always shows light mode to match the website theme?是否可以选择退出暗模式支持,以便我们的应用始终显示亮模式以匹配网站主题?

First, here is Apple's entry related to opting out of dark mode.首先,这是Apple与选择退出黑暗模式相关的条目 The content at this link is written for Xcode 11 & iOS 13 :此链接中的内容是为 Xcode 11 和 iOS 13 编写的

Entire app via info.plist file (Xcode 12)整个应用程序通过 info.plist 文件(Xcode 12)

Use the following key in your info.plist file:在 info.plist 文件中使用以下键:

UIUserInterfaceStyle

And assign it a value of Light .并为其分配一个Light值。

The XML for the UIUserInterfaceStyle assignment: UIUserInterfaceStyle分配的XML

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

Apple documentation for UIUserInterfaceStyle UIUserInterfaceStyle 的 Apple 文档


Entire app via info.plist in build settings (Xcode 13)整个应用程序通过 info.plist 在构建设置(Xcode 13)

在此处输入图片说明


Entire app window via window property通过 window 属性的整个应用程序窗口

You can set overrideUserInterfaceStyle against the app's window variable.您可以针对应用程序的window变量设置overrideUserInterfaceStyle This will apply to all views that appear within the window.这将适用于出现在窗口中的所有视图。 This became available with iOS 13, so for apps that support previous versions, you must include an availability check.这在 iOS 13 中可用,因此对于支持以前版本的应用程序,您必须包括可用性检查。

Depending on how your project was created, this may be in the AppDelegate or SceneDelegate file.根据项目的创建方式,这可能位于AppDelegateSceneDelegate文件中。

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

Individual UIViewController or UIView单独的 UIViewController 或 UIView

You can set overrideUserInterfaceStyle against the UIViewController s or UIView 's overrideUserInterfaceStyle variable.您可以针对UIViewController s 或UIViewoverrideUserInterfaceStyle变量设置overrideUserInterfaceStyle This became available with iOS 13, so for apps that support previous versions, you must include an availability check.这在 iOS 13 中可用,因此对于支持以前版本的应用程序,您必须包括可用性检查。

Swift迅速

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

For those poor souls in Objective-C对于Objective-C中的那些可怜的灵魂

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

When set against the UIViewController , the view controller and its children adopt the defined mode.当针对UIViewController设置时,视图控制器及其子级采用定义的模式。

When set against the UIView , the view and its children adopt the defined mode.当针对UIView设置时,视图及其子视图采用定义的模式。

Apple documentation for overrideUserInterfaceStyle 关于 overrideUserInterfaceStyle 的 Apple 文档


Individual views via SwiftUI View通过 SwiftUI 视图的个人视图

You can set preferredColorScheme to be either light or dark .您可以将preferredColorScheme设置为lightdark The provided value will set the color scheme for the presentation.提供的值将为演示文稿设置配色方案。

import SwiftUI

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

Apple documentation for preferredColorScheme PreferredColorScheme 的 Apple 文档


Credit to @Aron Nelson , @Raimundas Sakalauskas , @NSLeader and @rmaddy for improving this answer with their feedback.感谢 @Aron Nelson@Raimundas Sakalauskas@NSLeader@rmaddy通过他们的反馈改进了这个答案。

According to Apple's session on "Implementing Dark Mode on iOS" ( https://developer.apple.com/videos/play/wwdc2019/214/ starting at 31:13) it is possible to set overrideUserInterfaceStyle to UIUserInterfaceStyleLight or UIUserInterfaceStyleDark on any view controller or view, which will the be used in the traitCollection for any subview or view controller.根据苹果关于“在 iOS 上实现暗模式”的会议( https://developer.apple.com/videos/play/wwdc2019/214/从 31:13 开始),可以在任何视图UIUserInterfaceStyleDark overrideUserInterfaceStyle设置为UIUserInterfaceStyleLightUIUserInterfaceStyleDark控制器或视图,它将在traitCollection用于任何子视图或视图控制器。

As already mentioned by SeanR, you can set UIUserInterfaceStyle to Light or Dark in your app's plist file to change this for your whole app.正如 SeanR 已经提到的,您可以在应用程序的 plist 文件中将UIUserInterfaceStyle设置为LightDark ,以便为整个应用程序更改此设置。

If you are not using Xcode 11 or later (i,e iOS 13 or later SDK), your app has not automatically opted to support dark mode.如果您使用的不是 Xcode 11 或更高版本(即 iOS 13 或更高版本 SDK),您的应用程序尚未自动选择支持暗模式。 So, there's no need to opt out of dark mode.因此,无需选择退出黑暗模式。

If you are using Xcode 11 or later, the system has automatically enabled dark mode for your app.如果您使用的是 Xcode 11 或更高版本,系统已自动为您的应用启用暗模式。 There are two approaches to disable dark mode depending on your preference.根据您的喜好,有两种方法可以禁用暗模式。 You can disable it entirely or disable it for any specific window, view, or view controller.您可以完全禁用它或为任何特定窗口、视图或视图控制器禁用它。

Disable Dark Mode Entirely for your App为您的应用完全禁用暗模式

You can disable dark mode by including the UIUserInterfaceStyle key with a value as Light in your app's Info.plist file.您可以通过在应用程序的 Info.plist 文件中包含值为LightUIUserInterfaceStyle键来禁用暗模式。
UIUserInterfaceStyle 为 Light
This ignores the user's preference and always applies a light appearance to your app.这会忽略用户的偏好,并始终为您的应用程序应用浅色外观。

Disable dark mode for Window, View, or View Controller禁用窗口、视图或视图控制器的暗模式

You can force your interface to always appear in a light or dark style by setting the overrideUserInterfaceStyle property of the appropriate window, view, or view controller.您可以通过设置相应窗口、视图或视图控制器的overrideUserInterfaceStyle属性来强制您的界面始终以浅色或深色样式显示。

View controllers:视图控制器:

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

Views:意见:

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

Window:窗户:

/* 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

Note: Apple strongly encourages to support dark mode in your app.注意:Apple 强烈建议在您的应用中支持暗模式。 So, you can only disable dark mode temporarily.因此,您只能暂时禁用暗模式。

Read more here: Choosing a Specific Interface Style for Your iOS App在此处阅读更多信息: 为您的 iOS 应用选择特定的界面样式

********** Easiest way for Xcode 11 and above *********** ********** Xcode 11 及更高版本的最简单方法 ***********

Add this to info.plist before </dict></plist>将此添加到 info.plist 之前</dict></plist>

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

Xcode 12 and iOS 14 update. Xcode 12 和 iOS 14 更新。 I have try the previous options to opt-out dark mode and this sentence in the info.plist file is not working for me:我已经尝试了以前的选项来选择退出暗模式,而 info.plist 文件中的这句话对我不起作用:

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

Now it is renamed to:现在它更名为:

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

This setting will block all dark mode in the full app.此设置将阻止完整应用程序中的所有黑暗模式。

EDITED:编辑:

Fixed typo thank you to @sarah修正了错字,感谢@sarah

I think I've found the solution.我想我已经找到了解决方案。 I initially pieced it together from UIUserInterfaceStyle - Information Property List and UIUserInterfaceStyle - UIKit , but have now found it actually documented at Choosing a specific interface style for your iOS app .我最初将它从UIUserInterfaceStyle-Information Property ListUIUserInterfaceStyle-UIKit拼凑起来,但现在发现它实际上记录在为您的 iOS 应用程序选择特定界面样式中

In your info.plist , set UIUserInterfaceStyle ( User Interface Style ) to 1 ( UIUserInterfaceStyle.light ).info.plist ,将UIUserInterfaceStyle用户界面样式)设置为1UIUserInterfaceStyle.light )。

EDIT: As per dorbeetle's answer, a more appropriate setting for UIUserInterfaceStyle may be Light .编辑:按照dorbeetle的答案,对于较为合适的设定UIUserInterfaceStyle可能Light

The answer above works if you want to opt out the whole app.如果您想退出整个应用程序,上述答案有效。 If you are working on the lib that has UI, and you don't have luxury of editing .plist, you can do it via code too.如果您正在使用具有 UI 的库,并且您无法编辑 .plist,那么您也可以通过代码来完成。

If you are compiling against iOS 13 SDK, you can simply use following code:如果您是针对 iOS 13 SDK 进行编译,则只需使用以下代码:

Swift:迅速:

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

Obj-C:对象-C:

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

HOWEVER , if you want your code to compile against iOS 12 SDK too (which right now is still the latest stable SDK), you should resort to using selectors.但是,如果您也希望您的代码也针对iOS 12 SDK进行编译(它现在仍然是最新的稳定 SDK),您应该求助于使用选择器。 Code with selectors:带有选择器的代码:

Swift (XCode will show warnings for this code, but that's the only way to do it for now as property does not exist in SDK 12 therefore won't compile): Swift(XCode 将显示此代码的警告,但这是目前唯一的方法,因为 SDK 12 中不存在该属性,因此无法编译):

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

Obj-C:对象-C:

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

You can turn Dark Mode off in entire application in Xcode 11:您可以在 Xcode 11 的整个应用程序中关闭暗模式

  1. Go Info.plist去信息.plist
  2. Add bellow like添加波纹管

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

Info.plist will be look like below... Info.plist 将如下所示...

在此处输入图片说明

Latest Update-最新更新-

If you're using Xcode 10.x, then the default UIUserInterfaceStyle is light for iOS 13.x.如果您使用的是 Xcode 10.x,则 iOS 13.x 的默认UIUserInterfaceStylelight When run on an iOS 13 device, it will work in Light Mode only.在 iOS 13 设备上运行时,它只能在 Light Mode 下工作。

No need to explicitly add the UIUserInterfaceStyle key in Info.plist file, adding it will give an error when you Validate your app, saying:无需在 Info.plist 文件中显式添加UIUserInterfaceStyle键,添加它会在您验证应用程序时出错,说:

Invalid Info.plist Key.无效的 Info.plist 密钥。 The key 'UIUserInterfaceStyle' in the Payload/AppName.appInfo.plist file is not valid. Payload/AppName.appInfo.plist 文件中的密钥“UIUserInterfaceStyle”无效。

Only add the UIUserInterfaceStyle key in Info.plist file when using Xcode 11.x.使用 Xcode 11.x 时,仅在 Info.plist 文件中添加UIUserInterfaceStyle键。

For the entire App: (in the info.plist file):对于整个应用程序:(在info.plist文件中):

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

列表


Window (Usually the whole app):窗口(通常是整个应用程序):

window!.overrideUserInterfaceStyle = .light

You can get the window from SceneDelegate您可以从SceneDelegate获取窗口


UIViewController: UIViewController:

viewController.overrideUserInterfaceStyle = .light

You can set any viewController , even inside the viewController it self您可以设置任何viewController ,甚至在它自己的 viewController 内部


UIView:界面视图:

view.overrideUserInterfaceStyle = .light

You can set any view , even inside the view it self您可以设置任何view ,甚至里面的观点它的自我

You may need to use if #available(iOS 13.0, *) { ,,, } if you are supporting earlier iOS versions.如果您支持较早的 iOS 版本if #available(iOS 13.0, *) { ,,, }可能需要使用if #available(iOS 13.0, *) { ,,, }


SwiftUI View: SwiftUI 视图:

.preferredColorScheme(.light) <- This Modifier

or或者

.environment(\.colorScheme, .light) <- This Modifier

iOS 14.3 and Xcode 12.3 Update iOS 14.3 和 Xcode 12.3 更新

In info.plist file add Appearance as Light .在 info.plist 文件中添加Appearance as Light

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

If you will add UIUserInterfaceStyle key to the plist file, possibly Apple will reject release build as mentioned here: https://stackoverflow.com/a/56546554/7524146 Anyway it's annoying to explicitly tell each ViewController self.overrideUserInterfaceStyle = .light .如果您将UIUserInterfaceStyle键添加到 plist 文件中,Apple 可能会拒绝此处提到的发布版本: https : self.overrideUserInterfaceStyle = .light无论如何,明确告诉每个 ViewController self.overrideUserInterfaceStyle = .light很烦人。 But you can use this peace of code once for your root window object:但是你可以为你的根window对象使用一次这种代码的和平:

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

Just notice you can't do this inside application(application: didFinishLaunchingWithOptions:) because for this selector will not respond true at that early stage.请注意,您不能在application(application: didFinishLaunchingWithOptions:)执行此操作,因为此选择器在早期阶段不会响应true But you can do it later on.但你可以稍后再做。 It's super easy if you are using custom AppPresenter or AppRouter class in your app instead of starting UI in the AppDelegate automatically.如果您在应用程序中使用自定义AppPresenterAppRouter类而不是自动在 AppDelegate 中启动 UI,这将非常容易。

Apart from other responses, from my understanding of the following, you only need to prepare for Dark mode when compiling against iOS 13 SDK (using XCode 11).除了其他回答,根据我对以下的理解,你只需要在针对iOS 13 SDK(使用XCode 11)编译时准备Dark mode。

The system assumes that apps linked against the iOS 13 or later SDK support both light and dark appearances.系统假设与 iOS 13 或更高版本 SDK 链接的应用程序支持浅色和深色外观。 In iOS, you specify the specific appearance you want by assigning a specific interface style to your window, view, or view controller.在 iOS 中,您可以通过为窗口、视图或视图控制器分配特定的界面样式来指定所需的特定外观。 You can also disable support for Dark Mode entirely using an Info.plist key.您还可以使用 Info.plist 键完全禁用对暗模式的支持。

Link 关联

Swift 5斯威夫特 5

Two ways to switch dark to light mode:从暗模式切换到亮模式的两种方法:

1- info.plist 1- info.plist

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

2- Programmatically or Runtime 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
        }
    }

My app does not support dark mode as of now and uses a light app bar color.我的应用程序目前不支持暗模式并使用浅色应用程序栏颜色。 I was able to force the status bar content to dark text and icons by adding the following key to my Info.plist :通过将以下键添加到我的Info.plist ,我能够将状态栏内容强制为深色文本和图标:

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

Find the other possible values here: https://developer.apple.com/documentation/uikit/uistatusbarstyle在此处查找其他可能的值: https : //developer.apple.com/documentation/uikit/ustatusbarstyle

Flutter Users颤振用户

Don't forget to set the app bar brightness attribute on your Flutter app bar like this:不要忘记在 Flutter 应用栏上设置应用栏亮度属性,如下所示:

AppBar(
    backgroundColor: Colors.grey[100],
    brightness: Brightness.light, // <---------
    title: const Text('Hi there'),
),

In Xcode 12, you can change add as "appearances".在 Xcode 12 中,您可以将添加更改为“外观”。 This will work!!这会奏效!!

Yes you can skip by adding the following code in viewDidLoad:是的,您可以通过在 viewDidLoad 中添加以下代码来跳过:

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

Objective-c version Objective-C 版本

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

Add this to info.plist将此添加到info.plist

<key>UIUserInterfaceStyle</key>
    <string>Light</string>
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }

Here are a few tips and tricks which you can use in your app to support or bypass the dark mode.这里有一些提示和技巧,您可以在您的应用程序中使用它们来支持或绕过暗模式。

First tip: To override the ViewController style第一个提示:覆盖 ViewController 样式

you can override the interface style of UIViewController by您可以通过覆盖 UIViewController 的界面样式

1: overrideUserInterfaceStyle = .dark //For dark mode 1: overrideUserInterfaceStyle = .dark //对于暗模式

2: overrideUserInterfaceStyle = .light //For light mode 2: overrideUserInterfaceStyle = .light //用于光照模式

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

Second tip: Adding a key in info.plist第二个技巧:在 info.plist 中添加一个键

Simply you can add a new key只需添加一个新密钥

UIUserInterfaceStyle UI用户界面样式

in your app info.plist and set its value to Light or Dark.在您的应用程序 info.plist 中并将其值设置为 Light 或 Dark。 this will override the app default style to the value you provide.这会将应用默认样式覆盖为您提供的值。 You don't have to add overrideUserInterfaceStyle = .light this line in every viewController, just one line in info.plist that's it.您不必在每个 viewController 中添加 overrideUserInterfaceStyle = .light 这一行,只需在 info.plist 中添加一行即可。

Just simply add following key in your info.plist file :只需在info.plist文件中添加以下键:

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

Just add these line in info.plist file:只需在 info.plist 文件中添加这些行:

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

This will force app to run in light mode only.这将强制应用程序仅在轻型模式下运行。

Yes.. you can add below setting in iOS project.是的..您可以在iOS项目中添加以下设置。

In info.plist file add UIUserInterfaceStyle to Light.在 info.plist 文件中,将 UIUserInterfaceStyle 添加到 Light。

If your project is in IONIC.. You can add below setting in config file如果您的项目在 IONIC.. 您可以在配置文件中添加以下设置

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

Using these settings, device dark mode will not affect your app.使用这些设置,设备暗模式不会影响您的应用程序。

I would use this solution since window property may be changed during the app life cycle.我会使用此解决方案,因为窗口属性可能会在应用程序生命周期中更改。 So assigning "overrideUserInterfaceStyle = .light" needs to be repeated.因此需要重复分配“overrideUserInterfaceStyle = .light”。 UIWindow.appearance() enables us to set default value that will be used for newly created UIWindow objects. 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
    }
}
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}

You can do: add this new key UIUserInterfaceStyle to Info.plist and set its value to Light.您可以这样做:将这个新键 UIUserInterfaceStyle 添加到 Info.plist 并将其值设置为 Light。 and check alert controller appears with light mode.并检查警报控制器出现在灯光模式下。

UIUserInterfaceStyle Light If you are force light/dark mode in your whole application regardless of the user's settings by adding the key UIUserInterfaceStyle to your Info.plist file and setting its value to either Light or Dark. UIUserInterfaceStyle Light 如果您在整个应用程序中强制采用亮/暗模式,而不管用户的设置如何,通过将键 UIUserInterfaceStyle 添加到您的 Info.plist 文件并将其值设置为 Light 或 Dark。

This question has so many answers, rather using it in info.plist you can set it in AppDelegate like this:这个问题有很多答案,而不是在info.plist使用它,你可以像这样在AppDelegate设置它:

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

Test on Xcode 11.3, iOS 13.3在 Xcode 11.3、iOS 13.3 上测试

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

If you are looking for Dark mode tutorial and need to know everything about it. 如果您正在寻找黑暗模式教程,并且需要了解它的所有内容。 Then go through this blog. 然后浏览此博客。 It is small and interesting 小而有趣

https://www.yudiz.com/adapting-dark-mode-on-your-project-swift-5/ https://www.yudiz.com/adapting-dark-mode-on-your-project-swift-5/

Actually I just wrote some code that will allow you to globally opt out of dark mode in code without having to putz with every single viw controller in your application.实际上,我只是编写了一些代码,让您可以在代码中全局选择退出暗模式,而不必对应用程序中的每个视图控制器进行 putz。 This can probably be refined to opt out on a class by class basis by managing a list of classes.这可能可以通过管理类列表来改进,以逐个类地选择退出。 For me, what I want is for my users to see if they like the dark mode interface for my app, and if they don't like it, they can turn it off.对我来说,我想要的是让我的用户看到他们是否喜欢我的应用程序的暗模式界面,如果他们不喜欢它,他们可以将其关闭。 This will allow them to continue using dark mode for the rest of their applications.这将允许他们继续在其余应用程序中使用暗模式。

User choice is good (Ahem, looking at you Apple, this is how you should have implemented it).用户选择是好的(咳咳,看着你的苹果,这就是你应该如何实现它的)。

So how this works is that it's just a category of UIViewController.所以这是如何工作的,它只是 UIViewController 的一个类别。 When it loads it replaces the native viewDidLoad method with one that will check a global flag to see if dark mode is disabled for everything or not.当它加载时,它将本地 viewDidLoad 方法替换为一个将检查全局标志以查看是否对所有内容禁用暗模式的方法。

Because it is triggered on UIViewController loading it should automatically start up and disable dark mode by default.因为它是在 UIViewController 加载时触发的,所以它应该自动启动并默认禁用暗模式。 If this is not what you want, then you need to get in there somewhere early and set the flag, or else just set the default flag.如果这不是您想要的,那么您需要尽早进入并设置标志,否则只需设置默认标志。

I haven't yet written anything to respond to the user turning the flag on or off.我还没有写任何东西来回应用户打开或关闭标志。 So this is basically example code.所以这基本上是示例代码。 If we want the user to interact with this, all the view controllers will need to reload.如果我们希望用户与之交互,则需要重新加载所有视图控制器。 I don't know how to do that offhand but probably sending some notification is going to do the trick.我不知道怎么做,但可能发送一些通知就可以了。 So right now, this global on/off for dark mode is only going to work at startup or restart of the app.所以现在,这个黑暗模式的全局开/关只会在应用程序启动或重新启动时起作用。

Now, it's not just enough to try to turn off dark mode in every single MFING viewController in your huge app.现在,仅仅尝试在大型应用程序中的每个 MFING viewController 中关闭暗模式是不够的。 If you're using color assets you are completely boned.如果您使用的是颜色资产,那么您就完全没有问题了。 We for 10+ years have understood immutable objects to be immutable. 10 多年来,我们已经将不可变对象理解为不可变。 Colors you get from the color asset catalog say they are UIColor but they are dynamic (mutable) colors and will change underneath you as the system changes from dark to light mode.您从颜色资产目录中获得的颜色表示它们是 UIColor,但它们是动态(可变)颜色,并且会随着系统从暗模式变为亮模式而改变。 That is supposed to be a feature.这应该是一个功能。 But of course there is no master toggle to ask these things to stop making this change (as far as I know right now, maybe someone can improve this).但是当然没有主切换来要求这些东西停止进行这种更改(据我现在所知,也许有人可以改进这一点)。

So the solution is in two parts:所以解决方案分为两部分:

  1. a public category on UIViewController that gives some utility and convenience methods... for instance I don't think apple has thought about the fact that some of us mix in web code into our apps. UIViewController 上的一个公共类别,它提供了一些实用和方便的方法……例如,我认为苹果没有考虑过我们中的一些人将 Web 代码混合到我们的应用程序中的事实。 As such we have stylesheets that need to be toggled based on dark or light mode.因此,我们需要根据深色或浅色模式切换样式表。 Thus, you either need to build some kind of a dynamic stylesheet object (which would be good) or just ask what the current state is (bad but easy).因此,您要么需要构建某种动态样式表对象(这会很好),要么只是询问当前状态是什么(不好但很容易)。

  2. this category when it loads will replace the viewDidLoad method of the UIViewController class and intercept calls.这个类在加载时会替换 UIViewController 类的 viewDidLoad 方法并拦截调用。 I don't know if that breaks app store rules.我不知道这是否违反了应用商店规则。 If it does, there are other ways around that probably but you can consider it a proof of concept.如果是这样,可能还有其他方法可以解决,但您可以将其视为概念证明。 You can for instance make one subclass of all the main view controller types and make all of your own view controllers inherit from those, and then you can use the DarkMode category idea and call into it to force opt out all of your view controllers.例如,您可以创建所有主要视图控制器类型的一个子类,并使您自己的所有视图控制器都继承自那些,然后您可以使用 DarkMode 类别的想法并调用它来强制选择退出所有视图控制器。 It is uglier but it is not going to break any rules.它更丑,但它不会违反任何规则。 I prefer using the runtime because that's what the runtime was made to do.我更喜欢使用运行时,因为这是运行时要做的。 So in my version you just add the category, you set a global variable on the category for whether or not you want it to block dark mode, and it will do it.因此,在我的版本中,您只需添加类别,在类别上设置一个全局变量,以决定您是否希望它阻止黑暗模式,它会这样做。

  3. You are not out of the woods yet, as mentioned, the other problem is UIColor basically doing whatever the hell it wants.如前所述,您还没有摆脱困境,另一个问题是 UIColor 基本上可以随心所欲。 So even if your view controllers are blocking dark mode UIColor doesn't know where or how you're using it so can't adapt.因此,即使您的视图控制器阻止了暗模式 UIColor 也不知道您在哪里或如何使用它,因此无法适应。 As a result you can fetch it correctly but then it's going to revert on you at some point in the future.因此,您可以正确获取它,但它会在将来的某个时候恢复到您身上。 Maybe soon maybe later.也许很快也许以后。 So the way around that is by allocating it twice using a CGColor and turning it into a static color.因此,解决方法是使用 CGColor 将其分配两次并将其转换为静态颜色。 This means if your user goes back and re-enables dark mode on your settings page (the idea here is to make this work so that the user has control over your app over and above the rest of the system), all of those static colors need replacing.这意味着如果您的用户返回并在您的设置页面上重新启用暗模式(这里的想法是使这项工作有效,以便用户可以在系统其余部分之上控制您的应用程序),所有这些静态颜色需要更换。 So far this is left for someone else to solve.到目前为止,这是留给其他人解决的。 The easy ass way to do it is to make a default that you're opting out of dark mode, divide by zero to crash the app since you can't exit it and tell the user to just restart it.最简单的方法是默认您选择退出暗模式,除以零使应用程序崩溃,因为您无法退出它并告诉用户只需重新启动它。 That probably violates app store guidelines as well but it's an idea.这可能也违反了应用商店指南,但这是一个想法。

The UIColor category doesn't need to be exposed, it just works calling colorNamed: ... if you didn't tell the DarkMode ViewController class to block dark mode, it will work perfectly nicely as expected. UIColor 类别不需要公开,它只是调用 colorNamed: ... 如果你没有告诉 DarkMode ViewController 类阻止黑暗模式,它会像预期的那样完美地工作。 Trying to make something elegant instead of the standard apple sphaghetti code which is going to mean you're going to have to modify most of your app if you want to programatically opt out of dark mode or toggle it.尝试制作一些优雅的东西而不是标准的苹果意大利面代码,这意味着如果您想以编程方式选择退出暗模式或切换它,您将不得不修改大部分应用程序。 Now I don't know if there is a better way of programatically altering the Info.plist to turn off dark mode as needed.现在我不知道是否有更好的方法以编程方式更改 Info.plist 以根据需要关闭暗模式。 As far as my understanding goes that's a compile time feature and after that you're boned.就我的理解而言,这是一个编译时功能,之后你就被束缚住了。

So here is the code you need.所以这是你需要的代码。 Should be drop in and just use the one method to set the UI Style or set the default in the code.应该直接使用一种方法来设置 UI 样式或在代码中设置默认值。 You are free to use, modify, do whatever you want with this for any purpose and no warranty is given and I don't know if it will pass the app store.您可以出于任何目的自由使用,修改,做任何您想做的事情,并且不提供任何保证,我不知道它是否会通过应用商店。 Improvements very welcome.非常欢迎改进。

Fair warning I don't use ARC or any other handholding methods.公平警告我不使用 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

There is a set of utility functions that this uses for doing method swapping.有一组实用函数可用于进行方法交换。 Separate file.单独的文件。 This is standard stuff though and you can find similar code anywhere.不过这是标准的东西,你可以在任何地方找到类似的代码。

// 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;
    }
}

I'm copying and pasting this out of a couple of files since the q-runtime.h is my reusable library and this is just a part of it.因为 q-runtime.h 是我的可重用库,所以我正在从几个文件中复制并粘贴它,这只是其中的一部分。 If something doesn't compile let me know.如果某些内容无法编译,请告诉我。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM