iOS 13에서 다크 모드 변경 비활성화 [중복]
이 질문에 이미 답변이 있습니다.
내 응용 프로그램은 다크 모드를 위해 준비되지 않았으며 오늘 작업하지 않을 것입니다.
내 앱의 다크 모드 변경을 비활성화하는 방법이 있습니까?
다크 모드 옵트 아웃과 관련된 Apple의 항목. 이 페이지는 Xcode 11 및 iOS 13 용으로 작성되었습니다.
다음은 Apple이 밝거나 어두운 모드의 옵트 아웃을 논의하는 곳입니다.
이 섹션은 Xcode 11 사용에 적용됩니다.
개별적으로 UIViewController를 옵트 아웃하려면
override func viewDidLoad() {
super.viewDidLoad()
// overrideUserInterfaceStyle is available with iOS 13
if #available(iOS 13.0, *) {
// Always adopt a light interface style.
overrideUserInterfaceStyle = .light
}
}
overrideUserInterfaceStyle에 대한 Apple 문서
위 코드는 Xcode 11에서 어떻게 보일까요?
전체 신청서를 옵트 아웃하려는 경우
이 plist 값에는 Xcode 11이 필요합니다.
info.plist 파일 에서 다음 키를 사용하여 전체 앱을 옵트 아웃합니다 .
UIUserInterfaceStyle
그리고 값을 Light
.
XML 에 대한 UIUserInterfaceStyle
할당 :
<key>UIUserInterfaceStyle</key>
<string>Light</string>
이 섹션은 Xcode 10.x 사용에 적용됩니다.
제출에 Xcode 11을 사용하는 경우이 줄 아래의 모든 항목을 무시해도됩니다.
iOS 12에는 관련 API가 없기 때문에 위에 제공된 값을 사용하려고하면 오류가 발생합니다.
overrideUserInterfaceStyle
당신의 설정 을 위해UIViewController
이것은 컴파일러 버전과 iOS 버전을 테스트하여 Xcode 10에서 처리 할 수 있습니다.
#if compiler(>=5.1)
if #available(iOS 13.0, *) {
// Always adopt a light interface style.
overrideUserInterfaceStyle = .light
}
#endif
그러나 Xcode 버전 10.x를 사용하면 plist 설정이 실패합니다.
에 신용 @Aron 넬슨 , @Raimundas Sakalauskas 및 @NSLeader 이 문제를 데리고합니다.
사실 저는 여러분이 응용 프로그램의 모든 viw 컨트롤러를 사용하지 않고도 코드에서 다크 모드를 전역 적으로 옵트 아웃 할 수있는 코드를 작성했습니다. 클래스 목록을 관리하여 클래스별로 옵트 아웃하도록 조정할 수 있습니다. 제가 원하는 것은 사용자가 내 앱의 다크 모드 인터페이스가 마음에 드는지 확인하고 마음에 들지 않으면 끌 수 있다는 것입니다. 이렇게하면 나머지 응용 프로그램에 대해 계속 다크 모드를 사용할 수 있습니다.
사용자 선택은 좋습니다 (Ahem, Apple을 보면 이것이 구현해야하는 방법입니다).
이것이 작동하는 방식은 UIViewController의 범주 일뿐입니다. 로드 할 때 기본 viewDidLoad 메서드를 전역 플래그를 확인하여 모든 것에 대해 다크 모드가 비활성화되었는지 여부를 확인하는 메서드로 대체합니다.
UIViewController로드시 트리거되기 때문에 기본적으로 다크 모드가 자동으로 시작되고 비활성화됩니다. 이것이 원하는 것이 아니라면 어딘가에 일찍 들어가서 플래그를 설정하거나 기본 플래그를 설정해야합니다.
나는 아직 사용자가 플래그를 켜거나 끄는 것에 응답하는 것을 작성하지 않았습니다. 그래서 이것은 기본적으로 예제 코드입니다. 사용자가 이것과 상호 작용하기를 원한다면 모든 뷰 컨트롤러를 다시로드해야합니다. 나는 그렇게하는 방법을 모르지만 아마도 알림을 보내는 것이 트릭을 할 것입니다. 따라서 현재 다크 모드에 대한이 전역 켜기 / 끄기는 앱을 시작하거나 다시 시작할 때만 작동합니다.
이제 거대한 앱의 모든 단일 MFING viewController에서 다크 모드를 끄는 것만으로는 충분하지 않습니다. 색상 자산을 사용하는 경우 완전히 뼈대입니다. 우리는 10 년 이상 동안 불변 객체가 불변이라는 것을 이해했습니다. 색상 자산 카탈로그에서 가져온 색상은 UIColor라고 말하지만 동적 (변경 가능) 색상이며 시스템이 어두운 모드에서 밝은 모드로 변경되면 아래에서 변경됩니다. 그것은 기능이어야합니다. 그러나 물론 이러한 변경을 중단하도록 요청하는 마스터 토글은 없습니다 (지금 내가 아는 한 누군가가이를 개선 할 수있을 것입니다).
따라서 솔루션은 두 부분으로 나뉩니다.
일부 유틸리티와 편리한 메소드를 제공하는 UIViewController의 공개 카테고리 ... 예를 들어, 저는 애플이 우리 중 일부가 웹 코드를 앱에 혼합한다는 사실에 대해 생각하지 않았다고 생각합니다. 따라서 어둡거나 밝은 모드에 따라 전환해야하는 스타일 시트가 있습니다. 따라서 어떤 종류의 동적 스타일 시트 객체 (좋을 것임)를 빌드하거나 현재 상태가 무엇인지 (나쁘지만 쉬움) 물어볼 필요가 있습니다.
이 카테고리는로드 될 때 UIViewController 클래스의 viewDidLoad 메소드를 대체하고 호출을 가로 챕니다. 그것이 앱 스토어 규칙을 위반하는지 모르겠습니다. 그럴 경우 다른 방법이있을 수 있지만 개념 증명으로 간주 할 수 있습니다. 예를 들어 모든 메인 뷰 컨트롤러 유형의 하나의 서브 클래스를 만들고 자신의 모든 뷰 컨트롤러가 그로부터 상속하도록 만든 다음 DarkMode 카테고리 아이디어를 사용하고이를 호출하여 모든 뷰 컨트롤러를 강제로 옵트 아웃 할 수 있습니다. 추악하지만 어떤 규칙도 어기지는 않을 것입니다. 나는 런타임이 수행되도록 만들어진 것이기 때문에 런타임을 사용하는 것을 선호합니다. 그래서 제 버전에서는 카테고리를 추가하고 다크 모드를 차단할지 여부에 대한 카테고리에 전역 변수를 설정하면됩니다.
언급했듯이 당신은 아직 숲에서 벗어나지 않았습니다. 다른 문제는 UIColor가 기본적으로 원하는 것을 무엇이든 수행한다는 것입니다. 따라서 뷰 컨트롤러가 다크 모드를 차단하더라도 UIColor는 어디에 어떻게 사용하는지 알지 못하므로 적응할 수 없습니다. 결과적으로 올바르게 가져올 수 있지만 나중에 언젠가는 되돌릴 것입니다. 아마 곧 아마 나중에. 따라서 그 방법은 CGColor를 사용하여 두 번 할당하고 정적 색상으로 바꾸는 것입니다. 즉, 사용자가 돌아가서 설정 페이지에서 다크 모드를 다시 사용하도록 설정하면 (여기에서는 사용자가 시스템의 나머지 부분에서 앱을 제어 할 수 있도록이 작업을 수행하는 것입니다) 모든 정적 색상 교체가 필요합니다. 지금까지 이것은 다른 사람이 해결해야 할 문제입니다. 쉽게 할 수있는 방법은 기본값을 설정하는 것입니다. 다크 모드에서 다시 옵트 아웃하면 앱을 종료 할 수 없기 때문에 0으로 나누고 앱을 중단하고 사용자에게 다시 시작하라고 알려줍니다. 그것은 아마도 앱 스토어 지침을 위반할 수도 있지만 그것은 아이디어입니다.
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는 재사용 가능한 라이브러리이고 이것은 단지 일부일 뿐이므로 몇 개의 파일에서 이것을 복사하여 붙여 넣습니다. 컴파일이되지 않으면 알려주세요.
참고 URL : https://stackoverflow.com/questions/56546267/ios-13-disable-dark-mode-changes
'code' 카테고리의 다른 글
선택적 콜백을위한 JavaScript 스타일 (0) | 2020.09.11 |
---|---|
Python urllib2 : URL에서 JSON 응답 수신 (0) | 2020.09.11 |
WPF의 링크 버튼 (0) | 2020.09.11 |
void 포인터를 삭제하는 것이 안전합니까? (0) | 2020.09.11 |
Tic Tac Toe 게임 오버 결정을위한 알고리즘 (0) | 2020.09.11 |