Czy można zrezygnować z trybu ciemnego na iOS 13?

297

Duża część mojej aplikacji składa się z widoków internetowych, które zapewniają funkcjonalność niedostępną jeszcze przez implementacje natywne. Zespół internetowy nie planuje zaimplementować mrocznego motywu dla witryny. W związku z tym moja aplikacja będzie wyglądać trochę na pół / na pół z obsługą trybu ciemnego na iOS 13.

Czy można zrezygnować z obsługi trybu ciemnego, aby nasza aplikacja zawsze wyświetlała tryb światła pasujący do motywu witryny?

SeanR
źródło
71
Ustaw UIUserInterfaceStylena Lightw swojej Info.Plist. Zobacz developer.apple.com/library/archive/documentation/General/…
Tieme,
1
Dziękujemy za pytanie - dla nas wszystkich. Wiele aplikacji do przejścia. Jest to potrzebne, aby aplikacje działały, dopóki przełącznik nie będzie gotowy.
user3741598,
import Foundation import rozszerzenie UIKit UIViewController {przesłanianie otwórz func awakeFromNib () {super.awakeFromNib () jeśli #available (iOS 13.0, *) {// Zawsze przyjmuj lekki interfejs. overrideUserInterfaceStyle = .light}}}
Mohammad Razipour
1
po prostu dodaj UIUserInterfaceStyle w plist. to takie proste
Fattie,
Podczas przesyłania aplikacji do AppStore, Apple akceptuje ze względu na UIUserInterfaceStyle w trybie Light.
kiran

Odpowiedzi:

684

Po pierwsze, oto wpis Apple związany z rezygnacją z trybu ciemnego. Treść pod tym linkiem jest napisana dla Xcode 11 i iOS 13 :

Ta sekcja dotyczy użytkowania Xcode 11


Jeśli chcesz zrezygnować z CAŁEGO wniosku

Podejście nr 1

Użyj następującego klucza w pliku info.plist :

UIUserInterfaceStyle

I przypisz mu wartość Light.

Kod XML dla UIUserInterfaceStylezadania:

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

Podejście nr 2

Możesz ustawić overrideUserInterfaceStylena podstawie windowzmiennej aplikacji .

W zależności od sposobu utworzenia projektu może to być AppDelegateplik lub plik SceneDelegate.

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


Jeśli chcesz zrezygnować z UIViewController indywidualnie

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

Dokumentacja Apple dla overrideUserInterfaceStyle

Jak będzie wyglądał powyższy kod w Xcode 11:

wprowadź opis zdjęcia tutaj

Ta sekcja dotyczy użytkowania Xcode 10.x.


Jeśli używasz Xcode 11 do przesyłania, możesz bezpiecznie zignorować wszystko poniżej tego wiersza.

Ponieważ odpowiedni interfejs API nie istnieje w systemie iOS 12, podczas próby użycia powyższych wartości wystąpią błędy:

Do ustawienia overrideUserInterfaceStylew swoimUIViewController

wprowadź opis zdjęcia tutaj

Jeśli chcesz zrezygnować z UIViewController indywidualnie

Można to obsłużyć w Xcode 10, testując wersję kompilatora i wersję iOS:

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

Jeśli chcesz zrezygnować z CAŁEGO wniosku

Możesz zmodyfikować powyższy fragment kodu, aby działał z całą aplikacją dla Xcode 10, dodając następujący kod do swojego AppDelegatepliku.

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

Jednak ustawienie Plist nie powiedzie się, gdy używasz Xcode w wersji 10.x:

wprowadź opis zdjęcia tutaj

Podziękowania dla @Aron Nelson , @Raimundas Sakalauskas , @NSLeader i rmaddy za ulepszenie tej odpowiedzi za pomocą ich opinii.

CodeBender
źródło
2
UIUserInterfaceStyle światło jest blokowane podczas aktualizowania / przesyłania aplikacji teraz. Zostaje oznaczony jako nieprawidłowy wpis Plist. (Nieprawidłowy klucz Plist)
Aron Nelson
2
To nie zostanie skompilowane z iOS SDK 12 (obecnie najnowszym stabilnym SDK). Zobacz stackoverflow.com/a/57521901/2249485, aby znaleźć rozwiązanie, które będzie działać również z iOS 12 SDK.
Raimundas Sakalauskas
Jest to tak niesprawiedliwe, że pytanie, które ma o wiele więcej wyświetleń niż „pytanie pierwotne”, jest zablokowane dla udzielania odpowiedzi. :(
Raimundas Sakalauskas
7
Zamiast ustawiać overrideUserInterfaceStylew viewDidLoadkażdej kontrolera widoku, można ustawić go raz w głównym oknie aplikacji. O wiele łatwiej, jeśli chcesz, aby cała aplikacja zachowywała się w jeden sposób.
rmaddy,
2
#if compiler(>=5.1)Zamiast tego użyj responds(to:)isetValue
NSLeader
162

Według sesji Apple „Wprowadzenie ciemnościach MODE na iOS” ( https://developer.apple.com/videos/play/wwdc2019/214/ poczynając od 31:13) możliwe jest zestaw overrideUserInterfaceStyledo UIUserInterfaceStyleLightlub UIUserInterfaceStyleDarkna dowolnym kontrolerze widoku lub widoku , które będą używane wtraitCollection dowolnym kontrolerze widoku podrzędnego lub widoku.

Jak już wspomniano przez SeanR można ustawić UIUserInterfaceStylena Lightlub Darkw pliku plist Twojej aplikacji, aby zmienić ten dla całej aplikacji.

dorbeetle
źródło
17
Jeśli ustawisz klucz UIUserInterfaceStyle, Twoja aplikacja zostanie odrzucona w App Store
Sonius,
2
Apple odrzuciło kod błędu ITMS-90190 forums.developer.apple.com/thread/121028
PRASAD1240,
11
Odrzucenie najprawdopodobniej nastąpi, ponieważ pakiet iOS 13 SDK nie jest jeszcze w wersji beta. Myślę, że powinno to działać, gdy tylko Xcode 11 GM będzie dostępny.
dorbeetle,
2
@dorbeetle to nieprawda, przesłałem aplikację z tym kluczem pomyślnie, jak 1 miesiąc temu za pomocą Xcode 10. Odrzucenia nastąpiły niedawno. Wydaje się, że jest to nowa strategia Apple.
Steven
1
To się wciąż dzieje. Xcode GM2 zwrócił błąd podpisywania aplikacji. Xcode 10.3 zwrócił: „Nieprawidłowy klucz Info.plist. Klucz„ UIUserInterfaceStyle ”w pliku ładunku / Galileo.appInfo.plist jest nieprawidłowy.”
Evgen Bodunov,
64

Jeśli nie używasz Xcode 11 lub nowszego (tj. IOS 13 lub nowszego SDK), twoja aplikacja nie zdecydowała się automatycznie na obsługę trybu ciemnego. Dlatego nie ma potrzeby rezygnacji z trybu ciemnego.

Jeśli używasz Xcode 11 lub nowszego, system automatycznie włączył tryb ciemny dla Twojej aplikacji. Istnieją dwa sposoby wyłączenia trybu ciemnego w zależności od preferencji. Możesz go całkowicie wyłączyć lub wyłączyć dla dowolnego określonego okna, widoku lub kontrolera widoku.

Wyłącz tryb ciemny całkowicie dla swojej aplikacji

Możesz wyłączyć tryb ciemny, włączając UIUserInterfaceStyleklucz o wartości jak Lightw pliku Info.plist aplikacji. Ignoruje to preferencje użytkownika i zawsze nadaje aplikacji lekki wygląd.
UIUserInterfaceStyle as Light

Wyłącz tryb ciemny dla Window, View lub View Controller

Możesz wymusić wyświetlanie interfejsu zawsze w jasnym lub ciemnym stylu, ustawiając overrideUserInterfaceStylewłaściwość odpowiedniego okna, widoku lub kontrolera widoku.

Wyświetl kontrolery:

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

Wyświetlenia:

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

Okno:

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

Uwaga: Apple zdecydowanie zachęca do obsługi trybu ciemnego w Twojej aplikacji. Możesz więc tymczasowo wyłączyć tryb ciemny.

Przeczytaj więcej tutaj: Wybieranie określonego stylu interfejsu dla aplikacji na iOS

Ajith R Nayak
źródło
34

********** Najłatwiejszy sposób dla Xcode 11 i nowszych ***********

Dodaj to wcześniej do info.plist </dict></plist>

<key>UIUserInterfaceStyle</key>
<string>Light</string>
Kingsley Mitchell
źródło
to rozwiązanie zawiedzie po przesłaniu aplikacji na Xcode 10.x
Tawfik Bouabid
27

Myślę, że znalazłem rozwiązanie. Początkowo poskładałem go razem z UIUserInterfaceStyle - Lista właściwości informacji i UIUserInterfaceStyle - UIKit , ale teraz znalazłem to faktycznie udokumentowane przy wyborze konkretnego stylu interfejsu dla aplikacji na iOS .

W swoim info.plistustaw UIUserInterfaceStyle( Styl interfejsu użytkownika ) na 1 ( UIUserInterfaceStyle.light).

EDYCJA: Zgodnie z odpowiedzią dorbeetle UIUserInterfaceStylemoże być bardziej odpowiednie ustawienie Light.

SeanR
źródło
Wymuszanie trybu ciemnego poprzez ustawienie wartości na 2 nie działa jednak:[UIInterfaceStyle] '2' is not a recognized value for UIUserInterfaceStyle. Defaulting to Light.
funkenstrahlen
3
Posiadanie tego klucza na liście spowoduje odrzucenie App Store.
José
1
AppStore nie odrzuca już tej właściwości w plist.info. Wpisuję „Dark” (wielkie litery), ponieważ nasza aplikacja jest już ciemna. Bez problemów. To właściwie pozwala nam korzystać z kontroli systemu.
nickdnk
@nickdnk Myślę, że zbudowałeś swoją aplikację za pomocą Xcode 11, który jest zalecany przez Apple.
DawnSong
1
Tak. Nie zmienia to faktu, że Apple akceptuje ten parametr na liście, co starałem się wyjaśnić.
nickdnk
23

Powyższa odpowiedź działa, jeśli chcesz zrezygnować z całej aplikacji. Jeśli pracujesz nad biblioteką z interfejsem użytkownika i nie masz luksusu w edytowaniu .plist, możesz to zrobić również za pomocą kodu.

Jeśli kompilujesz przy użyciu zestawu SDK dla systemu iOS 13, możesz po prostu użyć następującego kodu:

Szybki:

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

Obj-C:

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

JEDNAK , jeśli chcesz, aby Twój kod kompilował się z iOS 12 SDK (który jest obecnie najnowszym stabilnym SDK), powinieneś skorzystać z selektorów. Kod z selektorami:

Swift (XCode wyświetli ostrzeżenia dla tego kodu, ale na razie jest to jedyny sposób, ponieważ właściwość nie istnieje w zestawie SDK 12, dlatego nie zostanie skompilowana):

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
źródło
Lepiej będzie, jeśli określisz, do której własności overrideUserInterfaceStylenależy ta właściwość .
DawnSong
12

Najnowsza aktualizacja-

Jeśli używasz Xcode 10.x, domyślnie UIUserInterfaceStylejest to lightiOS 13.x. Po uruchomieniu na urządzeniu z systemem iOS 13 będzie działać tylko w trybie światła.

Nie trzeba jawnie dodawać UIUserInterfaceStyleklucza w pliku Info.plist, ponieważ dodanie go spowoduje błąd podczas sprawdzania poprawności aplikacji, mówiąc:

Nieprawidłowy klucz Info.plist. Klucz „UIUserInterfaceStyle” w pliku Payload / AppName.appInfo.plist jest nieprawidłowy.

Dodaj tylko UIUserInterfaceStyleklucz w pliku Info.plist, gdy używasz Xcode 11.x.

kumarsiddharth123
źródło
1
Nie ma to nic wspólnego z Xcode 10 lub 11. Jeśli użytkownik wdroży aplikację z Xcode 10 i nie zajmie się trybem ciemnym, aplikacja zainstalowana na iPhonie 11, Pro lub Pro Max będzie miała problemy z trybem ciemnym. musisz zaktualizować Xcode 11 i rozwiązać ten problem.
Niranjan Molkeri
3
@NiranjanMolkeri Nie ma to nic wspólnego z nowszymi iPhone'ami. To dotyczy trybu ciemnego na iOS 13. W poprzedniej wersji beta aplikacji iOS 13 interfejs użytkownika miał problemy z trybem ciemnym, jeśli nie zostały wyraźnie potraktowane. Ale w najnowszej wersji jest to naprawione. Jeśli używasz XCode 10, domyślny UIUserInterfaceStyle jest lekki dla iOS13. Jeśli używasz Xode11, musisz sobie z tym poradzić.
kumarsiddharth123
Będziesz mieć problemy, jeśli prześlesz aplikację do TestFligth za pomocą Xcode 10.3, a lista zawiera klucz UIUserInterfaceStyle. Powie, że jest to nieprawidłowy plik plist. Musisz go usunąć, jeśli
budujesz
9

Jeśli dodasz UIUserInterfaceStyleklucz do pliku plist, być może Apple odrzuci kompilację wydania, jak wspomniano tutaj: https://stackoverflow.com/a/56546554/7524146 W każdym razie denerwujące jest jawne informowanie każdego kontrolera ViewController self.overrideUserInterfaceStyle = .light . Ale możesz użyć tego spokoju kodu dla swojego windowobiektu głównego :

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

Zauważ, że nie możesz tego zrobić wewnątrz, application(application: didFinishLaunchingWithOptions:)ponieważ ten selektor nie zareaguje truena tym wczesnym etapie. Ale możesz to zrobić później. To bardzo proste, jeśli używasz niestandardowego AppPresenterlub AppRouterzajęć w swojej aplikacji zamiast automatycznego uruchamiania interfejsu użytkownika w AppDelegate.

SerhiiK
źródło
9

Możesz wyłączyć tryb ciemny w całej aplikacji w Xcode 11:

  1. Przejdź do Info.plist
  2. Dodaj poniżej

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

Info.plist będzie wyglądać jak poniżej ...

wprowadź opis zdjęcia tutaj

Enamul Haque
źródło
1
z jakiegoś powodu nie działa z Xcode w wersji 11.3.1 (11C504)
Andrew
7

- Dla całej aplikacji (okno):

window!.overrideUserInterfaceStyle = .light

Możesz uzyskać okno z SceneDelegate

- W przypadku pojedynczego kontrolera ViewController:

viewController.overrideUserInterfaceStyle = .light

Można ustawić dowolny viewController, nawet wewnątrz viewController nim siebie

- W przypadku pojedynczego widoku:

view.overrideUserInterfaceStyle = .light

Możesz ustawić dowolny view, nawet wewnątrz widoku, który sam

Może być konieczne użycie, if #available(iOS 13.0, *) { ,,, }jeśli obsługujesz wcześniejsze wersje iOS.

Mojtaba Hosseini
źródło
6

Oprócz innych odpowiedzi, z mojego rozumienia następujących rzeczy, musisz tylko przygotować się do trybu ciemnego podczas kompilacji z pakietem iOS 13 SDK (używając XCode 11).

System zakłada, że ​​aplikacje połączone z zestawem SDK iOS 13 lub nowszym obsługują zarówno jasne, jak i ciemne wyglądy. W iOS określasz pożądany wygląd, przypisując określony styl interfejsu do okna, widoku lub kontrolera widoku. Możesz także całkowicie wyłączyć obsługę trybu ciemnego za pomocą klucza Info.plist.

Połączyć

Claudio
źródło
2

Tak, możesz pominąć dodając następujący kod w viewDidLoad:

if #available(iOS 13.0, *) {
        // Always adopt a light interface style.
        overrideUserInterfaceStyle = .light
    }
Muhammad Naeem Paracha
źródło
2

Moja aplikacja nie obsługuje obecnie trybu ciemnego i używa jasnego koloru paska aplikacji. Byłem w stanie zmusić zawartość paska stanu do ciemnego tekstu i ikon, dodając następujący klucz do mojego Info.plist:

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

Znajdź inne możliwe wartości tutaj: https://developer.apple.com/documentation/uikit/uistatusbarstyle

ToniTornado
źródło
2

Wersja C celu

 if (@available(iOS 13.0, *)) {
        _window.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
    }
ahbou
źródło
1

Oto kilka porad i wskazówek, których możesz użyć w swojej aplikacji do obsługi lub obejścia trybu ciemności.

Pierwsza wskazówka: aby zastąpić styl ViewController

możesz zastąpić styl interfejsu UIViewController przez

1: overrideUserInterfaceStyle = .dark // Dla trybu ciemnego

2: overrideUserInterfaceStyle = .light // Dla trybu światła

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

Druga wskazówka: dodawanie klucza w info.plist

Po prostu możesz dodać nowy klucz

UIUserInterfaceStyle

w aplikacji info.plist i ustaw jego wartość na Jasny lub Ciemny. spowoduje to zastąpienie domyślnego stylu aplikacji do podanej wartości. Nie musisz dodawać overrideUserInterfaceStyle = .light tej linii w każdym viewController, tylko jedna linia w info.plist to jest to.

Mohammed Ebrahim
źródło
1

Wystarczy dodać następujący klucz do info.plistpliku:

<key>UIUserInterfaceStyle</key>
    <string>Light</string>
Moeen Ahmad
źródło
1
 if #available(iOS 13.0, *) {
            overrideUserInterfaceStyle = .light
        } else {
            // Fallback on earlier versions
        }
Talha Rasool
źródło
Czy możesz wyjaśnić trochę, w jaki sposób ta odpowiedź rozwiąże problem, zamiast publikowania odpowiedzi tylko w kodzie.
Arun Vinoth,
Tak, na pewno @ArunVinoth W IOS 13 wprowadzono tryb ciemny, więc jeśli twój cel wdrożenia jest niższy niż 13, użyj powyższego kodu, w przeciwnym razie możesz użyć prostej instrukcji napisanej w bloku if.
Talha Rasool
1

Szybki 5

Dwa sposoby przełączania trybu ciemnego na jasny:

1- info.plist

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

2- Programowo

 UIApplication.shared.windows.forEach { window in
     window.overrideUserInterfaceStyle = .light
  } 
Rashid Latif
źródło
0

Chciałbym skorzystać z tego rozwiązania, ponieważ właściwość okna może zostać zmieniona podczas cyklu życia aplikacji. Dlatego przypisanie „overrideUserInterfaceStyle = .light” musi zostać powtórzone. UIWindow.appearance () pozwala nam ustawić wartość domyślną, która będzie używana dla nowo tworzonych obiektów 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
    }
}
Dmitry
źródło
0

Wystarczy dodać tę linię w pliku info.plist:

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

To zmusi aplikację do działania tylko w trybie lekkim.

Rahul Gusain
źródło
To było już komentowane i udzielano odpowiedzi wiele razy. Sugeruje to nawet przyjęta odpowiedź. Dlatego ten komentarz nie dodaje żadnych nowych informacji.
JeroenJK,
0
import UIKit

extension UIViewController {

    override open func awakeFromNib() {

        super.awakeFromNib()

        if #available(iOS 13.0, *) {

            overrideUserInterfaceStyle = .light

        }

    }
}
Mohammad Razipour
źródło
2
Dodaj wyjaśnienie do swojej odpowiedzi, edytując je, aby inni mogli się z niego uczyć
Nico Haase,
0

Możesz: dodać ten nowy klucz UIUserInterfaceStyle do Info.plist i ustawić jego wartość na Light. i sprawdź, czy kontroler alertów pojawia się w trybie światła.

UIUserInterfaceStyle Light Jeśli wymuszasz tryb jasnego / ciemnego w całej aplikacji, niezależnie od ustawień użytkownika, dodając klucz UIUserInterfaceStyle do pliku Info.plist i ustawiając jego wartość na Jasną lub Ciemną.

Hominda
źródło
0

To pytanie ma tak wiele odpowiedzi, raczej używając go info.plistmożesz ustawić w AppDelegatenastępujący sposób:

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

Test na Xcode 11.3, iOS 13.3

Niraj
źródło
-8

Właściwie właśnie napisałem kod, który pozwoli ci globalnie zrezygnować z trybu ciemnego w kodzie bez konieczności instalowania każdego kontrolera viw w twojej aplikacji. Prawdopodobnie można to poprawić, rezygnując z poszczególnych klas, zarządzając listą klas. Dla mnie chcę, aby użytkownicy zobaczyli, czy podoba im się interfejs trybu ciemnego dla mojej aplikacji, a jeśli im się to nie podoba, mogą go wyłączyć. Pozwoli im to kontynuować korzystanie z trybu ciemnego dla pozostałych aplikacji.

Wybór użytkownika jest dobry (Ahem, patrząc na ciebie Apple, tak powinieneś był go wdrożyć).

Jak to działa, to tylko kategoria UIViewController. Podczas ładowania zastępuje natywną metodę viewDidLoad taką, która sprawdzi flagę globalną, aby sprawdzić, czy tryb ciemny jest wyłączony dla wszystkich, czy nie.

Ponieważ jest on uruchamiany podczas ładowania UIViewController, powinien automatycznie uruchomić się i domyślnie wyłączyć tryb ciemny. Jeśli to nie jest to, czego potrzebujesz, musisz wcześniej gdzieś tam wejść i ustawić flagę, albo po prostu ustawić flagę domyślną.

Nie napisałem jeszcze nic, by odpowiedzieć na to, że użytkownik włącza lub wyłącza flagę. Jest to w zasadzie przykładowy kod. Jeśli chcemy, aby użytkownik z tym współdziałał, wszystkie kontrolery widoku będą musiały się ponownie załadować. Nie wiem, jak to zrobić odręcznie, ale prawdopodobnie wysłanie jakiegoś powiadomienia załatwi sprawę. Tak więc teraz to globalne włączanie / wyłączanie dla trybu ciemnego będzie działać tylko podczas uruchamiania lub ponownego uruchamiania aplikacji.

Teraz nie wystarczy spróbować wyłączyć tryb ciemny w każdym widoku kontrolera MFING w swojej ogromnej aplikacji. Jeśli używasz zasobów kolorów, jesteś całkowicie bez kości. Od ponad 10 lat rozumiemy niezmienne obiekty jako niezmienne. Kolory, które otrzymujesz z katalogu zasobów kolorów, mówią, że są to UIColor, ale są to kolory dynamiczne (zmienne) i będą się zmieniać pod tobą, gdy system zmieni się z trybu ciemnego na jasny. To ma być funkcja. Ale oczywiście nie ma przełącznika głównego, który prosi te rzeczy o zaprzestanie wprowadzania tej zmiany (o ile wiem teraz, może ktoś może to poprawić).

Rozwiązanie składa się z dwóch części:

  1. kategoria publiczna na UIViewController, która daje pewne metody użyteczności i wygody ... na przykład nie sądzę, że Apple pomyślał o tym, że niektórzy z nas mieszają kod internetowy z naszymi aplikacjami. Jako takie mamy arkusze stylów, które należy przełączać w oparciu o tryb ciemny lub jasny. Dlatego musisz albo zbudować jakiś dynamiczny obiekt arkusza stylów (co byłoby dobre), albo po prostu zapytać, jaki jest obecny stan (zły, ale łatwy).

  2. ta kategoria podczas ładowania zastąpi metodę viewDidLoad klasy UIViewController i przechwytuje połączenia. Nie wiem, czy to łamie zasady sklepu z aplikacjami. Jeśli tak, prawdopodobnie istnieją inne sposoby, ale można to uznać za dowód koncepcji. Możesz na przykład utworzyć jedną podklasę wszystkich głównych typów kontrolerów widoku i sprawić, że wszystkie własne kontrolery widoku odziedziczą po nich, a następnie możesz użyć idei kategorii DarkMode i wywołać ją, aby wymusić rezygnację ze wszystkich kontrolerów widoku. Jest brzydszy, ale nie złamie żadnych zasad. Wolę używać środowiska wykonawczego, ponieważ właśnie do tego zostało stworzone środowisko wykonawcze. Więc w mojej wersji po prostu dodajesz kategorię, ustawiasz zmienną globalną na kategorię, czy chcesz, aby blokowała tryb ciemny, i zrobi to.

  3. Jak już wspomniano, jeszcze nie wyszedłeś z lasu, innym problemem jest to, że UIColor zasadniczo robi wszystko, do diabła, czego chce. Więc nawet jeśli kontrolery widoku blokują tryb ciemny, UIColor nie wie, gdzie i jak go używasz, więc nie można go dostosować. W rezultacie możesz go poprawnie pobrać, ale w pewnym momencie wróci do ciebie. Może wkrótce może później. Tak więc można to zrobić, przydzielając go dwukrotnie za pomocą CGColor i zmieniając go w kolor statyczny. Oznacza to, że jeśli użytkownik wróci i ponownie włączy tryb ciemny na stronie ustawień (chodzi o to, aby działał tak, aby użytkownik miał kontrolę nad aplikacją ponad resztą systemu), wszystkie te statyczne kolory wymagają wymiany. Jak dotąd pozostaje to do rozwiązania przez kogoś innego. Najłatwiejszym sposobem na zrobienie tego jest ustawienie domyślnej wartości „ wyłączając tryb ciemny, podziel przez zero, aby zawiesić aplikację, ponieważ nie możesz jej zamknąć i powiedzieć użytkownikowi, aby po prostu zrestartował. To prawdopodobnie narusza również wytyczne sklepu z aplikacjami, ale to pomysł.

Kategoria UIColor nie musi być ujawniana, działa po prostu wywołując colorNamed: ... jeśli nie powiedziałeś klasie DarkMode ViewController, aby blokowała tryb ciemny, będzie działała idealnie zgodnie z oczekiwaniami. Próba zrobienia czegoś eleganckiego zamiast standardowego kodu sphaghetti z jabłkiem, co oznacza, że ​​będziesz musiał zmodyfikować większość aplikacji, jeśli chcesz programowo zrezygnować z trybu ciemnego lub go przełączyć. Teraz nie wiem, czy istnieje lepszy sposób na programową zmianę Info.plist, aby w razie potrzeby wyłączyć tryb ciemny. O ile dobrze rozumiem, jest to funkcja czasu kompilacji, a następnie jesteś bez kości.

Oto kod, którego potrzebujesz. Powinieneś wpaść i po prostu użyć jednej metody, aby ustawić styl interfejsu użytkownika lub ustawić wartość domyślną w kodzie. Możesz dowolnie używać, modyfikować, robić, co chcesz z tym do dowolnego celu i nie udziela się gwarancji, a ja nie wiem, czy przejdzie do sklepu z aplikacjami. Ulepszenia bardzo mile widziane.

Uczciwe ostrzeżenie Nie używam ARC ani innych metod trzymania w ręku.

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

Istnieje zestaw funkcji narzędziowych, których używa do wykonywania wymiany metod. Oddzielny plik. Jest to jednak standard i podobny kod można znaleźć w dowolnym miejscu.

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

Kopiuję i wklejam to z kilku plików, ponieważ q-runtime.h jest moją biblioteką wielokrotnego użytku i to tylko część tego. Jeśli coś się nie kompiluje, daj mi znać.

dbquarrel
źródło
Nie masz pecha, jeśli chodzi o kontrolowanie zachowania UIColor, jak omówiono w tym pytaniu: stackoverflow.com/questions/56487679/…
raven_raven