Aslında sadece uygulamanızdaki her bir viw denetleyicisi ile putz zorunda kalmadan koddaki karanlık moddan çıkmanıza izin verecek bazı kodlar yazdım. Bu muhtemelen, bir sınıflar listesi yöneterek sınıf bazında sınıftan çıkarmayı seçebilir. Benim için istediğim, kullanıcılarımın uygulamanızın karanlık mod arayüzünü beğenip beğenmediklerini görmeleri ve beğenmedikleri takdirde kapatabilirler. Bu, uygulamalarının geri kalanı için karanlık modu kullanmaya devam etmelerini sağlayacaktır.
Kullanıcı seçimi iyidir (Ahem, Apple'a bakmak, bunu nasıl uygulamanız gerekir).
Bunun nasıl çalıştığı sadece bir UIViewController kategorisidir. Yüklendiğinde, yerel viewDidLoad yöntemini, karanlık modun her şey için devre dışı bırakılıp bırakılmadığını görmek için genel bayrağı kontrol edecek bir yöntemle değiştirir.
UIViewController yüklemesinde tetiklendiğinden, varsayılan olarak karanlık modu otomatik olarak başlatmalı ve devre dışı bırakmalıdır. İstediğiniz bu değilse, oraya erken bir yere girip bayrağı ayarlamanız veya varsayılan bayrağı ayarlamanız gerekir.
Bayrağı açıp kapatan kullanıcıya yanıt vermek için henüz bir şey yazmadım. Yani bu temelde örnek kod. Kullanıcının bununla etkileşime girmesini istiyorsak, tüm görünüm denetleyicilerinin yeniden yüklenmesi gerekir. Bunu nasıl hazırlıksız yapacağımı bilmiyorum ama muhtemelen bazı bildirim göndermek hile yapacak. Şu anda, karanlık mod için bu genel açma / kapama sadece uygulamanın başlangıcında veya yeniden başlatılmasında çalışacaktır.
Şimdi, büyük uygulamanızdaki her bir MFING viewController'da karanlık modu kapatmaya çalışmak yeterli değildir. Renk varlıkları kullanıyorsanız, tamamen kemiklersiniz. 10 yılı aşkın bir süredir değişmez cisimlerin değişmez olduğunu anladık. Renk varlığı kataloğundan aldığınız renkler, bunların UIColor olduğunu ancak dinamik (değiştirilebilir) renkler olduğunu ve sistem karanlıktan aydınlık moduna geçtikçe altınızda değişeceğini söylüyor. Bunun bir özellik olması gerekiyordu. Ama elbette, bu şeylerin bu değişikliği yapmayı bırakmasını istemek için usta bir geçiş yok (şu anda bildiğim kadarıyla, belki biri bunu geliştirebilir).
Yani çözüm iki bölümden oluşuyor:
UIViewController'da bazı yardımcı program ve kolaylık yöntemleri sunan genel bir kategori ... örneğin, apple'ın bazılarımızın web kodumuzu uygulamalarımıza karıştırması gerçeğini düşündüğünü sanmıyorum. Bu nedenle, karanlık veya açık moda göre değiştirilmesi gereken stil sayfalarımız var. Bu nedenle, bir tür dinamik stil sayfası nesnesi oluşturmalısınız (ki bu iyi olur) veya sadece mevcut durumun ne olduğunu sormanız gerekir (kötü ama kolay).
yüklendiğinde bu kategori UIViewController sınıfının viewDidLoad yönteminin yerini alır ve aramaları engeller. Bunun uygulama mağazası kurallarını ihlal edip etmediğini bilmiyorum. Eğer öyleyse, bunun etrafında muhtemelen başka yollar da vardır, ancak bunu bir kavram kanıtı olarak düşünebilirsiniz. Örneğin, tüm ana görünüm denetleyicisi türlerinden bir alt sınıf oluşturabilir ve kendi görünüm denetleyicilerinizin bunlardan devralmasını sağlayabilirsiniz ve daha sonra DarkMode kategori fikrini kullanabilir ve tüm görünüm denetleyicilerinizi devre dışı bırakmaya zorlayabilirsiniz. Bu daha çirkin ama kurallara aykırı olmayacak. Çalışma zamanını kullanmayı tercih ederim, çünkü çalışma zamanının yapılması budur. Yani benim versiyonumda sadece kategoriyi ekliyorsunuz, karanlık modu engellemesini isteyip istemediğinize dair bir global değişken belirlersiniz ve bunu yapar.
Henüz ormandan çıkmadınız, belirtildiği gibi, diğer sorun UIColor'un temelde istediği her şeyi yapmasıdır. Bu nedenle, görünüm denetleyicileriniz karanlık modu engelliyor olsa bile UIColor, nerede veya nasıl kullandığınızı bilmiyor, bu yüzden uyum sağlayamıyor. Sonuç olarak, doğru bir şekilde getirebilirsiniz, ancak gelecekte bir noktada size geri dönecektir. Belki yakında belki sonra. Bunun etrafındaki yol, bir CGColor kullanarak iki kez ayırarak ve statik bir renge dönüştürmektir. Bu, kullanıcı geri dönüp ayarlar sayfanızda karanlık modu yeniden etkinleştirirse (buradaki fikir, kullanıcının uygulamanız üzerinde sistemin geri kalanının üstünde ve üstünde kontrol sahibi olmasını sağlamak için bu işi yapmaktır), tüm bu statik renklerin değiştirilmesi gerekiyor. Şimdiye kadar bu başka birinin çözmesi için kaldı. Bunu yapmanın kolay yolu, bir varsayılan karanlık moddan çıkmayı seçerseniz, uygulamadan çıkmanız ve kullanıcıya yeniden başlatmasını söylemeniz nedeniyle uygulamayı çökertmek için sıfıra bölün. Bu muhtemelen uygulama mağazası yönergelerini de ihlal eder, ancak bu bir fikirdir.
UIColor kategorisinin açıklanması gerekmez, sadece colorNamed çağrılmasıyla çalışır: ... DarkMode ViewController sınıfına karanlık modu engellemesini söylemediyseniz, beklendiği gibi mükemmel bir şekilde çalışacaktır. Karanlık moddan programlı olarak çıkmak veya değiştirmek için uygulamanızın çoğunu değiştirmek zorunda kalacağınız anlamına gelen standart elma sphaghetti kodu yerine zarif bir şey yapmaya çalışın. Artık Info.plist'i karanlık modu gerektiği gibi kapatmak için programlı olarak değiştirmenin daha iyi bir yolu olup olmadığını bilmiyorum. Anladığım kadarıyla bu derleme zamanı özelliği ve bundan sonra kemikli.
İşte ihtiyacınız olan kod. Bırakın ve sadece UI Stilini ayarlamak veya kodda varsayılanı ayarlamak için bir yöntemi kullanın. Herhangi bir amaç için bunu kullanmak, değiştirmek, istediğinizi yapmakta özgürsünüz ve hiçbir garanti verilmemektedir ve uygulama mağazasını geçip geçmeyeceğini bilmiyorum. Gelişmeler çok hoş geldiniz.
Adil uyarı ARC veya başka bir tutma yöntemi kullanmıyorum.
////// 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
Bunun, yöntem değiştirme işlemi için kullandığı bir dizi yardımcı program işlevi vardır. Ayrı dosya. Bu standart şeyler olsa ve benzer kodu her yerde bulabilirsiniz.
// 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;
}
}
Kopyalama ve q-runtime.h yeniden kullanılabilir kütüphane ve bu sadece bir parçası beri birkaç dosyadan yapıştırma. Bir şey derlenmezse bana bildirin.
UIUserInterfaceStyle
içinLight
sizin info.plist içinde. Bkz. Developer.apple.com/library/archive/documentation/General/…