Trên thực tế tôi chỉ viết một số mã sẽ cho phép bạn từ chối toàn cầu chế độ tối trong mã mà không cần phải đặt với mọi bộ điều khiển viw trong ứng dụng của bạn. Điều này có lẽ có thể được tinh chỉnh để từ chối một lớp theo cơ sở lớp bằng cách quản lý một danh sách các lớp. Đối với tôi, những gì tôi muốn là cho người dùng của tôi xem họ có thích giao diện chế độ tối cho ứng dụng của tôi không và nếu họ không thích nó, họ có thể tắt nó đi. Điều này sẽ cho phép họ tiếp tục sử dụng chế độ tối cho phần còn lại của ứng dụng.
Lựa chọn của người dùng là tốt (Ahem, nhìn vào Apple của bạn, đây là cách bạn nên thực hiện nó).
Vì vậy, cách thức hoạt động của nó là nó chỉ là một thể loại của UIViewControll. Khi tải, nó thay thế phương thức viewDidLoad riêng bằng một phương thức sẽ kiểm tra cờ toàn cầu để xem chế độ tối có bị vô hiệu hóa cho mọi thứ hay không.
Vì được kích hoạt khi tải UIViewControll, nó sẽ tự động khởi động và tắt chế độ tối theo mặc định. Nếu đây không phải là điều bạn muốn, thì bạn cần phải đến đó sớm và đặt cờ, nếu không thì chỉ cần đặt cờ mặc định.
Tôi chưa viết bất cứ điều gì để phản hồi cho người dùng bật hoặc tắt cờ. Vì vậy, đây là cơ bản mã ví dụ. Nếu chúng tôi muốn người dùng tương tác với điều này, tất cả các bộ điều khiển xem sẽ cần tải lại. Tôi không biết làm thế nào để làm điều đó một cách trực tiếp nhưng có lẽ gửi một số thông báo sẽ thực hiện thủ thuật. Vì vậy, ngay bây giờ, chế độ bật / tắt toàn cầu này cho chế độ tối chỉ hoạt động khi khởi động hoặc khởi động lại ứng dụng.
Bây giờ, nó không đủ để cố gắng tắt chế độ tối trong mỗi chế độ xem MFING duy nhất trong ứng dụng khổng lồ của bạn. Nếu bạn đang sử dụng tài sản màu, bạn hoàn toàn bị thu hút. Chúng tôi trong hơn 10 năm đã hiểu các đối tượng bất biến là bất biến. Màu sắc bạn nhận được từ danh mục tài sản màu cho biết chúng là UIColor nhưng chúng là màu động (có thể thay đổi) và sẽ thay đổi bên dưới bạn khi hệ thống thay đổi từ chế độ tối sang sáng. Đó được coi là một tính năng. Nhưng tất nhiên không có chủ nhân nào yêu cầu những điều này ngừng thực hiện thay đổi này (theo như tôi biết ngay bây giờ, có lẽ ai đó có thể cải thiện điều này).
Vì vậy, giải pháp là hai phần:
một danh mục công khai trên UIViewControll cung cấp một số phương thức tiện ích và tiện lợi ... chẳng hạn, tôi không nghĩ rằng apple đã nghĩ về thực tế rằng một số người trong chúng ta trộn mã web vào các ứng dụng của mình. Như vậy, chúng ta có các bảng định kiểu cần được bật dựa trên chế độ tối hoặc sáng. Do đó, bạn cần phải xây dựng một số loại đối tượng biểu định kiểu động (sẽ tốt) hoặc chỉ hỏi trạng thái hiện tại là gì (xấu nhưng dễ).
thể loại này khi tải sẽ thay thế phương thức viewDidLoad của lớp UIViewControll và chặn các cuộc gọi. Tôi không biết nếu điều đó phá vỡ quy tắc cửa hàng ứng dụng. Nếu có, có nhiều cách khác có lẽ nhưng bạn có thể coi đó là một bằng chứng về khái niệm. Ví dụ, bạn có thể tạo một lớp con của tất cả các loại trình điều khiển chế độ xem chính và làm cho tất cả các bộ điều khiển chế độ xem của riêng bạn được kế thừa từ các loại đó, sau đó bạn có thể sử dụng ý tưởng danh mục DarkMode và gọi nó để từ chối tất cả các bộ điều khiển xem của bạn. Nó xấu hơn nhưng nó sẽ không phá vỡ bất kỳ quy tắc nào. Tôi thích sử dụng thời gian chạy vì đó là những gì thời gian chạy được thực hiện. Vì vậy, trong phiên bản của tôi, bạn chỉ cần thêm danh mục, bạn đặt biến toàn cục trên danh mục cho dù bạn có muốn chặn chế độ tối hay không và nó sẽ làm điều đó.
Bạn chưa ra khỏi rừng, như đã đề cập, vấn đề khác là UIColor về cơ bản làm bất cứ điều gì nó muốn. Vì vậy, ngay cả khi bộ điều khiển chế độ xem của bạn đang chặn chế độ tối, UIColor không biết bạn đang sử dụng nó ở đâu và như thế nào. Kết quả là bạn có thể tìm nạp nó một cách chính xác nhưng sau đó nó sẽ trở lại với bạn vào một lúc nào đó trong tương lai. Có lẽ sớm có thể muộn hơn. Vì vậy, cách đó là bằng cách phân bổ hai lần bằng CGColor và biến nó thành màu tĩnh. Điều này có nghĩa là nếu người dùng của bạn quay lại và bật lại chế độ tối trên trang cài đặt của bạn (ý tưởng ở đây là làm cho nó hoạt động để người dùng có quyền kiểm soát ứng dụng của bạn trên và trên phần còn lại của hệ thống), tất cả các màu tĩnh đó cần thay thế. Cho đến nay điều này là để cho người khác giải quyết. Cách dễ dàng để làm điều đó là tạo một mặc định rằng bạn ' đang từ chối chế độ tối, chia cho số 0 để làm hỏng ứng dụng vì bạn không thể thoát khỏi ứng dụng và yêu cầu người dùng khởi động lại nó. Điều đó có thể vi phạm nguyên tắc của cửa hàng ứng dụng, nhưng đó là một ý tưởng.
Danh mục UIColor không cần phải phơi bày, nó chỉ hoạt động khi gọi colorNamed: ... nếu bạn không nói với lớp DarkMode ViewContoder chặn chế độ tối, nó sẽ hoạt động hoàn hảo như mong đợi. Cố gắng làm cho một cái gì đó thanh lịch thay vì mã sphaghetti táo tiêu chuẩn, điều đó có nghĩa là bạn sẽ phải sửa đổi hầu hết ứng dụng của mình nếu bạn muốn lập trình từ chối chế độ tối hoặc chuyển đổi nó theo chương trình. Bây giờ tôi không biết có cách nào tốt hơn để thay đổi Info.plist theo chương trình để tắt chế độ tối khi cần. Theo như sự hiểu biết của tôi thì đó là một tính năng thời gian biên dịch và sau đó bạn được rút ra.
Vì vậy, đây là mã bạn cần. Nên thả vào và chỉ sử dụng một phương thức để đặt Kiểu UI hoặc đặt mặc định trong mã. Bạn có thể tự do sử dụng, sửa đổi, làm bất cứ điều gì bạn muốn với mục đích này và không có bảo hành nào được đưa ra và tôi không biết liệu nó có vượt qua cửa hàng ứng dụng hay không. Cải tiến rất đáng hoan nghênh.
Cảnh báo công bằng Tôi không sử dụng ARC hoặc bất kỳ phương pháp nắm giữ nào khác.
////// 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
Có một tập hợp các hàm tiện ích mà điều này sử dụng để thực hiện hoán đổi phương thức. Tập tin riêng biệt. Đây là công cụ tiêu chuẩn mặc dù và bạn có thể tìm thấy mã tương tự ở bất cứ đâu.
// 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;
}
}
Tôi đang sao chép và dán tệp này ra khỏi một vài tệp vì q-runtime.h là thư viện có thể sử dụng lại của tôi và đây chỉ là một phần của nó. Nếu một cái gì đó không biên dịch cho tôi biết.
UIUserInterfaceStyle
thànhLight
trong Info.Plist của bạn. Xem developer.apple.com/l