ที่จริงแล้วฉันเพิ่งเขียนโค้ดบางอย่างที่จะช่วยให้คุณสามารถยกเลิกโหมดมืดในโค้ดโดยไม่ต้อง putz กับคอนโทรลเลอร์ viw ทุกตัวในแอปพลิเคชันของคุณ สิ่งนี้อาจได้รับการขัดเกลาเพื่อเลือกไม่เข้าชั้นเรียนตามระดับชั้นเรียนโดยจัดการรายการของชั้นเรียน สำหรับฉันสิ่งที่ฉันต้องการคือให้ผู้ใช้ของฉันดูว่าพวกเขาชอบอินเทอร์เฟซโหมดมืดสำหรับแอพของฉันหรือไม่และถ้าพวกเขาไม่ชอบมันพวกเขาสามารถปิดได้ นี่จะช่วยให้พวกเขาใช้โหมดมืดต่อไปสำหรับแอปพลิเคชั่นที่เหลือ
ตัวเลือกของผู้ใช้เป็นสิ่งที่ดี (อะแฮ่มมองคุณ Apple นี่เป็นวิธีที่คุณควรนำไปใช้)
ดังนั้นวิธีการทำงานนี้เป็นเพียงหมวดหมู่ของ UIViewController เมื่อมันโหลดมันจะแทนที่เมธอด native viewDidLoad ด้วยวิธีที่จะตรวจสอบค่าสถานะโกลบอลเพื่อดูว่าโหมดมืดถูกปิดใช้งานสำหรับทุกสิ่งหรือไม่
เนื่องจากมันถูกทริกเกอร์ในการโหลด UIViewController มันควรจะเริ่มต้นโดยอัตโนมัติและปิดการใช้งานโหมดมืดโดยค่าเริ่มต้น หากนี่ไม่ใช่สิ่งที่คุณต้องการคุณจะต้องเข้าที่ก่อนกำหนดและตั้งค่าสถานะหรืออื่น ๆ เพียงตั้งค่าสถานะเริ่มต้น
ฉันยังไม่ได้เขียนอะไรเพื่อตอบสนองต่อผู้ใช้ในการเปิดหรือปิดการตั้งค่าสถานะ ดังนั้นนี่คือตัวอย่างโค้ดโดยทั่วไป หากเราต้องการให้ผู้ใช้โต้ตอบกับสิ่งนี้ผู้ควบคุมมุมมองทั้งหมดจะต้องโหลดซ้ำ ฉันไม่รู้ว่าจะทำอย่างไรในทันทีทันใด แต่การส่งการแจ้งเตือนบางอย่างอาจเป็นการหลอกลวง ดังนั้นในตอนนี้การเปิด / ปิดส่วนกลางสำหรับโหมดมืดจะทำงานเมื่อเริ่มต้นหรือเริ่มแอพใหม่เท่านั้น
ตอนนี้มันยังไม่เพียงพอที่จะลองปิดโหมดมืดในทุก ๆ มุมมอง MFING viewController ในแอพขนาดใหญ่ของคุณ หากคุณกำลังใช้สินทรัพย์สีคุณจะมีกระดูกสมบูรณ์ พวกเราเป็นเวลากว่า 10 ปีที่เข้าใจวัตถุที่ไม่เปลี่ยนรูปแบบซึ่งไม่อาจเปลี่ยนแปลงได้ สีที่คุณได้รับจากแคตตาล็อกสินทรัพย์สีระบุว่าเป็น UIColor แต่เป็นสีแบบไดนามิก (ไม่แน่นอน) และจะเปลี่ยนแปลงภายใต้ตัวคุณเนื่องจากระบบจะเปลี่ยนจากโหมดมืดเป็นแสง นั่นควรจะเป็นคุณสมบัติ แต่แน่นอนว่าไม่มีการสลับหลักเพื่อขอให้สิ่งเหล่านี้หยุดทำการเปลี่ยนแปลง (เท่าที่ฉันรู้ในตอนนี้บางทีอาจมีใครบางคนสามารถปรับปรุงได้)
ดังนั้นวิธีการแก้ปัญหาอยู่ในสองส่วน:
หมวดหมู่สาธารณะบน UIViewController ที่ให้ประโยชน์ด้านสาธารณูปโภคและความสะดวกสบาย ... ตัวอย่างเช่นฉันไม่คิดว่าแอปเปิลคิดเกี่ยวกับข้อเท็จจริงที่ว่าพวกเราบางคนผสมโค้ดเว็บลงในแอพของเรา ด้วยเหตุนี้เราจึงมีสไตล์ชีทที่ต้องเปิดใช้งานตามโหมดมืดหรือสว่าง ดังนั้นคุณต้องสร้างวัตถุสไตล์ชีตแบบไดนามิก (ซึ่งจะดี) หรือถามว่าสถานะปัจจุบันคืออะไร (ไม่ดี แต่ง่าย)
หมวดหมู่นี้เมื่อโหลดจะแทนที่เมธอด viewDidLoad ของคลาส UIViewController และการสกัดกั้นการโทร ฉันไม่รู้ว่านี่ทำผิดกฎของ App Store หรือไม่ ถ้าเป็นเช่นนั้นมีวิธีอื่นที่อาจเป็นไปได้ แต่คุณสามารถพิจารณาได้ว่าเป็นการพิสูจน์แนวคิด ตัวอย่างเช่นคุณสามารถสร้างคลาสย่อยหนึ่งประเภทของตัวควบคุมมุมมองหลักทั้งหมดและทำให้ตัวควบคุมมุมมองของคุณเองทั้งหมดสืบทอดจากสิ่งเหล่านั้นจากนั้นคุณสามารถใช้แนวคิดหมวดหมู่ DarkMode และเรียกใช้เพื่อบังคับให้ยกเลิกตัวควบคุมมุมมองทั้งหมดของคุณ มันน่าเกลียด แต่มันจะไม่ผิดกฎใด ๆ ฉันชอบที่จะใช้ runtime เพราะนั่นคือสิ่งที่ runtime ทำขึ้นมา ดังนั้นในรุ่นของฉันคุณเพียงแค่เพิ่มหมวดหมู่คุณตั้งค่าตัวแปรส่วนกลางในหมวดหมู่ว่าคุณต้องการให้มันปิดกั้นโหมดมืดหรือไม่และมันจะทำเช่นนั้น
คุณยังไม่ได้ออกจากป่าดังที่กล่าวมาปัญหาอีกข้อคือ UIColor ทำสิ่งที่มันต้องการ ดังนั้นแม้ว่าตัวควบคุมมุมมองของคุณกำลังบล็อกโหมดมืด UIColor ไม่ทราบว่าคุณกำลังใช้อยู่ที่ไหนหรือวิธีการดังกล่าวไม่สามารถปรับได้ เป็นผลให้คุณสามารถดึงข้อมูลได้อย่างถูกต้อง แต่จากนั้นจะสามารถคืนค่าคุณได้ในอนาคต อาจจะช้าในภายหลัง ทางรอบนั้นคือการจัดสรรมันสองครั้งโดยใช้ CGColor และเปลี่ยนมันเป็นสีคงที่ ซึ่งหมายความว่าหากผู้ใช้ของคุณย้อนกลับไปและเปิดใช้งานโหมดมืดบนหน้าการตั้งค่าของคุณ (ความคิดที่นี่คือการทำให้การทำงานนี้เพื่อให้ผู้ใช้สามารถควบคุมแอปของคุณมากกว่าและเหนือส่วนที่เหลือของระบบ) สีคงทั้งหมดเหล่านั้น จำเป็นต้องเปลี่ยน จนถึงตอนนี้เหลือสำหรับคนอื่นที่จะแก้ปัญหา วิธีที่ง่ายที่สุดในการทำคือการเริ่มต้นให้คุณ กำลังออกจากโหมดมืดโดยแบ่งเป็นศูนย์เพื่อหยุดแอพเนื่องจากคุณไม่สามารถออกจากมันและบอกให้ผู้ใช้เพิ่งรีสตาร์ท นั่นอาจเป็นการละเมิดหลักเกณฑ์ของ App Store เช่นกัน แต่เป็นแนวคิด
หมวดหมู่ UIColor ไม่จำเป็นต้องถูกเปิดเผยเพียงแค่ใช้งานการเรียก colorNamed: ... ถ้าคุณไม่บอกคลาส DarkMode ViewController ให้บล็อกโหมดมืดมันจะทำงานได้อย่างสมบูรณ์แบบอย่างที่คาดไว้ พยายามทำให้บางสิ่งบางอย่างสวยงามแทนรหัสแอปเปิ้ลสปาเก็ตตี้มาตรฐานซึ่งหมายความว่าคุณจะต้องแก้ไขแอพส่วนใหญ่หากคุณต้องการยกเลิกโหมดมืดโดยใช้โปรแกรมหรือสลับมัน ตอนนี้ฉันไม่รู้ว่ามีวิธีที่ดีกว่าในการเปลี่ยน Info.plist แบบเป็นโปรแกรมเพื่อปิดโหมดมืดตามต้องการหรือไม่ เท่าที่ความเข้าใจของฉันไปถึงนั่นคือคุณลักษณะการรวบรวมเวลาและหลังจากนั้นคุณก็รู้สึกผิด
ดังนั้นนี่คือรหัสที่คุณต้องการ ควรดร็อปอินและใช้วิธีเดียวในการตั้งค่าสไตล์ UI หรือตั้งค่าเริ่มต้นในโค้ด คุณมีอิสระที่จะใช้แก้ไขทำอะไรก็ได้ที่คุณต้องการด้วยสิ่งนี้ไม่ว่าจะเพื่อวัตถุประสงค์ใดและไม่มีการรับประกันและฉันไม่รู้ว่าจะผ่านแอพสโตร์หรือไม่ การปรับปรุงยินดีมาก
การเตือนที่เป็นธรรมฉันไม่ได้ใช้ ARC หรือวิธีการจับอื่น ๆ
////// 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
มีชุดยูทิลิตี้ฟังก์ชั่นที่ใช้สำหรับการทำวิธีการแลกเปลี่ยน แยกไฟล์ นี่คือสิ่งมาตรฐานแม้ว่าและคุณสามารถค้นหารหัสที่คล้ายกันได้ทุกที่
// 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;
}
}
ฉันกำลังคัดลอกและวางไฟล์นี้สองสามไฟล์ตั้งแต่ q-runtime.h เป็นไลบรารีที่ใช้ซ้ำได้ของฉันและนี่เป็นเพียงส่วนหนึ่งของมัน หากสิ่งที่ไม่ได้รวบรวมแจ้งให้ฉันทราบ
UIUserInterfaceStyle
เป็นLight
ใน Info.Plist ของคุณ ดู developer.apple.com/library/archive/documentation/General/…