簡體   English   中英

是否可以在 iOS 13 上選擇退出暗模式?

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

我的應用程序的很大一部分由 Web 視圖組成,以提供本機實現尚不可用的功能。 網絡團隊沒有計划為網站實施深色主題。 因此,我的應用程序在 iOS 13 上支持深色模式時看起來有點半/半。

是否可以選擇退出暗模式支持,以便我們的應用始終顯示亮模式以匹配網站主題?

首先,這是Apple與選擇退出黑暗模式相關的條目 此鏈接中的內容是為 Xcode 11 和 iOS 13 編寫的

整個應用程序通過 info.plist 文件(Xcode 12)

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

UIUserInterfaceStyle

並為其分配一個Light值。

UIUserInterfaceStyle分配的XML

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

UIUserInterfaceStyle 的 Apple 文檔


整個應用程序通過 info.plist 在構建設置(Xcode 13)

在此處輸入圖片說明


通過 window 屬性的整個應用程序窗口

您可以針對應用程序的window變量設置overrideUserInterfaceStyle 這將適用於出現在窗口中的所有視圖。 這在 iOS 13 中可用,因此對於支持以前版本的應用程序,您必須包括可用性檢查。

根據項目的創建方式,這可能位於AppDelegateSceneDelegate文件中。

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

單獨的 UIViewController 或 UIView

您可以針對UIViewController s 或UIViewoverrideUserInterfaceStyle變量設置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 視圖的個人視圖

您可以將preferredColorScheme設置為lightdark 提供的值將為演示文稿設置配色方案。

import SwiftUI

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

PreferredColorScheme 的 Apple 文檔


感謝 @Aron Nelson@Raimundas Sakalauskas@NSLeader@rmaddy通過他們的反饋改進了這個答案。

根據蘋果關於“在 iOS 上實現暗模式”的會議( https://developer.apple.com/videos/play/wwdc2019/214/從 31:13 開始),可以在任何視圖UIUserInterfaceStyleDark overrideUserInterfaceStyle設置為UIUserInterfaceStyleLightUIUserInterfaceStyleDark控制器或視圖,它將在traitCollection用於任何子視圖或視圖控制器。

正如 SeanR 已經提到的,您可以在應用程序的 plist 文件中將UIUserInterfaceStyle設置為LightDark ,以便為整個應用程序更改此設置。

如果您使用的不是 Xcode 11 或更高版本(即 iOS 13 或更高版本 SDK),您的應用程序尚未自動選擇支持暗模式。 因此,無需選擇退出黑暗模式。

如果您使用的是 Xcode 11 或更高版本,系統已自動為您的應用啟用暗模式。 根據您的喜好,有兩種方法可以禁用暗模式。 您可以完全禁用它或為任何特定窗口、視圖或視圖控制器禁用它。

為您的應用完全禁用暗模式

您可以通過在應用程序的 Info.plist 文件中包含值為LightUIUserInterfaceStyle鍵來禁用暗模式。
UIUserInterfaceStyle 為 Light
這會忽略用戶的偏好,並始終為您的應用程序應用淺色外觀。

禁用窗口、視圖或視圖控制器的暗模式

您可以通過設置相應窗口、視圖或視圖控制器的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 應用選擇特定的界面樣式

********** Xcode 11 及更高版本的最簡單方法 ***********

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

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

Xcode 12 和 iOS 14 更新。 我已經嘗試了以前的選項來選擇退出暗模式,而 info.plist 文件中的這句話對我不起作用:

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

現在它更名為:

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

此設置將阻止完整應用程序中的所有黑暗模式。

編輯:

修正了錯字,感謝@sarah

我想我已經找到了解決方案。 我最初將它從UIUserInterfaceStyle-Information Property ListUIUserInterfaceStyle-UIKit拼湊起來,但現在發現它實際上記錄在為您的 iOS 應用程序選擇特定界面樣式中

info.plist ,將UIUserInterfaceStyle用戶界面樣式)設置為1UIUserInterfaceStyle.light )。

編輯:按照dorbeetle的答案,對於較為合適的設定UIUserInterfaceStyle可能Light

如果您想退出整個應用程序,上述答案有效。 如果您正在使用具有 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"];
    }
}

您可以在 Xcode 11 的整個應用程序中關閉暗模式

  1. 去信息.plist
  2. 添加波紋管

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

Info.plist 將如下所示...

在此處輸入圖片說明

最新更新-

如果您使用的是 Xcode 10.x,則 iOS 13.x 的默認UIUserInterfaceStylelight 在 iOS 13 設備上運行時,它只能在 Light Mode 下工作。

無需在 Info.plist 文件中顯式添加UIUserInterfaceStyle鍵,添加它會在您驗證應用程序時出錯,說:

無效的 Info.plist 密鑰。 Payload/AppName.appInfo.plist 文件中的密鑰“UIUserInterfaceStyle”無效。

使用 Xcode 11.x 時,僅在 Info.plist 文件中添加UIUserInterfaceStyle鍵。

對於整個應用程序:(在info.plist文件中):

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

列表


窗口(通常是整個應用程序):

window!.overrideUserInterfaceStyle = .light

您可以從SceneDelegate獲取窗口


UIViewController:

viewController.overrideUserInterfaceStyle = .light

您可以設置任何viewController ,甚至在它自己的 viewController 內部


界面視圖:

view.overrideUserInterfaceStyle = .light

您可以設置任何view ,甚至里面的觀點它的自我

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


SwiftUI 視圖:

.preferredColorScheme(.light) <- This Modifier

或者

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

iOS 14.3 和 Xcode 12.3 更新

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

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

如果您將UIUserInterfaceStyle鍵添加到 plist 文件中,Apple 可能會拒絕此處提到的發布版本: https : self.overrideUserInterfaceStyle = .light無論如何,明確告訴每個 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,這將非常容易。

除了其他回答,根據我對以下的理解,你只需要在針對iOS 13 SDK(使用XCode 11)編譯時准備Dark mode。

系統假設與 iOS 13 或更高版本 SDK 鏈接的應用程序支持淺色和深色外觀。 在 iOS 中,您可以通過為窗口、視圖或視圖控制器分配特定的界面樣式來指定所需的特定外觀。 您還可以使用 Info.plist 鍵完全禁用對暗模式的支持。

關聯

斯威夫特 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
        }
    }

我的應用程序目前不支持暗模式並使用淺色應用程序欄顏色。 通過將以下鍵添加到我的Info.plist ,我能夠將狀態欄內容強制為深色文本和圖標:

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

在此處查找其他可能的值: https : //developer.apple.com/documentation/uikit/ustatusbarstyle

顫振用戶

不要忘記在 Flutter 應用欄上設置應用欄亮度屬性,如下所示:

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

在 Xcode 12 中,您可以將添加更改為“外觀”。 這會奏效!!

是的,您可以通過在 viewDidLoad 中添加以下代碼來跳過:

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

Objective-C 版本

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

將此添加到info.plist

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

這里有一些提示和技巧,您可以在您的應用程序中使用它們來支持或繞過暗模式。

第一個提示:覆蓋 ViewController 樣式

您可以通過覆蓋 UIViewController 的界面樣式

1: overrideUserInterfaceStyle = .dark //對於暗模式

2: overrideUserInterfaceStyle = .light //用於光照模式

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

第二個技巧:在 info.plist 中添加一個鍵

只需添加一個新密鑰

UI用戶界面樣式

在您的應用程序 info.plist 中並將其值設置為 Light 或 Dark。 這會將應用默認樣式覆蓋為您提供的值。 您不必在每個 viewController 中添加 overrideUserInterfaceStyle = .light 這一行,只需在 info.plist 中添加一行即可。

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

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

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

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

這將強制應用程序僅在輕型模式下運行。

是的..您可以在iOS項目中添加以下設置。

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

如果您的項目在 IONIC.. 您可以在配置文件中添加以下設置

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

使用這些設置,設備暗模式不會影響您的應用程序。

我會使用此解決方案,因為窗口屬性可能會在應用程序生命周期中更改。 因此需要重復分配“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
    }
}
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}

您可以這樣做:將這個新鍵 UIUserInterfaceStyle 添加到 Info.plist 並將其值設置為 Light。 並檢查警報控制器出現在燈光模式下。

UIUserInterfaceStyle Light 如果您在整個應用程序中強制采用亮/暗模式,而不管用戶的設置如何,通過將鍵 UIUserInterfaceStyle 添加到您的 Info.plist 文件並將其值設置為 Light 或 Dark。

這個問題有很多答案,而不是在info.plist使用它,你可以像這樣在AppDelegate設置它:

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

在 Xcode 11.3、iOS 13.3 上測試

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

如果您正在尋找黑暗模式教程,並且需要了解它的所有內容。 然后瀏覽此博客。 小而有趣

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

實際上,我只是編寫了一些代碼,讓您可以在代碼中全局選擇退出暗模式,而不必對應用程序中的每個視圖控制器進行 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 是我的可重用庫,所以我正在從幾個文件中復制並粘貼它,這只是其中的一部分。 如果某些內容無法編譯,請告訴我。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM