Можно ли отказаться от темного режима на iOS 13?

297

Большая часть моего приложения состоит из веб-представлений, обеспечивающих функциональность, пока недоступную в собственных реализациях. У веб-команды нет планов по реализации темной темы для сайта. Таким образом, мое приложение будет выглядеть наполовину с поддержкой Dark Mode на iOS 13.

Можно ли отказаться от поддержки Dark Mode, чтобы наше приложение всегда отображало светлый режим в соответствии с темой сайта?

SeanR
источник
71
Набор UIUserInterfaceStyleдля Lightвашей info.plist. См developer.apple.com/library/archive/documentation/General/...
Tieme
1
Спасибо за вопрос - для всех нас. Много приложений, чтобы пройти. Это необходимо для того, чтобы приложения работали до тех пор, пока переключатель не будет готов.
user3741598
import Foundation import UIKit extension UIViewController {переопределить открытый func awakeFromNib () {super.awakeFromNib (), если #available (iOS 13.0, *) {// Всегда использовать легкий интерфейсный стиль. overrideUserInterfaceStyle = .light}}}
Мохаммед Разипур
1
просто добавьте UIUserInterfaceStyle в plist . это так просто
Толстяк
При отправке приложения в appstore сделайте Apple принять из-за UIUserInterfaceStyle в режиме Light.
Киран

Ответы:

685

Во-первых, вот запись Apple, связанная с отказом от темного режима. Содержание по этой ссылке написано для Xcode 11 и iOS 13 :

Этот раздел относится к использованию Xcode 11


Если вы хотите отказаться от ВСЕХ заявлений

Подход № 1

Используйте следующий ключ в вашем файле info.plist :

UIUserInterfaceStyle

И присвойте ему значение Light.

XML для UIUserInterfaceStyleзадания:

<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, вы будете получать ошибки при попытке использовать значения, указанные выше:

Для настройки overrideUserInterfaceStyleв вашемUIViewController

введите описание изображения здесь

Если вы хотите отказаться от вашего UIViewController на индивидуальной основе

Это может быть обработано в Xcode 10, проверяя версию компилятора и версию iOS:

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

Если вы хотите отказаться от ВСЕХ заявлений

Вы можете изменить вышеупомянутый фрагмент, чтобы работать против всего приложения для Xcode 10, добавив следующий код в свой AppDelegateфайл.

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

Однако настройка plist не удастся при использовании Xcode версии 10.x:

введите описание изображения здесь

Кредит @Aron Нельсон , @Raimundas Сакалаускасом , @NSLeader и rmaddy для улучшения этого ответа с их обратной связи.

CodeBender
источник
2
UIUserInterfaceStyle light теперь блокируется при обновлении / загрузке приложения. Он помечается как недопустимая запись в списке. (Неверный ключ
Арон Нельсон
2
Это не скомпилируется с iOS SDK 12 (в настоящее время последний стабильный SDK). См. Stackoverflow.com/a/57521901/2249485 для решения, которое будет работать с iOS 12 SDK тоже.
Раймундас Сакалаускас
Это настолько несправедливо, что вопрос, который имеет гораздо больше взглядов, чем «оригинальный вопрос», заблокирован для предоставления ответов. :(
Раймундас Сакалаускас
7
Вместо того чтобы устанавливать overrideUserInterfaceStyleв viewDidLoadкаждой контроллере представления, вы можете установить его один раз в главном окне приложения. Намного проще, если вы хотите, чтобы все приложение работало в одном направлении.
rmaddy
2
Используйте #if compiler(>=5.1)вместо этого responds(to:)иsetValue
NSLeader
162

Согласно сессии Apple, на «Dark Mode исполнителя на прошивке» ( https://developer.apple.com/videos/play/wwdc2019/214/ начиная с 31:13) , то можно установить overrideUserInterfaceStyleв UIUserInterfaceStyleLightили UIUserInterfaceStyleDarkна любом контроллере представления или представление , который будет использоваться в traitCollectionлюбом контроллере подпредставления или представления.

Как уже упомянуто SeanR, вы можете установить UIUserInterfaceStyleна Lightили Darkв Plist файла вашего приложения , чтобы изменить это для всего приложения.

dorbeetle
источник
17
Если вы установите ключ UIUserInterfaceStyle, ваше приложение будет отклонено в магазине приложений
Sonius,
2
Apple отказалась с кодом ошибки ITMS-90190 forums.developer.apple.com/thread/121028
PRASAD1240
11
Отказ, скорее всего, произойдет, потому что iOS 13 SDK еще не вышел из бета-версии. Я думаю, что это должно работать, как только появится Xcode 11 GM.
Дорбитл
2
@dorbeetle это не правда, я успешно загрузил свое приложение с этим ключом, как 1 месяц назад с Xcode 10. Отказы происходят недавно. Кажется, некоторые виды новой стратегии Apple.
Стивен
1
Это все еще происходит. Xcode GM2 вернул ошибку подписи приложения. Xcode 10.3 вернул: «Неверный ключ Info.plist. Ключ« UIUserInterfaceStyle »в файле Payload / Galileo.appInfo.plist недействителен».
Бодунов
64

Если вы не используете Xcode 11 или новее (например, iOS 13 или новее SDK), ваше приложение автоматически не поддерживает темный режим. Таким образом, нет необходимости отказываться от темного режима.

Если вы используете Xcode 11 или новее, система автоматически включила темный режим для вашего приложения. Есть два подхода для отключения темного режима в зависимости от ваших предпочтений. Вы можете отключить его полностью или отключить для любого конкретного окна, вида или контроллера просмотра.

Отключить темный режим полностью для вашего приложения

Вы можете отключить темный режим, включив UIUserInterfaceStyleключ со значением, как Lightв файле Info.plist вашего приложения.
UIUserInterfaceStyle as 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

Аджит Р Наяк
источник
34

********** Самый простой способ для Xcode 11 и выше ***********

Добавьте это в info.plist перед </dict></plist>

<key>UIUserInterfaceStyle</key>
<string>Light</string>
Кингсли Митчелл
источник
это решение не сработает, когда
отправка
27

Я думаю, что нашел решение. Сначала я собрал его воедино из UIUserInterfaceStyle - список свойств информации и UIUserInterfaceStyle - UIKit , но теперь обнаружил, что это действительно задокументировано в разделе « Выбор определенного стиля интерфейса для приложения iOS» .

В вашем случае info.plistустановите UIUserInterfaceStyle( Стиль интерфейса пользователя ) на 1 ( UIUserInterfaceStyle.light).

РЕДАКТИРОВАТЬ: Согласно ответу Дорбитл, более подходящим параметром для UIUserInterfaceStyleможет быть Light.

SeanR
источник
Принудительное включение темного режима путем установки значения 2 не работает, хотя:[UIInterfaceStyle] '2' is not a recognized value for UIUserInterfaceStyle. Defaulting to Light.
funkenstrahlen
3
Наличие этого ключа в списке приведет к отклонению в App Store.
Хосе
1
AppStore больше не отклоняет это свойство в plist.info. Я поставил "Dark" (с большой буквы), так как наше приложение уже темно. Без проблем. Это правильно позволяет нам использовать системные элементы управления.
nickdnk
@nickdnk Я думаю, что вы создали свое приложение с Xcode 11, что рекомендовано Apple.
DawnSong
1
Да, я сделал. Это не меняет того факта, что Apple принимает этот параметр в списке, что я пытался прояснить.
nickdnk
23

Ответ выше работает, если вы хотите отказаться от всего приложения. Если вы работаете с библиотекой, в которой есть пользовательский интерфейс, и у вас нет возможности редактировать .plist, вы можете сделать это и с помощью кода.

Если вы компилируете с iOS 13 SDK, вы можете просто использовать следующий код:

Swift:

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

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

Obj-C:

if (@available(iOS 13.0, *)) {
    if ([self respondsToSelector:NSSelectorFromString(@"overrideUserInterfaceStyle")]) {
        [self setValue:@(UIUserInterfaceStyleLight) forKey:@"overrideUserInterfaceStyle"];
    }
}
Раймундас Сакалаускас
источник
Будет лучше, если вы укажете, к какой собственности overrideUserInterfaceStyleотносится.
DawnSong
12

Последнее обновление-

Если вы используете Xcode 10.x, то по умолчанию UIUserInterfaceStyleиспользуется lightiOS 13.x. При запуске на устройстве iOS 13 оно будет работать только в легком режиме.

Нет необходимости явно добавлять UIUserInterfaceStyleключ в файл Info.plist, добавление которого приведет к ошибке при проверке приложения:

Неверный ключ Info.plist. Ключ 'UIUserInterfaceStyle' в файле Payload / AppName.appInfo.plist недопустим.

Добавить только UIUserInterfaceStyleключ в файле Info.plist при использовании Xcode 11.x.

kumarsiddharth123
источник
1
Это не имеет ничего общего с Xcode 10 или 11. Если пользователь развертывает приложение из Xcode 10 и не заботится о темном режиме, то при установке приложения на iPhone 11, Pro или Pro Max у него будут проблемы с темным режимом. вам нужно обновить до Xcode 11 и решить эту проблему.
Ниранджан Молкери
3
@NiranjanMolkeri Это не имеет ничего общего с более новыми iPhone. Речь идет о темном режиме на iOS 13. В предыдущей версии бета-версии iOS 13 у приложений возникали проблемы с темным режимом, если они явно не обрабатывались. Но в последней версии это исправлено. Если вы используете XCode 10, то по умолчанию UIUserInterfaceStyle светлый для iOS13. Если вы используете Xode11, вам нужно справиться с этим.
kumarsiddharth123
У вас будут проблемы, если вы загрузите приложение в TestFligth, используя Xcode 10.3, и 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на этом раннем этапе. Но вы можете сделать это позже. Это очень просто, если вы используете пользовательский AppPresenterили AppRouterкласс в своем приложении вместо автоматического запуска пользовательского интерфейса в AppDelegate.

SerhiiK
источник
9

Вы можете включить Dark Mode во всем приложении в Xcode 11:

  1. Перейти Info.plist
  2. Добавить ниже как

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

Info.plist будет выглядеть ниже ...

введите описание изображения здесь

Энамул Хак
источник
1
почему-то не работает для Xcode версии 11.3.1 (11C504)
Эндрю
7

- Для всего приложения (окна):

window!.overrideUserInterfaceStyle = .light

Вы можете получить окно от SceneDelegate

- Для одного ViewController:

viewController.overrideUserInterfaceStyle = .light

Вы можете установить любой viewController, даже внутри viewController это сам

- Для одного просмотра:

view.overrideUserInterfaceStyle = .light

Вы можете установить любой view, даже внутри просмотра себя

Возможно, вам придется использовать, if #available(iOS 13.0, *) { ,,, }если вы поддерживаете более ранние версии iOS.

Мойтаба Хоссейни
источник
6

Помимо других ответов, исходя из моего понимания следующего, вам нужно только подготовиться к режиму Dark при компиляции с iOS 13 SDK (с использованием XCode 11).

Система предполагает, что приложения, связанные с iOS 13 или более поздним SDK, поддерживают как светлые, так и темные изображения. В iOS вы указываете желаемый внешний вид, назначая определенный стиль интерфейса вашему окну, представлению или контроллеру представления. Вы также можете полностью отключить поддержку Dark Mode, используя ключ Info.plist.

Ссылка на сайт

Claudio
источник
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

ToniTornado
источник
2

Версия Objective-c

 if (@available(iOS 13.0, *)) {
        _window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    }
ahbou
источник
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. это заменит стиль приложения по умолчанию на указанное вами значение. Вам не нужно добавлять overrideUserInterfaceStyle = .light эту строку в каждый viewController, только одна строка в info.plist.

Мухаммед Эбрахим
источник
1

Просто добавьте следующий ключ в ваш info.plistфайл:

<key>UIUserInterfaceStyle</key>
    <string>Light</string>
Моин ахмад
источник
1
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }
Талха Расул
источник
Можете ли вы немного объяснить, как этот ответ решит проблему, вместо того, чтобы публиковать ответ только по коду.
Арун Винот
Да, конечно @ArunVinoth В IOS 13 введен темный режим, поэтому, если ваша цель развертывания ниже 13, используйте приведенный выше код, иначе вы можете использовать простое утверждение, записанное в блоке if.
Талха Расул
1

Swift 5

Два способа переключить темный в светлый режим:

1- info.plist

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

2- Программно

 UIApplication.shared.windows.forEach { window in
     window.overrideUserInterfaceStyle = .light
  } 
Рашид Латиф
источник
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
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}
Мохаммед Разипур
источник
2
Пожалуйста, добавьте некоторые пояснения к своему ответу, отредактировав его, чтобы другие могли извлечь из него урок
Нико Хаас
0

Вы можете сделать: добавить этот новый ключ UIUserInterfaceStyle в Info.plist и установить его значение Light. и проверьте, появляется ли контроллер оповещений в режиме освещения.

UIUserInterfaceStyle Light Если вы используете режим «свет / темнота» во всем приложении независимо от настроек пользователя, добавив ключ UIUserInterfaceStyle в файл Info.plist и установив для него значение Light или Dark.

Hominda
источник
0

На этот вопрос так много ответов, что, используя его, info.plistвы можете установить его AppDelegateследующим образом:

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

Тест на Xcode 11.3, iOS 13.3

Niraj
источник
-8

На самом деле я только что написал некоторый код, который позволит вам глобально отказаться от темного режима в коде, не прибегая к использованию каждого отдельного контроллера viw в вашем приложении. Это, вероятно, может быть улучшено, чтобы отказаться от класса за классом, управляя списком классов. Я хочу, чтобы мои пользователи увидели, нравится ли им интерфейс темного режима для моего приложения, и если им это не нравится, они могут отключить его. Это позволит им продолжать использовать темный режим для остальных приложений.

Выбор пользователя хороший (Гм, глядя на тебя, Apple, вот как ты должен был это реализовать).

Итак, как это работает, так это то, что это просто категория UIViewController. Когда он загружается, он заменяет собственный метод viewDidLoad на тот, который будет проверять глобальный флаг, чтобы увидеть, отключен ли темный режим для всех или нет.

Поскольку он запускается при загрузке UIViewController, он должен автоматически запуститься и отключить темный режим по умолчанию. Если это не то, что вам нужно, тогда вам нужно рано или поздно войти туда и установить флаг, или просто установить флаг по умолчанию.

Я еще ничего не написал, чтобы пользователь мог включить или выключить флаг. Так что это в основном пример кода. Если мы хотим, чтобы пользователь взаимодействовал с этим, все контроллеры представления должны будут перезагрузиться. Я не знаю, как сделать это от руки, но, вероятно, отправка какого-то уведомления поможет. Так что сейчас это глобальное включение / выключение для темного режима будет работать только при запуске или перезапуске приложения.

Теперь недостаточно просто отключить темный режим в каждом отдельном контроллере MFING в вашем огромном приложении. Если вы используете цветовые активы, вы полностью очищены от костей. Мы более 10 лет понимаем, что неизменяемые объекты неизменны. Цвета, которые вы получаете из каталога цветовых активов, говорят, что это UIColor, но они являются динамическими (изменяемыми) цветами и будут меняться под вами при переходе системы из темного в светлый режим. Это должно быть особенность. Но, конечно, нет главного переключателя, который бы попросил эти вещи прекратить вносить эти изменения (насколько я знаю сейчас, возможно, кто-то может улучшить это).

Итак, решение состоит из двух частей:

  1. публичная категория на UIViewController, которая предоставляет некоторые полезные и удобные методы ... например, я не думаю, что Apple думала о том, что некоторые из нас смешивают веб-код с нашими приложениями. Таким образом, у нас есть таблицы стилей, которые нужно переключать на основе темного или светлого режима. Таким образом, вам нужно либо создать какой-то динамический объект таблицы стилей (что было бы хорошо), либо просто спросить, каково текущее состояние (плохо, но легко).

  2. эта категория при загрузке заменит метод viewDidLoad класса UIViewController и перехватывает вызовы. Я не знаю, нарушает ли это правила магазина приложений. Если это произойдет, возможно, есть и другие способы, но вы можете считать это доказательством концепции. Например, вы можете сделать один подкласс из всех основных типов контроллеров представления и сделать так, чтобы все ваши собственные контроллеры представления наследовали от них, а затем вы можете использовать идею категории DarkMode и вызвать ее, чтобы заставить отказаться от всех ваших контроллеров представления. Это уродливее, но не нарушает никаких правил. Я предпочитаю использовать среду выполнения, потому что именно для этого была создана среда выполнения. Так что в моей версии вы просто добавляете категорию, вы устанавливаете глобальную переменную для категории, хотите ли вы, чтобы она блокировала темный режим, и она это сделает.

  3. Вы еще не вышли из леса, как уже упоминалось, другая проблема заключается в том, что UIColor делает все, что хочет. Так что даже если ваши контроллеры вида блокируют темный режим, UIColor не знает, где и как вы его используете, поэтому не может адаптироваться. В результате вы можете получить его правильно, но в будущем он вернется к вам. Возможно, скоро, может быть, позже. Так что обойти это можно, выделив его дважды с помощью CGColor и превратив его в статический цвет. Это означает, что если ваш пользователь вернется и снова включит темный режим на странице настроек (идея заключается в том, чтобы заставить эту работу работать таким образом, чтобы пользователь мог контролировать ваше приложение над остальной частью системы), все эти статические цвета нужна замена. Пока это остается решать кому-то другому. Самый простой способ сделать это - сделать по умолчанию Отказавшись от темного режима, разделите на ноль, чтобы вывести приложение из строя, поскольку вы не можете выйти из него, и попросите пользователя просто перезапустить его. Это, вероятно, нарушает правила магазина приложений, но это идея.

Категория UIColor не нуждается в показе, она просто работает, вызывая colorNamed: ... если вы не указали классу DarkMode ViewController блокировать темный режим, он будет работать прекрасно, как и ожидалось. Попытка сделать что-то элегантное вместо стандартного кода яблочного сфагетти, что будет означать, что вам придется изменить большую часть своего приложения, если вы хотите программно отказаться от темного режима или переключить его. Теперь я не знаю, есть ли лучший способ программно изменить Info.plist, чтобы отключить темный режим по мере необходимости. Насколько я понимаю, это особенность времени компиляции, и после этого вы костей.

Итак, вот код, который вам нужен. Должен быть вставлен и просто использовать один метод, чтобы установить стиль пользовательского интерфейса или установить по умолчанию в коде. Вы можете свободно использовать, изменять, делать с этим все, что вы хотите, для каких-либо целей, и никаких гарантий не дается, и я не знаю, пройдет ли это в магазине приложений. Улучшения очень приветствуются.

Справедливое предупреждение, я не использую 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 - это моя многократно используемая библиотека, и это только ее часть. Если что-то не компилируется, дайте мне знать.

dbquarrel
источник
Вам не повезло, когда дело доходит до контроля поведения UIColor, как обсуждалось в этом вопросе: stackoverflow.com/questions/56487679/…
raven_raven