Sebenarnya saya hanya menulis beberapa kode yang akan memungkinkan Anda untuk secara global memilih keluar dari mode gelap dalam kode tanpa harus memasang setiap pengontrol viw tunggal dalam aplikasi Anda. Ini mungkin dapat disempurnakan untuk memilih keluar dari kelas berdasarkan kelas dengan mengelola daftar kelas. Bagi saya, yang saya inginkan adalah agar pengguna melihat apakah mereka menyukai antarmuka mode gelap untuk aplikasi saya, dan jika mereka tidak menyukainya, mereka dapat mematikannya. Ini akan memungkinkan mereka untuk terus menggunakan mode gelap untuk sisa aplikasi mereka.
Pilihan pengguna baik (Ahem, melihat Anda Apple, ini adalah bagaimana Anda seharusnya menerapkannya).
Jadi cara kerjanya adalah hanya kategori UIViewController. Ketika dimuat itu menggantikan metode viewDidLoad asli dengan yang akan memeriksa bendera global untuk melihat apakah mode gelap dinonaktifkan untuk semuanya atau tidak.
Karena dipicu pada pemuatan UIViewController maka secara otomatis akan memulai dan menonaktifkan mode gelap secara default. Jika ini bukan yang Anda inginkan, maka Anda harus masuk ke suatu tempat lebih awal dan mengatur bendera, atau hanya mengatur bendera default.
Saya belum menulis apa pun untuk menanggapi pengguna mengaktifkan atau menonaktifkan bendera. Jadi ini pada dasarnya adalah contoh kode. Jika kami ingin pengguna berinteraksi dengan ini, semua pengontrol tampilan perlu memuat ulang. Saya tidak tahu bagaimana melakukan itu begitu saja, tetapi mungkin mengirimkan beberapa pemberitahuan akan berhasil. Jadi saat ini, mode on / off global ini untuk mode gelap hanya akan berfungsi saat startup atau restart aplikasi.
Sekarang, itu tidak hanya cukup untuk mencoba mematikan mode gelap di setiap viewController MFING tunggal di aplikasi besar Anda. Jika Anda menggunakan aset warna, Anda sepenuhnya bertulang. Kami selama 10+ tahun telah memahami objek yang tidak dapat berubah menjadi tidak berubah. Warna yang Anda dapatkan dari katalog aset warna mengatakan bahwa itu adalah UIColor tetapi warnanya dinamis (bisa berubah) dan akan berubah di bawah Anda saat sistem berubah dari mode gelap ke terang. Itu seharusnya menjadi fitur. Tetapi tentu saja tidak ada master beralih untuk meminta hal-hal ini berhenti membuat perubahan ini (sejauh yang saya tahu sekarang, mungkin seseorang dapat meningkatkan ini).
Jadi solusinya ada dalam dua bagian:
kategori publik di UIViewController yang memberikan beberapa metode utilitas dan kenyamanan ... misalnya saya tidak berpikir apple telah memikirkan fakta bahwa sebagian dari kita mencampur kode web ke dalam aplikasi kita. Karena itu kami memiliki stylesheet yang perlu diubah berdasarkan mode gelap atau terang. Jadi, Anda perlu membangun semacam objek stylesheet dinamis (yang akan baik) atau hanya bertanya seperti apa keadaan saat ini (buruk tapi mudah).
kategori ini saat dimuat akan menggantikan metode viewDidLoad dari kelas UIViewController dan mencegat panggilan. Saya tidak tahu apakah itu melanggar aturan toko aplikasi. Jika ya, ada beberapa cara lain yang mungkin tetapi Anda dapat menganggapnya sebagai bukti konsep. Misalnya Anda dapat membuat satu subkelas dari semua jenis pengontrol tampilan utama dan membuat semua pengontrol tampilan Anda mewarisi dari itu, dan kemudian Anda dapat menggunakan ide kategori DarkMode dan memanggilnya untuk memaksa memilih keluar dari semua pengontrol tampilan Anda. Itu lebih buruk tetapi tidak akan melanggar aturan apa pun. Saya lebih suka menggunakan runtime karena itulah yang harus dilakukan runtime. Jadi dalam versi saya, Anda hanya menambahkan kategori, Anda menetapkan variabel global pada kategori untuk apakah Anda ingin memblokir mode gelap, dan itu akan melakukannya.
Anda belum keluar dari hutan, seperti yang disebutkan, masalah lainnya adalah UIColor pada dasarnya melakukan apa pun yang diinginkannya. Jadi, bahkan jika pengontrol tampilan Anda memblokir mode gelap, UIColor tidak tahu di mana atau bagaimana Anda menggunakannya jadi tidak bisa beradaptasi. Sebagai hasilnya, Anda dapat mengambilnya dengan benar tetapi kemudian akan mengembalikan Anda di beberapa titik di masa depan. Mungkin sebentar lagi nanti. Jadi cara mengatasinya adalah dengan mengalokasikannya dua kali menggunakan CGColor dan mengubahnya menjadi warna statis. Ini berarti jika pengguna Anda kembali dan mengaktifkan kembali mode gelap pada halaman pengaturan Anda (ide di sini adalah untuk membuat ini berfungsi sehingga pengguna memiliki kontrol atas aplikasi Anda di atas dan di atas sisa sistem), semua warna-warna statis perlu diganti. Sejauh ini yang tersisa untuk dipecahkan orang lain. Cara mudah untuk melakukannya adalah dengan membuat default bahwa Anda kembali memilih keluar dari mode gelap, bagi dengan nol untuk crash aplikasi karena Anda tidak dapat keluar dan memberitahu pengguna untuk hanya me-restart itu. Itu mungkin melanggar pedoman toko aplikasi juga tapi itu ide.
Kategori UIColor tidak perlu diekspos, itu hanya berfungsi memanggil colorNamed: ... jika Anda tidak memberi tahu kelas DarkMode ViewController untuk memblokir mode gelap, itu akan berfungsi dengan baik seperti yang diharapkan. Mencoba membuat sesuatu yang elegan alih-alih kode sphaghetti apel standar yang berarti Anda harus memodifikasi sebagian besar aplikasi Anda jika Anda ingin secara sistematis memilih keluar dari mode gelap atau mengubahnya. Sekarang saya tidak tahu apakah ada cara yang lebih baik untuk secara sistematis mengubah Info.plist untuk mematikan mode gelap sesuai kebutuhan. Sejauh pemahaman saya itu adalah fitur waktu kompilasi dan setelah itu Anda boned.
Jadi di sini adalah kode yang Anda butuhkan. Harus drop in dan cukup gunakan satu metode untuk mengatur Gaya UI atau mengatur default dalam kode. Anda bebas untuk menggunakan, memodifikasi, melakukan apa pun yang Anda inginkan dengan tujuan ini dan tidak ada garansi yang diberikan dan saya tidak tahu apakah itu akan melewati app store. Perbaikan sangat disambut.
Peringatan yang adil saya tidak menggunakan ARC atau metode pegangan lainnya.
////// 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
Ada satu set fungsi utilitas yang digunakan untuk melakukan metode swapping. File terpisah. Ini adalah hal standar dan Anda dapat menemukan kode serupa di mana saja.
// 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;
}
}
Saya menyalin dan menempelkan ini dari beberapa file sejak q-runtime.h adalah pustaka yang dapat digunakan kembali dan ini hanya sebagian saja. Jika ada sesuatu yang tidak dikompilasi, beri tahu saya.
UIUserInterfaceStyle
keLight
dalam Info.Plist Anda. Lihat developer.apple.com/library/archive/documentation/General/…