¿Es posible desactivar el modo oscuro en iOS 13?


296

Una gran parte de mi aplicación consiste en vistas web para proporcionar una funcionalidad que aún no está disponible a través de implementaciones nativas. El equipo web no tiene planes de implementar un tema oscuro para el sitio web. Como tal, mi aplicación se verá un poco mitad / mitad con soporte para Modo Oscuro en iOS 13.

¿Es posible optar por no admitir el modo oscuro de modo que nuestra aplicación siempre muestre el modo claro para que coincida con el tema del sitio web?


70
Establecer UIUserInterfaceStylea Lighten su info.plist. Ver developer.apple.com/library/archive/documentation/General/…
Tieme

1
Gracias por preguntar, por todos nosotros. Muchas aplicaciones para pasar. Esto es necesario para mantener las aplicaciones funcionando hasta que la palanca esté lista.
user3741598

import Foundation import UIKit extension UIViewController {anular función abierta awakeFromNib () {super.awakeFromNib () si #available (iOS 13.0, *) {// Siempre adopte un estilo de interfaz ligero. overrideUserInterfaceStyle = .light}}}
Mohammad Razipour

1
simplemente agregue UIUserInterfaceStyle en plist. es así de fácil
Fattie

Al enviar la aplicación a la tienda de aplicaciones, Apple acepta debido a UIUserInterfaceStyle en modo Light.
kiran el

Respuestas:


684

Primero, aquí está la entrada de Apple relacionada con la opción de salir del modo oscuro. El contenido de este enlace está escrito para Xcode 11 y iOS 13 :

Esta sección se aplica al uso de Xcode 11


Si desea inhabilitar su aplicación COMPLETA

Enfoque n. ° 1

Use la siguiente clave en su archivo info.plist :

UIUserInterfaceStyle

Y asígnele un valor de Light.

El XML para la UIUserInterfaceStyletarea:

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

Enfoque n. ° 2

Puede establecer overrideUserInterfaceStylecontra la windowvariable de la aplicación .

Dependiendo de cómo se creó su proyecto, esto puede estar en el AppDelegatearchivo o en el SceneDelegate.

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


Si desea optar por su UIViewController de forma individual

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

Documentación de Apple para overrideUserInterfaceStyle

Cómo se verá el código anterior en Xcode 11:

ingrese la descripción de la imagen aquí

Esta sección se aplica al uso de Xcode 10.x


Si está utilizando Xcode 11 para su envío, puede ignorar todo lo que se encuentra debajo de esta línea.

Dado que la API relevante no existe en iOS 12, obtendrá errores al intentar utilizar los valores proporcionados anteriormente:

Para poner overrideUserInterfaceStyleen suUIViewController

ingrese la descripción de la imagen aquí

Si desea optar por su UIViewController de forma individual

Esto se puede manejar en Xcode 10 probando la versión del compilador y la versión de iOS:

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

Si desea inhabilitar su aplicación COMPLETA

Puede modificar el fragmento anterior para que funcione con toda la aplicación para Xcode 10, agregando el siguiente código a su AppDelegatearchivo.

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

Sin embargo, la configuración de plist fallará al usar Xcode versión 10.x:

ingrese la descripción de la imagen aquí

Gracias a @Aron Nelson , @Raimundas Sakalauskas , @NSLeader y rmaddy por mejorar esta respuesta con sus comentarios.


2
La luz UIUserInterfaceStyle se bloquea al actualizar / cargar su aplicación ahora. Se marca como una entrada de lista inválida. (Clave de plist inválida)
Aron Nelson

2
Esto no se compilará con iOS SDK 12 (actualmente el último SDK estable). Consulte stackoverflow.com/a/57521901/2249485 para ver la solución que también funcionará con iOS 12 SDK.
Raimundas Sakalauskas

Esto es tan injusto que la pregunta que tiene muchos más puntos de vista que la "pregunta original" está bloqueada para proporcionar respuestas. :(
Raimundas Sakalauskas

77
En lugar de establecer overrideUserInterfaceStyleen el viewDidLoadde todos los controladores de vista, se puede establecer una vez en la ventana principal de la aplicación. Mucho más fácil si quieres que toda la aplicación se comporte de una manera.
rmaddy

2
Utilice en su #if compiler(>=5.1)lugar responds(to:)ysetValue
NSLeader

162

De acuerdo con la sesión de Apple en "modo oscuro de aplicación en iOS" ( https://developer.apple.com/videos/play/wwdc2019/214/ a partir de las 31:13), es posible fijar overrideUserInterfaceStylea UIUserInterfaceStyleLighto UIUserInterfaceStyleDarken cualquier controlador de vista o vista , que se utilizará en traitCollectioncualquier subvista o controlador de vista.

Como ya se ha mencionado por SeanR, se puede establecer UIUserInterfaceStyleque Lighto Darken el archivo plist de su aplicación para cambiar esto para toda la aplicación.


17
Si configura la clave UIUserInterfaceStyle, su aplicación será rechazada en App store
Sonius

2
Apple rechazó con el código de error ITMS-90190 forums.developer.apple.com/thread/121028
PRASAD1240

11
Es muy probable que el rechazo suceda porque el SDK de iOS 13 aún no está fuera de beta. Creo que esto debería funcionar tan pronto como el Xcode 11 GM esté disponible.
dorbeetle

2
@dorbeetle no es cierto, cargué mi aplicación con esta clave con éxito como hace 1 mes con Xcode 10. Los rechazos ocurren recientemente. Parece algún tipo de nueva estrategia de Apple.
Steven

1
Sigue sucediendo Xcode GM2 devolvió un error de firma de la aplicación. Xcode 10.3 devolvió: "Clave inválida de Info.plist. La clave 'UIUserInterfaceStyle' en el archivo Payload / Galileo.appInfo.plist no es válida".
Evgen Bodunov

64

Si no está utilizando Xcode 11 o posterior (i, e iOS 13 o posterior SDK), su aplicación no ha optado automáticamente por el modo oscuro. Por lo tanto, no hay necesidad de salir del modo oscuro.

Si está utilizando Xcode 11 o posterior, el sistema ha habilitado automáticamente el modo oscuro para su aplicación. Hay dos enfoques para deshabilitar el modo oscuro según su preferencia. Puede deshabilitarlo por completo o deshabilitarlo para cualquier ventana, vista o controlador de vista específico.

Desactiva el modo oscuro por completo para tu aplicación

Puede deshabilitar el modo oscuro al incluir la UIUserInterfaceStyleclave con un valor como Lighten el archivo Info.plist de su aplicación. Esto ignora las preferencias del usuario y siempre aplica una apariencia ligera a su aplicación.
UIUserInterfaceStyle as Light

Deshabilitar el modo oscuro para ventana, vista o controlador de vista

Puede forzar su interfaz para que siempre aparezca en un estilo claro u oscuro configurando el overrideUserInterfaceStyle propiedad de la ventana, vista o controlador de vista apropiado.

Ver controladores:

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

Puntos de vista:

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

Ventana:

/* 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: Apple recomienda encarecidamente que admita el modo oscuro en su aplicación. Por lo tanto, solo puede deshabilitar el modo oscuro temporalmente.

Lea más aquí: Cómo elegir un estilo de interfaz específico para su aplicación iOS


34

********** La forma más fácil para Xcode 11 y superior ***********

Agregue esto a info.plist antes </dict></plist>

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

esta solución fallará cuando envíe la aplicación en Xcode 10.x
Tawfik Bouabid el

27

Creo que he encontrado la solución. Inicialmente lo ensamblé desde UIUserInterfaceStyle - Lista de propiedades de información y UIUserInterfaceStyle - UIKit , pero ahora lo he encontrado documentado en Elegir un estilo de interfaz específico para su aplicación iOS .

En su info.plist, establezca UIUserInterfaceStyle( Estilo de interfaz de usuario ) en 1 ( UIUserInterfaceStyle.light).

EDITAR: Según la respuesta de dorbeetle, UIUserInterfaceStylepuede ser una configuración más adecuada para Light.


Sin embargo[UIInterfaceStyle] '2' is not a recognized value for UIUserInterfaceStyle. Defaulting to Light.
aplicar el

3
Tener esta clave en la lista resultará en un rechazo de la App Store.
José

1
AppStore ya no rechaza esta propiedad en plist.info. Puse "Oscuro" (en mayúscula) ya que nuestra aplicación ya está oscura. No hay problemas. Esto nos permite usar adecuadamente los controles del sistema.
nickdnk

@nickdnk Creo que construiste tu aplicación con Xcode 11, que recomienda Apple.
DawnSong

1
Sí, lo hice. No cambia el hecho de que Apple acepta este parámetro en la lista, que era lo que estaba tratando de aclarar.
nickdnk

23

La respuesta anterior funciona si desea inhabilitar toda la aplicación. Si está trabajando en la biblioteca que tiene una interfaz de usuario y no tiene el lujo de editar .plist, también puede hacerlo a través del código.

Si está compilando contra iOS 13 SDK, simplemente puede usar el siguiente código:

Rápido:

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

Obj-C:

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

SIN EMBARGO , si desea que su código también se compile con iOS 12 SDK (que en este momento sigue siendo el último SDK estable), debe recurrir al uso de selectores. Código con selectores:

Swift (XCode mostrará advertencias para este código, pero esa es la única forma de hacerlo por ahora, ya que la propiedad no existe en el SDK 12, por lo tanto, no se compilará):

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"];
    }
}

Será mejor si especifica a qué overrideUserInterfaceStylepertenece la propiedad .
DawnSong

12

Última actualización-

Si está utilizando Xcode 10.x, el valor predeterminado UIUserInterfaceStylees lightpara iOS 13.x. Cuando se ejecuta en un dispositivo iOS 13, solo funcionará en modo ligero.

No es necesario agregar explícitamente la UIUserInterfaceStyleclave en el archivo Info.plist, agregarlo dará un error cuando valide su aplicación, diciendo:

Clave de lista de información no válida. La clave 'UIUserInterfaceStyle' en el archivo Payload / AppName.appInfo.plist no es válida.

Solo agregue la UIUserInterfaceStyleclave en el archivo Info.plist cuando use Xcode 11.x.


1
Esto no tiene nada que ver con Xcode 10 u 11. Si el usuario implementa la aplicación desde Xcode 10 y no se ocupa del modo oscuro, la aplicación cuando se instala en iPhone 11, Pro o Pro Max tendrá problemas con el modo oscuro. necesita actualizar a Xcode 11 y abordar este problema.
Niranjan Molkeri

3
@NiranjanMolkeri Esto no tiene nada que ver con los nuevos iPhone. Se trata del modo oscuro en iOS 13. En las aplicaciones anteriores de la versión beta de iOS 13, la interfaz de usuario tenía problemas con el modo oscuro si no se manejaba explícitamente. Pero en la última versión, eso está arreglado. Si está utilizando XCode 10, entonces el UIUserInterfaceStyle predeterminado es ligero para iOS13. Si está utilizando Xode11, debe manejarlo.
kumarsiddharth123

Tendrá problemas si carga una aplicación en TestFligth usando Xcode 10.3 y la lista incluye la clave UIUserInterfaceStyle. Dirá que es un archivo plist inválido.
Debe

9

Si va a agregar UIUserInterfaceStyleclave al archivo plist, posiblemente Apple rechazará la versión de lanzamiento como se menciona aquí: https://stackoverflow.com/a/56546554/7524146 De todos modos, es molesto decirle explícitamente a cada ViewController self.overrideUserInterfaceStyle = .light . Pero puede usar esta paz de código una vez para su windowobjeto raíz :

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

Solo tenga en cuenta que no puede hacer esto adentro application(application: didFinishLaunchingWithOptions:)porque este selector no responderá trueen esa etapa temprana. Pero puedes hacerlo más tarde. Es super fácil si está utilizando la costumbre AppPresentero AppRouterclase en su aplicación en lugar de iniciar la interfaz de usuario en el AppDelegate automáticamente.


9

Puede desactivar el Modo oscuro en toda la aplicación en Xcode 11:

  1. Ir a la lista de información
  2. Añadir abajo como

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

Info.plist se verá a continuación ...

ingrese la descripción de la imagen aquí


1
no funciona para Xcode versión 11.3.1 (11C504) por alguna razón
Andrew

7

- Para toda la aplicación (ventana):

window!.overrideUserInterfaceStyle = .light

Puedes obtener la ventana de SceneDelegate

- Para un solo ViewController:

viewController.overrideUserInterfaceStyle = .light

Se puede establecer cualquier viewController, incluso dentro de la viewController se auto

- Para una sola vista:

view.overrideUserInterfaceStyle = .light

Se puede establecer cualquier view, incluso dentro de la vista auto

Es posible que deba usarlo if #available(iOS 13.0, *) { ,,, }si admite versiones anteriores de iOS.


6

Además de otras respuestas, según tengo entendido lo siguiente, solo necesita prepararse para el modo Oscuro al compilar contra iOS 13 SDK (usando XCode 11).

El sistema supone que las aplicaciones vinculadas con el SDK de iOS 13 o posterior admiten apariencias claras y oscuras. En iOS, usted especifica la apariencia específica que desea asignando un estilo de interfaz específico a su ventana, vista o controlador de vista. También puede deshabilitar la compatibilidad con el Modo oscuro por completo utilizando una tecla Info.plist.

Enlace


2

Sí, puede omitir agregando el siguiente código en viewDidLoad:

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

2

Mi aplicación no admite el modo oscuro a partir de ahora y utiliza un color de barra de aplicación claro. Pude forzar el contenido de la barra de estado a texto e iconos oscuros al agregar la siguiente clave a mi Info.plist:

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

Encuentre los otros valores posibles aquí: https://developer.apple.com/documentation/uikit/uistatusbarstyle


2

Versión objetiva-c

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

1

Aquí hay algunos consejos y trucos que puede usar en su aplicación para apoyar o evitar el modo oscuro.

Primer consejo: para anular el estilo ViewController

puede anular el estilo de interfaz de UIViewController

1: overrideUserInterfaceStyle = .dark // Para el modo oscuro

2: overrideUserInterfaceStyle = .light // Para el modo de luz

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

Segundo consejo: agregar una clave en info.plist

Simplemente puedes agregar una nueva clave

UIUserInterfaceStyle

en su aplicación info.plist y establezca su valor en Claro u Oscuro. esto anulará el estilo predeterminado de la aplicación al valor que proporcione. No tiene que agregar overrideUserInterfaceStyle = .light esta línea en cada viewController, solo una línea en info.plist, eso es todo.


1

Simplemente agregue la siguiente clave en su info.plistarchivo:

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

1
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }

¿Puede explicar un poco cómo esta respuesta resolverá el problema, en lugar de publicar una respuesta de solo código.
Arun Vinoth

Sí, seguro @ArunVinoth En el IOS 13 se introduce el modo oscuro, por lo que si el objetivo de implementación es inferior a 13, use el código anterior, de lo contrario, puede usar una declaración simple escrita en if block.
Talha Rasool

1

Swift 5

Dos formas de cambiar el modo oscuro al claro:

1- info.plist

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

2- programáticamente

 UIApplication.shared.windows.forEach { window in
     window.overrideUserInterfaceStyle = .light
  } 

0

Usaría esta solución ya que la propiedad de la ventana puede cambiarse durante el ciclo de vida de la aplicación. Por lo tanto, se debe repetir la asignación de "overrideUserInterfaceStyle = .light" UIWindow.appearance () nos permite establecer el valor predeterminado que se usará para los objetos UIWindow recién creados.

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

Simplemente agregue estas líneas en el archivo info.plist:

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

Esto obligará a la aplicación a ejecutarse solo en modo ligero.


Esto ya fue comentado y respondido muchas veces. Incluso la respuesta aceptada sugiere esto. Por lo tanto, este comentario no agrega ninguna información nueva.
JeroenJK

0
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}

2
Agregue alguna explicación a su respuesta editándola, de modo que otros puedan aprender de ella
Nico Haase

0

Puede hacerlo: agregue esta nueva clave UIUserInterfaceStyle a Info.plist y establezca su valor en Light. y verifique que el controlador de alerta aparezca con el modo de luz.

UIUserInterfaceStyle Light Si está forzado en modo claro / oscuro en toda su aplicación, independientemente de la configuración del usuario, agregue la clave UIUserInterfaceStyle a su archivo Info.plist y configure su valor en Claro u Oscuro.


0

Esta pregunta tiene muchas respuestas, en lugar de usarla info.plistpuedes configurarla AppDelegateasí:

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

Prueba en Xcode 11.3, iOS 13.3


-8

En realidad, acabo de escribir un código que le permitirá optar globalmente por el modo oscuro en el código sin tener que usar putz con cada controlador viw de su aplicación. Esto probablemente se puede refinar para optar por una clase por clase administrando una lista de clases. Para mí, lo que quiero es que mis usuarios vean si les gusta la interfaz de modo oscuro para mi aplicación, y si no les gusta, pueden desactivarla. Esto les permitirá continuar usando el modo oscuro para el resto de sus aplicaciones.

La elección del usuario es buena (ejem, mirándote Apple, así es como deberías haberlo implementado).

Entonces, cómo funciona esto es que es solo una categoría de UIViewController. Cuando se carga, reemplaza el método viewDidLoad nativo con uno que verificará un indicador global para ver si el modo oscuro está desactivado para todo o no.

Debido a que se activa al cargar UIViewController, debería iniciarse automáticamente y deshabilitar el modo oscuro de forma predeterminada. Si esto no es lo que quieres, entonces debes llegar temprano a algún lugar y configurar la bandera, o simplemente configurar la bandera predeterminada.

Todavía no he escrito nada para responder al usuario que activa o desactiva la bandera. Entonces este es básicamente un código de ejemplo. Si queremos que el usuario interactúe con esto, todos los controladores de vista deberán volver a cargarse. No sé cómo hacer eso de forma espontánea, pero probablemente enviar alguna notificación será suficiente. Así que en este momento, este encendido / apagado global para el modo oscuro solo funcionará al iniciar o reiniciar la aplicación.

Ahora, no es suficiente tratar de desactivar el modo oscuro en cada MFING viewController en su gran aplicación. Si está utilizando activos de color, está completamente deshuesado. Por más de 10 años hemos entendido que los objetos inmutables son inmutables. Los colores que obtiene del catálogo de activos de color dicen que son UIColor pero son colores dinámicos (mutables) y cambiarán debajo de usted a medida que el sistema cambie del modo oscuro al claro. Se supone que eso es una característica. Pero, por supuesto, no hay una palanca maestra para pedirle a estas cosas que dejen de hacer este cambio (por lo que sé en este momento, tal vez alguien pueda mejorar esto).

Entonces la solución está en dos partes:

  1. una categoría pública en UIViewController que ofrece algunos métodos de utilidad y conveniencia ... por ejemplo, no creo que Apple haya pensado en el hecho de que algunos de nosotros mezclamos código web en nuestras aplicaciones. Como tal, tenemos hojas de estilo que deben alternarse según el modo oscuro o claro. Por lo tanto, necesita construir algún tipo de objeto de hoja de estilo dinámico (lo que sería bueno) o simplemente preguntar cuál es el estado actual (malo pero fácil).

  2. esta categoría cuando se carga reemplazará el método viewDidLoad de la clase UIViewController e interceptará llamadas. No sé si eso rompe las reglas de la tienda de aplicaciones. Si lo hace, probablemente hay otras formas de evitarlo, pero puede considerarlo una prueba de concepto. Por ejemplo, puede crear una subclase de todos los tipos de controladores de vista principales y hacer que todos sus propios controladores de vista hereden de ellos, y luego puede usar la idea de categoría DarkMode y llamarla para forzar la exclusión voluntaria de todos sus controladores de vista. Es más feo pero no va a romper ninguna regla. Prefiero usar el tiempo de ejecución porque para eso se hizo el tiempo de ejecución. Entonces, en mi versión, solo agrega la categoría, establece una variable global en la categoría para saber si desea o no bloquear el modo oscuro, y lo hará.

  3. Todavía no está fuera de peligro, como se mencionó, el otro problema es que UIColor básicamente hace lo que quiera. Entonces, incluso si sus controladores de vista están bloqueando el modo oscuro, UIColor no sabe dónde o cómo lo está utilizando, por lo que no puede adaptarse. Como resultado, puede buscarlo correctamente, pero luego volverá sobre usted en algún momento en el futuro. Tal vez pronto tal vez más tarde. Entonces, la forma de evitarlo es asignándolo dos veces usando un CGColor y convirtiéndolo en un color estático. Esto significa que si su usuario regresa y vuelve a habilitar el modo oscuro en su página de configuración (la idea aquí es hacer que esto funcione para que el usuario tenga control sobre su aplicación más allá del resto del sistema), todos esos colores estáticos necesita ser reemplazado Hasta ahora, esto queda para que alguien más lo resuelva. La manera más fácil de hacerlo es establecer un valor predeterminado que optando por salir del modo oscuro, divida entre cero para bloquear la aplicación, ya que no puede salir de ella y dígale al usuario que simplemente la reinicie. Eso probablemente también viola las pautas de la tienda de aplicaciones, pero es una idea.

La categoría UIColor no necesita exponerse, solo funciona llamando a colorNamed: ... si no le dijo a la clase DarkMode ViewController que bloquee el modo oscuro, funcionará perfectamente como se esperaba. Intentar hacer algo elegante en lugar del código estándar de sphaghetti de Apple, lo que significa que tendrá que modificar la mayor parte de su aplicación si desea optar programáticamente por el modo oscuro o alternarlo. Ahora no sé si hay una mejor manera de alterar programáticamente Info.plist para desactivar el modo oscuro según sea necesario. Hasta donde tengo entendido, esa es una característica de tiempo de compilación y después de eso estás deshuesado.

Así que aquí está el código que necesitas. Debe aparecer y usar el único método para establecer el estilo de la interfaz de usuario o establecer el valor predeterminado en el código. Usted es libre de usar, modificar, hacer lo que quiera con esto para cualquier propósito y no se otorga ninguna garantía y no sé si pasará la tienda de aplicaciones. Mejoras muy bienvenidas.

Advertencia justa No uso ARC ni ningún otro método de retención.

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

Hay un conjunto de funciones de utilidad que esto usa para realizar el intercambio de métodos. Archivo separado. Sin embargo, esto es algo estándar y puedes encontrar código similar en cualquier 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;
    }
}

Estoy copiando y pegando esto de un par de archivos ya que q-runtime.h es mi biblioteca reutilizable y esto es solo una parte de ella. Si algo no se compila, hágamelo saber.


No tiene mala suerte cuando se trata de controlar el comportamiento de UIColor, como se discutió en esta pregunta: stackoverflow.com/questions/56487679/…
raven_raven
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.