É possível desativar o modo escuro no iOS 13?

295

Grande parte do meu aplicativo consiste em visualizações da Web para fornecer funcionalidades ainda não disponíveis em implementações nativas. A equipe da web não tem planos de implementar um tema sombrio para o site. Dessa forma, meu aplicativo ficará meio / meio com o suporte ao Modo escuro no iOS 13.

É possível desativar o suporte ao Modo escuro para que nosso aplicativo sempre mostre o modo claro para combinar com o tema do site?

SeanR
fonte
70
Defina UIUserInterfaceStylecomo Lightna sua Info.Plist. Veja developer.apple.com/library/archive/documentation/General/…
Tieme
1
Obrigado por perguntar - por todos nós. Muitos aplicativos para percorrer. Isso é necessário para manter os aplicativos funcionando até que a alternância esteja pronta.
user3741598
importar Foundation importar extensão UIKit UIViewController {substitui a função aberta awakeFromNib () {super.awakeFromNib () se # indisponível (iOS 13.0, *) {// Sempre adote um estilo de interface leve. overrideUserInterfaceStyle = .light}}}
Mohammad Razipour
1
basta adicionar UIUserInterfaceStyle no plist. é fácil assim
Fattie
Ao enviar o aplicativo para a appstore, a apple aceita devido a UIUserInterfaceStyle no modo Light.
Kiran

Respostas:

683

Primeiro, aqui está a entrada da Apple relacionada à saída do modo escuro. O conteúdo deste link foi escrito para o Xcode 11 e iOS 13 :

Esta seção se aplica ao uso do Xcode 11


Se você deseja cancelar sua inscrição INTEIRA

Abordagem # 1

Use a seguinte chave no seu arquivo info.plist :

UIUserInterfaceStyle

E atribua a ele um valor de Light.

O XML para a UIUserInterfaceStyleatribuição:

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

Abordagem # 2

Você pode definir overrideUserInterfaceStylea windowvariável do aplicativo .

Dependendo de como seu projeto foi criado, isso pode estar no AppDelegatearquivo ou no arquivo SceneDelegate.

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


Se você deseja desativar o seu UIViewController individualmente

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

Documentação da Apple para overrideUserInterfaceStyle

Como será o código acima no Xcode 11:

insira a descrição da imagem aqui

Esta seção se aplica ao uso do Xcode 10.x


Se você estiver usando o Xcode 11 para enviar, poderá ignorar com segurança tudo abaixo desta linha.

Como a API relevante não existe no iOS 12, você receberá erros ao tentar usar os valores fornecidos acima:

Para definir overrideUserInterfaceStyleem seuUIViewController

insira a descrição da imagem aqui

Se você deseja desativar o seu UIViewController individualmente

Isso pode ser tratado no Xcode 10 testando a versão do compilador e a versão do iOS:

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

Se você deseja cancelar sua inscrição INTEIRA

Você pode modificar o trecho acima para funcionar com todo o aplicativo do Xcode 10, adicionando o seguinte código ao seu AppDelegatearquivo.

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

No entanto, a configuração plist falhará ao usar o Xcode versão 10.x:

insira a descrição da imagem aqui

Os nossos agradecimentos a @Aron Nelson , @Raimundas Sakalauskas , @NSLeader e rmaddy por melhorarem essa resposta com seus comentários.

CodeBender
fonte
2
A luz UIUserInterfaceStyle é bloqueada ao atualizar / carregar seu aplicativo agora. Ele é sinalizado como uma entrada inválida. (Chave de plist inválida)
Aron Nelson
2
Isso não será compilado no iOS SDK 12 (atualmente o SDK estável mais recente). Consulte stackoverflow.com/a/57521901/2249485 para obter uma solução que também funcionará com o iOS 12 SDK.
Raimundas Sakalauskas
Isso é tão injusto que a pergunta que tem muito mais visualizações do que a "pergunta original" é bloqueada para fornecer respostas. :(
Raimundas Sakalauskas
7
Em vez de configurar overrideUserInterfaceStyleem viewDidLoadtodos os controladores de exibição, você pode configurá-lo uma vez na janela principal do aplicativo. Muito mais fácil se você quiser que o aplicativo inteiro se comporte de uma maneira.
precisa saber é
2
Use #if compiler(>=5.1)vez responds(to:)esetValue
NSLeader
162

De acordo com a sessão da Apple sobre "Implementando o Modo Escuro no iOS" ( https://developer.apple.com/videos/play/wwdc2019/214/ a partir das 31:13), é possível definir overrideUserInterfaceStylepara UIUserInterfaceStyleLightou UIUserInterfaceStyleDarkem qualquer controlador de exibição ou exibição , que será usado no traitCollectionpara qualquer subview ou view controller.

Como já mencionado pelo SeanR, você pode definir UIUserInterfaceStylepara Lightou Darkno arquivo plist do seu aplicativo para alterar isso em todo o aplicativo.

dorbeetle
fonte
17
Se você definir a chave UIUserInterfaceStyle seu aplicativo será rejeitado na App Store
Sonius
2
A Apple rejeitou com o código de erro ITMS-90190 forums.developer.apple.com/thread/121028
PRASAD1240
11
É provável que a rejeição ocorra porque o iOS 13 SDK ainda não está na versão beta. Eu acho que isso deve funcionar assim que o Xcode 11 GM estiver disponível.
precisa saber é o seguinte
2
@dorbeetle não é verdade, carreguei meu aplicativo com essa chave com sucesso há 1 mês com o Xcode 10. As rejeições acontecem recentemente. Parece alguns tipos de nova estratégia da Apple.
Steven
1
Ainda está acontecendo. O Xcode GM2 retornou um erro de assinatura do aplicativo. O Xcode 10.3 retornou: "Chave Info.plist inválida. A chave 'UIUserInterfaceStyle' no arquivo Payload / Galileo.appInfo.plist não é válida."
Evgen Bodunov 20/09/19
64

Se você não estiver usando o Xcode 11 ou posterior (i, e iOS 13 ou posterior SDK), seu aplicativo não optou automaticamente pelo suporte ao modo escuro. Portanto, não há necessidade de sair do modo escuro.

Se você estiver usando o Xcode 11 ou posterior, o sistema ativou automaticamente o modo escuro para seu aplicativo. Existem duas abordagens para desativar o modo escuro, dependendo da sua preferência. Você pode desativá-lo totalmente ou desativá-lo para qualquer janela, exibição ou controlador específico.

Desativar o modo escuro totalmente para o seu aplicativo

Você pode desativar o modo escuro incluindo a UIUserInterfaceStylechave com um valor, como Lightno arquivo Info.plist do seu aplicativo. Isso ignora a preferência do usuário e sempre aplica uma aparência leve ao seu aplicativo.
UIUserInterfaceStyle as Light

Desativar o modo escuro para Window, View ou View Controller

Você pode forçar sua interface a sempre aparecer em um estilo claro ou escuro, definindo o overrideUserInterfaceStyle propriedade da janela, exibição ou controlador de exibição apropriado.

Ver controladores:

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

Visualizações:

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

Janela:

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

Nota: A Apple recomenda enfaticamente o suporte ao modo escuro no seu aplicativo. Portanto, você só pode desativar o modo escuro temporariamente.

Leia mais aqui: Escolhendo um estilo de interface específico para seu aplicativo para iOS

Ajith R Nayak
fonte
34

********** A maneira mais fácil para o Xcode 11 e superior ***********

Adicione isso ao info.plist antes </dict></plist>

<key>UIUserInterfaceStyle</key>
<string>Light</string>
Kingsley Mitchell
fonte
esta solução falhará ao enviar o aplicativo no Xcode 10.x
Tawfik Bouabid
27

Eu acho que encontrei a solução. Inicialmente, eu o reuni em UIUserInterfaceStyle - Information Property List e UIUserInterfaceStyle - UIKit , mas agora o encontrei realmente documentado em Escolhendo um estilo de interface específico para o seu aplicativo iOS .

No seu info.plist, defina UIUserInterfaceStyle( Estilo da interface do usuário ) como 1 ( UIUserInterfaceStyle.light).

EDIT: Conforme resposta do dorbeetle, UIUserInterfaceStylepode ser uma configuração mais apropriada Light.

SeanR
fonte
A imposição do modo escuro, definindo o valor como 2, não funciona:[UIInterfaceStyle] '2' is not a recognized value for UIUserInterfaceStyle. Defaulting to Light.
funkenstrahlen
3
Ter essa chave na lista resultará em uma rejeição da App Store.
José José
1
O AppStore não rejeita mais essa propriedade no plist.info. Coloquei "Escuro" (em maiúscula), pois nosso aplicativo já está escuro. Sem problemas. Isso nos permite usar adequadamente os controles do sistema.
nickdnk 25/09/19
@nickdnk Acho que você criou seu aplicativo com o Xcode 11, recomendado pela Apple.
DawnSong 26/09/19
1
Sim eu fiz. Isso não muda o fato de a Apple aceitar esse parâmetro na lista, que era o que eu estava tentando deixar claro.
nickdnk 26/09/19
23

A resposta acima funciona se você quiser desativar todo o aplicativo. Se você está trabalhando na lib que possui interface do usuário e não tem o luxo de editar o .plist, também pode fazê-lo via código.

Se você estiver compilando no iOS 13 SDK, poderá simplesmente usar o seguinte código:

Rápido:

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

Obj-C:

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

NO ENTANTO , se você deseja que seu código seja compilado no iOS 12 SDK também (que ainda é o SDK estável mais recente), você deve recorrer ao uso de seletores. Código com seletores:

Swift (o XCode mostrará avisos para esse código, mas essa é a única maneira de fazê-lo por enquanto, pois a propriedade não existe no SDK 12 e, portanto, não será compilada):

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"];
    }
}
Raimundas Sakalauskas
fonte
Será melhor se você especificar a que propriedade overrideUserInterfaceStylepertence.
DawnSong
12

Última atualização-

Se você estiver usando o Xcode 10.x, o padrão UIUserInterfaceStyleé o lightiOS 13.x. Quando executado em um dispositivo iOS 13, ele funcionará apenas no Modo Luz.

Não há necessidade de adicionar explicitamente a UIUserInterfaceStylechave no arquivo Info.plist, pois isso causará um erro ao validar seu aplicativo, dizendo:

Chave Info.plist inválida. A chave 'UIUserInterfaceStyle' no arquivo Payload / AppName.appInfo.plist não é válida.

Adicione apenas a UIUserInterfaceStylechave no arquivo Info.plist ao usar o Xcode 11.x.

kumarsiddharth123
fonte
1
Isso não tem nada a ver com o Xcode 10 ou 11. Se o usuário implantar o formulário do aplicativo Xcode 10 e não cuidar do modo escuro, o aplicativo, quando instalado no iPhone 11, Pro ou Pro Max, terá problemas no modo escuro. você precisa atualizar para o Xcode 11 e resolver esse problema.
Niranjan Molkeri
3
@NiranjanMolkeri Isso não tem nada a ver com os iPhones mais recentes. É sobre o modo Escuro no iOS 13. Na versão anterior do iOS 13, os aplicativos da versão beta estavam com problemas no modo escuro, se não tratados explicitamente. Mas na versão mais recente, isso é corrigido. Se você estiver usando o XCode 10, o UIUserInterfaceStyle padrão será leve para iOS13. Se você estiver usando o Xode11, precisará lidar com isso.
kumarsiddharth123
Você terá problemas se fizer upload de um aplicativo no TestFligth usando o Xcode 10.3 e o plist incluir a chave UIUserInterfaceStyle. Ele dirá que é um arquivo plist inválido. Você tem que quer removê-lo se construindo no Xcode 10, ou upload usando o Xcode 11
eharo2
9

Se você adicionar uma UIUserInterfaceStylechave ao arquivo plist, possivelmente a Apple rejeitará a compilação de versão conforme mencionado aqui: https://stackoverflow.com/a/56546554/7524146 De qualquer forma, é irritante dizer explicitamente a cada ViewController self.overrideUserInterfaceStyle = .light . Mas você pode usar essa paz de código uma vez para o seu windowobjeto raiz :

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

Observe que você não pode fazer isso application(application: didFinishLaunchingWithOptions:)por dentro, pois esse seletor não responderá trueno estágio inicial. Mas você pode fazer isso mais tarde. É super fácil se você estiver usando classe AppPresenterou personalizado AppRouterno seu aplicativo em vez de iniciar a interface do usuário no AppDelegate automaticamente.

SerhiiK
fonte
9

Você pode desativar o Modo escuro em todo o aplicativo no Xcode 11:

  1. Ir Info.plist
  2. Adicionar abaixo como

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

Info.plist será semelhante abaixo ...

insira a descrição da imagem aqui

Enamul Haque
fonte
1
não funciona para o Xcode versão 11.3.1 (11C504) por algum motivo
Andrew
7

- Para todo o aplicativo (janela):

window!.overrideUserInterfaceStyle = .light

Você pode obter a janela de SceneDelegate

- Para um único ViewController:

viewController.overrideUserInterfaceStyle = .light

Você pode definir qualquer viewController, mesmo dentro do viewController-lo auto

- Para uma única visualização:

view.overrideUserInterfaceStyle = .light

Você pode definir qualquer view, mesmo dentro da vista, auto

Pode ser necessário usar if #available(iOS 13.0, *) { ,,, }se você suporta versões anteriores do iOS.

Mojtaba Hosseini
fonte
6

Além de outras respostas, pelo que entendi a seguir, você só precisa se preparar para o modo Escuro ao compilar com o iOS 13 SDK (usando o XCode 11).

O sistema pressupõe que os aplicativos vinculados ao SDK do iOS 13 ou posterior suportem aparências claras e escuras. No iOS, você especifica a aparência específica que deseja, atribuindo um estilo de interface específico à sua janela, exibição ou controlador de exibição. Você também pode desativar totalmente o suporte ao Modo Escuro usando uma chave Info.plist.

Ligação

Claudio
fonte
2

Sim, você pode pular adicionando o seguinte código em viewDidLoad:

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

Meu aplicativo não suporta o modo escuro a partir de agora e usa uma cor clara na barra de aplicativos. Consegui forçar o conteúdo da barra de status para texto e ícones escuros adicionando a seguinte chave ao meu Info.plist:

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

Encontre os outros valores possíveis aqui: https://developer.apple.com/documentation/uikit/uistatusbarstyle

ToniTornado
fonte
2

Versão Objective-c

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

Aqui estão algumas dicas e truques que você pode usar no seu aplicativo para oferecer suporte ou ignorar o modo escuro.

Primeira dica: para substituir o estilo ViewController

você pode substituir o estilo de interface do UIViewController

1: overrideUserInterfaceStyle = .dark // Para o modo escuro

2: overrideUserInterfaceStyle = .light // Para o modo light

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

Segunda dica: Adicionando uma chave no info.plist

Simplesmente você pode adicionar uma nova chave

UIUserInterfaceStyle

em seu aplicativo info.plist e defina seu valor como Claro ou Escuro. isso substituirá o estilo padrão do aplicativo pelo valor que você fornecer. Você não precisa adicionar overrideUserInterfaceStyle = .light essa linha em cada viewController, apenas uma linha em info.plist.

Mohammed Ebrahim
fonte
1

Basta adicionar a seguinte chave no seu info.plistarquivo:

<key>UIUserInterfaceStyle</key>
    <string>Light</string>
Moeen Ahmad
fonte
1
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }
Talha Rasool
fonte
Você pode explicar um pouco como essa resposta resolverá o problema, em vez de postar uma resposta apenas de código.
Arun Vinoth 26/12/19
Sim, com certeza @ArunVinoth No IOS 13, o modo escuro é introduzido, portanto, se seu destino de implantação for menor que 13, use o código acima, caso contrário, você pode usar uma declaração simples escrita em if block.
Talha Rasool
1

Swift 5

Duas maneiras de alternar entre o modo escuro e claro:

1- info.plist

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

2- Programaticamente

 UIApplication.shared.windows.forEach { window in
     window.overrideUserInterfaceStyle = .light
  } 
Rashid Latif
fonte
0

Eu usaria essa solução, pois a propriedade da janela pode ser alterada durante o ciclo de vida do aplicativo. Portanto, a atribuição de "overrideUserInterfaceStyle = .light" precisa ser repetida. UIWindow.appearance () nos permite definir o valor padrão que será usado para objetos UIWindow recém-criados.

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
    }
}
Dmitry
fonte
0

Basta adicionar estas linhas no arquivo info.plist:

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

Isso forçará o aplicativo a ser executado apenas no modo de luz.

Rahul Gusain
fonte
Isso já foi comentado e respondido várias vezes. Até a resposta aceita está sugerindo isso. Portanto, este comentário não adiciona nenhuma informação nova.
JeroenJK
0
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}
Mohammad Razipour
fonte
2
Adicione algumas explicações à sua resposta editando-a, para que outras pessoas possam aprender com ela
Nico Haase
0

Você pode fazer: adicione esta nova chave UIUserInterfaceStyle a Info.plist e defina seu valor como Light. e verifique se o controlador de alerta aparece no modo de luz.

UIUserInterfaceStyle Light Se você forçar o modo claro / escuro em todo o aplicativo, independentemente das configurações do usuário, adicione a chave UIUserInterfaceStyle ao arquivo Info.plist e defina seu valor como Claro ou Escuro.

Hominda
fonte
0

Esta pergunta tem muitas respostas. Em vez de usá-la, info.plistvocê pode configurá-la da AppDelegateseguinte maneira:

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

Teste no Xcode 11.3, iOS 13.3

Niraj
fonte
-8

Na verdade, acabei de escrever um código que permitirá que você desative globalmente o modo escuro no código sem precisar digitar todos os controladores viw em seu aplicativo. Provavelmente, isso pode ser refinado para optar por excluir classe por classe, gerenciando uma lista de classes. Para mim, o que eu quero é que meus usuários vejam se eles gostam da interface de modo escuro do meu aplicativo e, se não gostam, podem desativá-lo. Isso permitirá que eles continuem usando o modo escuro para o restante de seus aplicativos.

A escolha do usuário é boa (Ahem, olhando para você Apple, é assim que você deveria ter implementado).

Então, como isso funciona é que é apenas uma categoria do UIViewController. Quando carrega, substitui o método viewDidLoad nativo por um que verificará um sinalizador global para ver se o modo escuro está desativado para tudo ou não.

Como é acionado no carregamento do UIViewController, ele deve iniciar automaticamente e desativar o modo escuro por padrão. Se isso não é o que você deseja, é necessário chegar lá cedo e definir o sinalizador, ou apenas definir o sinalizador padrão.

Ainda não escrevi nada para responder ao usuário que liga ou desliga a bandeira. Portanto, este é basicamente um código de exemplo. Se quisermos que o usuário interaja com isso, todos os controladores de exibição precisarão ser recarregados. Não sei como fazer isso de imediato, mas provavelmente enviar alguma notificação será suficiente. Então, neste momento, esse global ativado / desativado para o modo escuro só funcionará na inicialização ou reinicialização do aplicativo.

Agora, não é apenas o suficiente tentar desativar o modo escuro em todos os controladores de exibição MFING em seu enorme aplicativo. Se você estiver usando materiais coloridos, estará completamente desossado. Por mais de 10 anos, entendemos que objetos imutáveis ​​são imutáveis. As cores que você obtém do catálogo de ativos de cores dizem que são UIColor, mas são cores dinâmicas (mutáveis) e serão alteradas embaixo de você conforme o sistema muda do modo escuro para o claro. Isso deveria ser um recurso. Mas é claro que não há um mestre para pedir que essas coisas parem de fazer essa alteração (até onde eu sei agora, talvez alguém possa melhorar isso).

Portanto, a solução está dividida em duas partes:

  1. uma categoria pública no UIViewController que fornece alguns métodos de utilidade e conveniência ... por exemplo, não acho que a apple tenha pensado no fato de que alguns de nós misturam código da web em nossos aplicativos. Como tal, temos folhas de estilo que precisam ser alternadas com base no modo escuro ou claro. Portanto, você precisa criar algum tipo de objeto de folha de estilo dinâmico (o que seria bom) ou apenas perguntar qual é o estado atual (ruim, mas fácil).

  2. essa categoria ao carregar substituirá o método viewDidLoad da classe UIViewController e interceptará as chamadas. Não sei se isso viola as regras da loja de aplicativos. Nesse caso, provavelmente existem outras maneiras de contornar isso, mas você pode considerá-lo uma prova de conceito. Você pode, por exemplo, criar uma subclasse de todos os tipos principais de controladores de exibição e herdar todos os seus próprios controladores de exibição e, em seguida, você pode usar a ideia da categoria DarkMode e entrar nela para forçar a desativação de todos os controladores de exibição. É mais feio, mas não vai quebrar nenhuma regra. Eu prefiro usar o tempo de execução porque é para isso que o tempo de execução foi feito. Então, na minha versão, você apenas adiciona a categoria, define uma variável global para a categoria, quer queira ou não bloquear o modo escuro, e ele fará isso.

  3. Você ainda não saiu da floresta, como mencionado, o outro problema é que o UIColor está basicamente fazendo o que quiser. Portanto, mesmo que seus controladores de tela estejam bloqueando o modo escuro, o UIColor não sabe onde ou como você o está usando, por isso não pode se adaptar. Como resultado, você pode buscá-lo corretamente, mas, em algum momento, ele reverterá em você. Talvez em breve, talvez mais tarde. Portanto, a solução é alocá-lo duas vezes usando um CGColor e transformá-lo em uma cor estática. Isso significa que, se o usuário voltar e reativar o modo escuro na sua página de configurações (a idéia aqui é fazer com que isso funcione para que o usuário tenha controle sobre seu aplicativo além do resto do sistema), todas essas cores estáticas precisa substituir. Até agora, isso resta para outra pessoa resolver. A maneira mais fácil de fazer isso é tornar um padrão que você Ao sair do modo escuro, divida por zero para travar o aplicativo, pois você não pode sair dele e peça ao usuário que o reinicie. Provavelmente isso também viola as diretrizes da loja de aplicativos, mas é uma ideia.

A categoria UIColor não precisa ser exposta, apenas funciona chamando colorNamed: ... se você não disse à classe DarkMode ViewController para bloquear o modo escuro, ela funcionará perfeitamente como esperado. Tentando criar algo elegante em vez do código padrão de sphaghetti da maçã, o que significa que você precisará modificar a maior parte do seu aplicativo se desejar desativar programaticamente o modo escuro ou alterná-lo. Agora não sei se existe uma maneira melhor de alterar programaticamente o Info.plist para desativar o modo escuro conforme necessário. No meu entender, esse é um recurso de tempo de compilação e, depois disso, você é desossado.

Então, aqui está o código que você precisa. Deve aparecer e usar apenas um método para definir o estilo da interface do usuário ou o padrão no código. Você é livre para usar, modificar, fazer o que quiser com isso para qualquer finalidade e não há garantia e não sei se ele passará na loja de aplicativos. Melhorias muito bem-vindas.

Aviso justo Eu não uso o ARC ou qualquer outro método de retenção manual.

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

Há um conjunto de funções utilitárias que ele usa para fazer a troca de métodos. Arquivo separado. Porém, isso é padrão e você pode encontrar código semelhante em qualquer lugar.

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

Estou copiando e colando isso em alguns arquivos desde que o q-runtime.h é minha biblioteca reutilizável e isso é apenas parte dela. Se algo não compilar, me avise.

dbquarrel
fonte
Você não está fora de sorte quando se trata de controlando comportamento UIColor, como discutido nesta questão: stackoverflow.com/questions/56487679/...
raven_raven