내 응용 프로그램의 대부분은 웹 구현으로 구성되어 기본 구현으로는 아직 사용할 수없는 기능을 제공합니다. 웹 팀은 웹 사이트에 어두운 테마를 구현할 계획이 없습니다. 따라서 내 앱은 iOS 13에서 다크 모드를 지원하면 절반의 절반으로 보입니다.
앱이 웹 사이트 테마와 일치하도록 항상 라이트 모드를 표시하도록 다크 모드 지원을 선택 해제 할 수 있습니까?
내 응용 프로그램의 대부분은 웹 구현으로 구성되어 기본 구현으로는 아직 사용할 수없는 기능을 제공합니다. 웹 팀은 웹 사이트에 어두운 테마를 구현할 계획이 없습니다. 따라서 내 앱은 iOS 13에서 다크 모드를 지원하면 절반의 절반으로 보입니다.
앱이 웹 사이트 테마와 일치하도록 항상 라이트 모드를 표시하도록 다크 모드 지원을 선택 해제 할 수 있습니까?
답변:
첫째, 다음은 어두운 모드 선택 해제와 관련된 Apple의 항목 입니다. 이 링크의 내용은 Xcode 11 및 iOS 13 용으로 작성되었습니다 .
ENTIRE 신청을 거부하려면
info.plist 파일 에서 다음 키를 사용하십시오 .
UIUserInterfaceStyle
그리고 값을 할당하십시오 Light
.
XML 에 대한 UIUserInterfaceStyle
할당 :
<key>UIUserInterfaceStyle</key>
<string>Light</string>
overrideUserInterfaceStyle
앱의 window
변수 에 대해 설정할 수 있습니다 .
프로젝트 생성 방법에 따라 AppDelegate
파일 또는 에있을 수 있습니다 SceneDelegate
.
if #available(iOS 13.0, *) {
window?.overrideUserInterfaceStyle = .light
}
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에서 어떻게 보일까요?
제출에 Xcode 11을 사용하는 경우이 줄 아래의 모든 내용을 무시해도됩니다.
iOS 12에는 관련 API가 없으므로 위에 제공된 값을 사용하려고하면 오류가 발생합니다.
overrideUserInterfaceStyle
귀하의 설정 에UIViewController
UIViewController를 개별적으로 선택 해제하려면
컴파일러 버전과 iOS 버전을 테스트하여 Xcode 10에서 처리 할 수 있습니다.
#if compiler(>=5.1)
if #available(iOS 13.0, *) {
// Always adopt a light interface style.
overrideUserInterfaceStyle = .light
}
#endif
ENTIRE 신청을 거부하려면
AppDelegate
파일에 다음 코드를 추가하여 위의 스 니펫을 수정하여 Xcode 10의 전체 응용 프로그램에서 작동하도록 할 수 있습니다.
#if compiler(>=5.1)
if #available(iOS 13.0, *) {
// Always adopt a light interface style.
window?.overrideUserInterfaceStyle = .light
}
#endif
그러나 Xcode 버전 10.x를 사용하는 경우 plist 설정이 실패합니다.
에 신용 @Aron 넬슨 , @Raimundas Sakalauskas , @NSLeader 및 rmaddy 그들의 피드백이 답변을 향상.
overrideUserInterfaceStyle
에서 viewDidLoad
모든 뷰 컨트롤러, 당신은 응용 프로그램의 주 창에서 한 번에 설정할 수 있습니다. 전체 앱이 한 방향으로 동작하도록하려면 훨씬 쉽습니다.
#if compiler(>=5.1)
대신 responds(to:)
과setValue
"iOS에서 다크 모드 구현"( https://developer.apple.com/videos/play/wwdc2019/214/ 31:13에서 시작) 에 대한 Apple의 세션에 따르면 모든 뷰 컨트롤러 또는 뷰에서 설정 하거나 설정할 overrideUserInterfaceStyle
수 있습니다 하위 뷰 또는 뷰 컨트롤러에 사용될 것 입니다.UIUserInterfaceStyleLight
UIUserInterfaceStyleDark
traitCollection
이미 SeanR에서 언급 한 바와 같이, 당신은 설정할 수 UIUserInterfaceStyle
로 Light
또는 Dark
전체 앱이 변경 앱을 plist 파일에.
Xcode 11 이상 (iOS 13 이상 SDK)을 사용하지 않는 경우 앱이 자동으로 다크 모드를 지원하지 않도록 선택했습니다. 따라서 어두운 모드를 선택 해제 할 필요가 없습니다.
Xcode 11 이상을 사용하는 경우 시스템에서 앱에 대해 어두운 모드가 자동으로 활성화되었습니다. 선호도에 따라 어두운 모드를 비활성화하는 두 가지 방법이 있습니다. 특정 창,보기 또는보기 컨트롤러에 대해 완전히 비활성화하거나 비활성화 할 수 있습니다.
앱에서 다크 모드를 완전히 비활성화
앱의 Info.plist 파일에서 UIUserInterfaceStyle
와 같이 키를 값으로 포함하여 어두운 모드를 비활성화 할 수 있습니다 Light
.
사용자의 선호도를 무시하고 항상 앱에 밝은 모양을 적용합니다.
Window, View 또는 View Controller에 대해 어두운 모드 비활성화
overrideUserInterfaceStyle
적절한 창, 뷰 또는 뷰 컨트롤러 의 속성을 설정하여 인터페이스가 항상 밝거나 어두운 스타일로 표시되도록 할 수 있습니다 .
컨트롤러보기 :
override func viewDidLoad() {
super.viewDidLoad()
/* view controller’s views and child view controllers
always adopt a light interface style. */
overrideUserInterfaceStyle = .light
}
견해:
// The view and all of its subviews always adopt light style.
youView.overrideUserInterfaceStyle = .light
창문:
/* Everything in the window adopts the style,
including the root view controller and all presentation controllers that
display content in that window.*/
window.overrideUserInterfaceStyle = .light
참고 : Apple은 앱에서 다크 모드를 지원할 것을 적극 권장합니다. 따라서 일시적으로 어두운 모드 만 비활성화 할 수 있습니다.
자세한 내용은 여기를 참조하십시오 : iOS 앱을위한 특정 인터페이스 스타일 선택
********** Xcode 11 이상을위한 가장 쉬운 방법 ***********
전에 info.plist에 추가하십시오 </dict></plist>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
해결책을 찾았습니다. 나는 처음에 UIUserInterfaceStyle-Information Property List 와 UIUserInterfaceStyle-UIKit 에서 한 조각을 만들었지 만, 실제로 iOS 앱을위한 특정 인터페이스 스타일 선택에 문서화되어있는 것을 발견했다 .
당신의에서 info.plist
설정 UIUserInterfaceStyle
( 사용자 인터페이스 스타일 에) 1 ( UIUserInterfaceStyle.light
).
편집 : dorbeetle의 답변에 따라 더 적합한 설정 UIUserInterfaceStyle
은입니다 Light
.
[UIInterfaceStyle] '2' is not a recognized value for UIUserInterfaceStyle. Defaulting to Light.
위의 답변은 전체 앱을 선택 해제하려는 경우 작동합니다. UI가있는 lib에서 작업 중이고 .plist를 편집 할 권한이없는 경우 코드를 통해 수행 할 수도 있습니다.
iOS 13 SDK에 대해 컴파일하는 경우 다음 코드를 간단히 사용할 수 있습니다.
빠른:
if #available(iOS 13.0, *) {
self.overrideUserInterfaceStyle = .light
}
Obj-C :
if (@available(iOS 13.0, *)) {
self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
그러나 iOS 12 SDK 에 대해 코드를 컴파일하려면 (현재 여전히 안정적인 최신 SDK 임) 선택기를 사용해야합니다. 선택기가있는 코드 :
Swift (XCode는이 코드에 대한 경고를 표시하지만 SDK 12에 속성이 없으므로 컴파일하지 않기 때문에 지금이 유일한 방법입니다) :
if #available(iOS 13.0, *) {
if self.responds(to: Selector("overrideUserInterfaceStyle")) {
self.setValue(UIUserInterfaceStyle.light.rawValue, forKey: "overrideUserInterfaceStyle")
}
}
Obj-C :
if (@available(iOS 13.0, *)) {
if ([self respondsToSelector:NSSelectorFromString(@"overrideUserInterfaceStyle")]) {
[self setValue:@(UIUserInterfaceStyleLight) forKey:@"overrideUserInterfaceStyle"];
}
}
overrideUserInterfaceStyle
속한 것을 지정하면 더 좋습니다 .
최근 업데이트-
Xcode 10.x를 사용하는 경우 기본값 UIUserInterfaceStyle
은 light
iOS 13.x입니다. iOS 13 기기에서 실행하면 기기는 라이트 모드에서만 작동합니다.
UIUserInterfaceStyle
Info.plist 파일에 키 를 명시 적으로 추가 할 필요가 없습니다. 키를 추가하면 앱을 확인할 때 오류가 발생합니다.
잘못된 Info.plist 키입니다. Payload / AppName.appInfo.plist 파일의 'UIUserInterfaceStyle'키가 유효하지 않습니다.
UIUserInterfaceStyle
Xcode 11.x를 사용할 때는 Info.plist 파일에 키만 추가하십시오 .
UIUserInterfaceStyle
plist 파일 에 키를 추가 하면 Apple은 https://stackoverflow.com/a/56546554/7524146에서 언급 한 것처럼 릴리스 빌드를 거부 할 수 있습니다.
어쨌든 각 ViewController 를 명시 적으로 말하면 귀찮습니다self.overrideUserInterfaceStyle = .light
. 그러나 루트 window
객체 에이 코드의 평화를 한 번 사용할 수 있습니다 .
if #available(iOS 13.0, *) {
if window.responds(to: Selector(("overrideUserInterfaceStyle"))) {
window.setValue(UIUserInterfaceStyle.light.rawValue, forKey: "overrideUserInterfaceStyle")
}
}
application(application: didFinishLaunchingWithOptions:)
이 선택기는 true
초기 단계에서 응답하지 않기 때문에이 작업을 수행 할 수 없습니다 . 그러나 나중에 할 수 있습니다. AppDelegate에서 UI를 자동으로 시작하는 대신 앱에서 사용자 정의 AppPresenter
또는 AppRouter
클래스를 사용하면 매우 쉽습니다 .
window!.overrideUserInterfaceStyle = .light
당신은에서 창을 얻을 수 있습니다 SceneDelegate
viewController.overrideUserInterfaceStyle = .light
viewController
viewController 내부에서도 자체를 설정할 수 있습니다.
view.overrideUserInterfaceStyle = .light
view
보기 내부에서도 자체를 설정할 수 있습니다.
if #available(iOS 13.0, *) { ,,, }
이전 iOS 버전을 지원하는 경우 사용해야 할 수 있습니다.
예, viewDidLoad에 다음 코드를 추가하여 건너 뛸 수 있습니다.
if #available(iOS 13.0, *) {
// Always adopt a light interface style.
overrideUserInterfaceStyle = .light
}
내 앱은 현재 어두운 모드를 지원하지 않으며 밝은 앱 바 색상을 사용합니다. 내 키에 다음 키를 추가하여 상태 표시 줄 내용을 어두운 텍스트 및 아이콘으로 강제 설정할 수있었습니다 Info.plist
.
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleDarkContent</string>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
다른 가능한 값은 여기 ( https://developer.apple.com/documentation/uikit/uistatusbarstyle) 에서 찾으십시오.
어두운 모드를 지원하거나 우회하기 위해 앱에서 사용할 수있는 몇 가지 팁과 요령이 있습니다.
UIViewController의 인터페이스 스타일을 재정의 할 수 있습니다
1 : overrideUserInterfaceStyle = .dark // 다크 모드의 경우
2 : overrideUserInterfaceStyle = .light // 라이트 모드
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
overrideUserInterfaceStyle = .light
}
}
간단히 새 키를 추가 할 수 있습니다
UIUserInterfaceStyle
app info.plist에서 값을 Light 또는 Dark로 설정하십시오. 앱 기본 스타일을 제공 한 값으로 재정의합니다. 모든 viewController에 overrideUserInterfaceStyle = .light를 추가 할 필요는 없습니다. info.plist에 한 줄만 있으면됩니다.
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .light
} else {
// Fallback on earlier versions
}
앱 수명주기 동안 창 속성이 변경 될 수 있으므로이 솔루션을 사용합니다. 따라서 "overrideUserInterfaceStyle = .light"할당을 반복해야합니다. UIWindow.appearance ()를 사용하면 새로 만든 UIWindow 객체에 사용될 기본값을 설정할 수 있습니다.
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 13.0, *) {
UIWindow.appearance().overrideUserInterfaceStyle = .light
}
return true
}
}
import UIKit
extension UIViewController {
override open func awakeFromNib() {
super.awakeFromNib()
if #available(iOS 13.0, *) {
overrideUserInterfaceStyle = .light
}
}
}
실제로 나는 방금 응용 프로그램의 모든 단일 viw 컨트롤러와 함께 putz하지 않고도 코드에서 암 모드를 선택적으로 해제 할 수있는 코드를 작성했습니다. 클래스 목록을 관리하여 클래스별로 선택 해제하도록 조정할 수 있습니다. 내가 원하는 것은 사용자가 내 앱의 어두운 모드 인터페이스를 좋아하는지 확인하고 마음에 들지 않으면 끌 수 있다는 것입니다. 이렇게하면 나머지 응용 프로그램에 계속 어두운 모드를 사용할 수 있습니다.
사용자의 선택이 좋습니다 (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가 재사용 가능한 라이브러리이므로이 파일의 일부에 불과하므로이 파일을 복사하여 붙여 넣습니다. 컴파일되지 않은 것이 있으면 알려주세요.
UIUserInterfaceStyle
에Light
당신의 Info.plist에. developer.apple.com/library/archive/documentation/General/…을