Bridge iOS 13 traitCollection and color (#78)
* Use system dynamic color on iOS 13+ * Swizzling for DMTraitCollection * Remove override of dm_updateDynamicColors and dm_updateDynamicImages * Update naming and return UITraitCollection.current directly * Allow using view controller to observe change
This commit is contained in:
Родитель
13e0d9db28
Коммит
7a20bcf603
|
@ -48,7 +48,6 @@
|
|||
8CDA629F2366DAA9004895B5 /* UILabel+DarkModeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CDA62852366DAA9004895B5 /* UILabel+DarkModeKit.swift */; };
|
||||
8CDA62A02366DAA9004895B5 /* UISlider+DarkModeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CDA62862366DAA9004895B5 /* UISlider+DarkModeKit.swift */; };
|
||||
8CDA62A12366DAA9004895B5 /* UITextField+DarkModeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CDA62872366DAA9004895B5 /* UITextField+DarkModeKit.swift */; };
|
||||
8CDA62A22366DAA9004895B5 /* UIApplication+DarkModeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CDA62882366DAA9004895B5 /* UIApplication+DarkModeKit.swift */; };
|
||||
8CDA62A32366DAA9004895B5 /* DMDynamicImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CDA62892366DAA9004895B5 /* DMDynamicImage.m */; };
|
||||
8CDA62A42366DAA9004895B5 /* DarkModeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CDA628A2366DAA9004895B5 /* DarkModeManager.swift */; };
|
||||
8CDA62A52366DAA9004895B5 /* DMDynamicColor.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CDA628B2366DAA9004895B5 /* DMDynamicColor.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -144,7 +143,6 @@
|
|||
8CDA62852366DAA9004895B5 /* UILabel+DarkModeKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UILabel+DarkModeKit.swift"; sourceTree = "<group>"; };
|
||||
8CDA62862366DAA9004895B5 /* UISlider+DarkModeKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UISlider+DarkModeKit.swift"; sourceTree = "<group>"; };
|
||||
8CDA62872366DAA9004895B5 /* UITextField+DarkModeKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITextField+DarkModeKit.swift"; sourceTree = "<group>"; };
|
||||
8CDA62882366DAA9004895B5 /* UIApplication+DarkModeKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+DarkModeKit.swift"; sourceTree = "<group>"; };
|
||||
8CDA62892366DAA9004895B5 /* DMDynamicImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DMDynamicImage.m; sourceTree = "<group>"; };
|
||||
8CDA628A2366DAA9004895B5 /* DarkModeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkModeManager.swift; sourceTree = "<group>"; };
|
||||
8CDA628B2366DAA9004895B5 /* DMDynamicColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DMDynamicColor.h; sourceTree = "<group>"; };
|
||||
|
@ -321,7 +319,6 @@
|
|||
8CDA62852366DAA9004895B5 /* UILabel+DarkModeKit.swift */,
|
||||
8CDA62862366DAA9004895B5 /* UISlider+DarkModeKit.swift */,
|
||||
8CDA62872366DAA9004895B5 /* UITextField+DarkModeKit.swift */,
|
||||
8CDA62882366DAA9004895B5 /* UIApplication+DarkModeKit.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -537,7 +534,6 @@
|
|||
8CE066D0239E5586002CE16D /* UIImage+DarkModeKit.m in Sources */,
|
||||
8CE066CF239E5582002CE16D /* UIColor+DarkModeKit.m in Sources */,
|
||||
8CDA62A02366DAA9004895B5 /* UISlider+DarkModeKit.swift in Sources */,
|
||||
8CDA62A22366DAA9004895B5 /* UIApplication+DarkModeKit.swift in Sources */,
|
||||
8CDA628F2366DAA9004895B5 /* DMDynamicColor.m in Sources */,
|
||||
8CDA62A42366DAA9004895B5 /* DarkModeManager.swift in Sources */,
|
||||
8CDA62982366DAA9004895B5 /* UITextView+DarkModeKit.swift in Sources */,
|
||||
|
|
|
@ -18,8 +18,8 @@ NS_SWIFT_NAME(DynamicColor)
|
|||
@property (nonatomic, readonly) UIColor *lightColor;
|
||||
@property (nonatomic, readonly) UIColor *darkColor;
|
||||
|
||||
- (instancetype)initWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor;
|
||||
- (instancetype)initWithDynamicProvider:(UIColor * (^)(DMTraitCollection *traitCollection))dynamicProvider;
|
||||
+ (UIColor *)colorWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor;
|
||||
+ (UIColor *)colorWithDynamicProvider:(UIColor * (^)(DMTraitCollection *traitCollection))dynamicProvider;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -16,13 +16,6 @@
|
|||
|
||||
@implementation DMDynamicColorProxy
|
||||
|
||||
// TODO: We need a more generic initializer.
|
||||
- (instancetype)initWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
|
||||
return [self initWithDynamicProvider:^(DMTraitCollection *traitCollection){
|
||||
return traitCollection.userInterfaceStyle == DMUserInterfaceStyleDark ? darkColor : lightColor;
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDynamicProvider:(UIColor * (^)(DMTraitCollection *traitCollection))dynamicProvider {
|
||||
self.dynamicProvider = dynamicProvider;
|
||||
return self;
|
||||
|
@ -39,7 +32,7 @@
|
|||
// MARK: UIColor
|
||||
|
||||
- (UIColor *)colorWithAlphaComponent:(CGFloat)alpha {
|
||||
return [[DMDynamicColor alloc] initWithDynamicProvider:^UIColor *(DMTraitCollection *traitCollection) {
|
||||
return [DMDynamicColor colorWithDynamicProvider:^UIColor *(DMTraitCollection *traitCollection) {
|
||||
return [self.dynamicProvider(traitCollection) colorWithAlphaComponent:alpha];
|
||||
}];
|
||||
}
|
||||
|
@ -99,11 +92,13 @@
|
|||
|
||||
@implementation DMDynamicColor
|
||||
|
||||
- (UIColor *)initWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
|
||||
return (DMDynamicColor *)[[DMDynamicColorProxy alloc] initWithLightColor:lightColor darkColor:darkColor];
|
||||
+ (UIColor *)colorWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
|
||||
return [self colorWithDynamicProvider:^(DMTraitCollection *traitCollection){
|
||||
return traitCollection.userInterfaceStyle == DMUserInterfaceStyleDark ? darkColor : lightColor;
|
||||
}];
|
||||
}
|
||||
|
||||
- (instancetype)initWithDynamicProvider:(UIColor * _Nonnull (^)(DMTraitCollection * _Nonnull))dynamicProvider {
|
||||
+ (UIColor *)colorWithDynamicProvider:(UIColor * _Nonnull (^)(DMTraitCollection * _Nonnull))dynamicProvider {
|
||||
return (DMDynamicColor *)[[DMDynamicColorProxy alloc] initWithDynamicProvider:dynamicProvider];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class UITraitCollection;
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -15,14 +17,28 @@ typedef NS_ENUM(NSInteger, DMUserInterfaceStyle) {
|
|||
|
||||
@interface DMTraitCollection : NSObject
|
||||
|
||||
@property (class, nonatomic, strong) DMTraitCollection *currentTraitCollection;
|
||||
@property (class, nonatomic, readonly) DMTraitCollection *currentTraitCollection;
|
||||
@property (class, nonatomic, readonly) DMTraitCollection *overrideTraitCollection;
|
||||
|
||||
+ (DMTraitCollection *)traitCollectionWithUserInterfaceStyle:(DMUserInterfaceStyle)userInterfaceStyle;
|
||||
+ (DMTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection API_AVAILABLE(ios(13.0));
|
||||
|
||||
@property (nonatomic, readonly) DMUserInterfaceStyle userInterfaceStyle;
|
||||
@property (nonatomic, readonly) UITraitCollection *uiTraitCollection API_AVAILABLE(ios(13.0));
|
||||
|
||||
- (instancetype)init NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
+ (void)setOverrideTraitCollection:(DMTraitCollection *)overrideTraitCollection animated:(BOOL)animated;
|
||||
|
||||
// MARK: - Observer Registration
|
||||
+ (void)registerWithApplication:(UIApplication *)application syncImmediately:(BOOL)syncImmediately animated:(BOOL)animated;
|
||||
+ (void)registerWithViewController:(UIViewController *)viewController syncImmediately:(BOOL)syncImmediately animated:(BOOL)animated;
|
||||
+ (void)unregister;
|
||||
|
||||
// MARK: - Swizzling
|
||||
// TODO: move swizzling to private header
|
||||
+ (void)swizzleUIScreenTraitCollectionDidChange API_AVAILABLE(ios(13.0));
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - DMTraitEnvironment
|
||||
|
@ -33,4 +49,10 @@ typedef NS_ENUM(NSInteger, DMUserInterfaceStyle) {
|
|||
|
||||
@end
|
||||
|
||||
@interface NSObject (DMTraitEnvironment)
|
||||
|
||||
+ (void)swizzleTraitCollectionDidChangeToDMTraitCollectionDidChange API_AVAILABLE(ios(13.0));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -4,17 +4,131 @@
|
|||
//
|
||||
|
||||
#import "DMTraitCollection.h"
|
||||
#import "UIView+DarkModeKit.h"
|
||||
|
||||
@import ObjectiveC;
|
||||
|
||||
@implementation NSObject (DMTraitEnvironment)
|
||||
|
||||
+ (void)swizzleTraitCollectionDidChangeToDMTraitCollectionDidChange {
|
||||
[self swizzleTraitCollectionDidChangeToDMTraitCollectionDidChangeWithBlock:nil];
|
||||
}
|
||||
|
||||
+ (void)swizzleTraitCollectionDidChangeToDMTraitCollectionDidChangeWithBlock:(void (^)(id<UITraitEnvironment>, UITraitCollection *))block API_AVAILABLE(ios(13.0)) {
|
||||
// Only swizzling classes that conforms to both UITraitEnvironment & DMTraitEnvironment
|
||||
if (!class_conformsToProtocol(self, @protocol(UITraitEnvironment)) || !class_conformsToProtocol(self, @protocol(DMTraitEnvironment))) {
|
||||
return;
|
||||
}
|
||||
|
||||
SEL selector = @selector(traitCollectionDidChange:);
|
||||
Method method = class_getInstanceMethod(self, selector);
|
||||
|
||||
if (!method)
|
||||
NSAssert(NO, @"Method not found for [%@ traitCollectionDidChange:]", NSStringFromClass(self));
|
||||
|
||||
IMP imp = method_getImplementation(method);
|
||||
class_replaceMethod(self, selector, imp_implementationWithBlock(^(id<UITraitEnvironment> self, UITraitCollection *previousTraitCollection) {
|
||||
// Call previous implementation
|
||||
((void (*)(NSObject *, SEL, UITraitCollection *))imp)(self, selector, previousTraitCollection);
|
||||
|
||||
// Call DMTraitEnvironment method
|
||||
[(id <DMTraitEnvironment>)self dmTraitCollectionDidChange:previousTraitCollection == nil ? nil : [DMTraitCollection traitCollectionWithUITraitCollection:previousTraitCollection]];
|
||||
|
||||
// Call custom block
|
||||
if (block) {
|
||||
block(self, previousTraitCollection);
|
||||
}
|
||||
}), method_getTypeEncoding(method));
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation DMTraitCollection
|
||||
|
||||
static DMTraitCollection *_currentTraitCollection = nil;
|
||||
static DMTraitCollection *_overrideTraitCollection = nil; // This is set manually in setCurrentTraitCollection:animated
|
||||
static void (^_userInterfaceStyleChangeHandler)(DMTraitCollection *, BOOL) = nil;
|
||||
|
||||
+ (DMTraitCollection *)currentTraitCollection {
|
||||
return _currentTraitCollection;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
return [DMTraitCollection traitCollectionWithUITraitCollection:UITraitCollection.currentTraitCollection];
|
||||
}
|
||||
return [self overrideTraitCollection];
|
||||
}
|
||||
|
||||
+ (void)setCurrentTraitCollection:(DMTraitCollection *)currentTraitCollection {
|
||||
_currentTraitCollection = currentTraitCollection;
|
||||
+ (DMTraitCollection *)overrideTraitCollection {
|
||||
if (!_overrideTraitCollection) {
|
||||
// Provide unspecified at first
|
||||
_overrideTraitCollection = [DMTraitCollection traitCollectionWithUserInterfaceStyle:DMUserInterfaceStyleUnspecified];
|
||||
}
|
||||
return _overrideTraitCollection;
|
||||
}
|
||||
|
||||
+ (DMTraitCollection *)currentSystemTraitCollection API_AVAILABLE(ios(13.0)) {
|
||||
return [DMTraitCollection traitCollectionWithUITraitCollection:UIScreen.mainScreen.traitCollection];
|
||||
}
|
||||
|
||||
+ (void)setOverrideTraitCollection:(DMTraitCollection *)currentTraitCollection animated:(BOOL)animated {
|
||||
_overrideTraitCollection = currentTraitCollection;
|
||||
[self syncImmediatelyAnimated:animated];
|
||||
}
|
||||
|
||||
+ (void)updateUIWithViews:(NSArray<UIView *> *)views viewControllers:(NSArray<UIViewController *> *)viewControllers traitCollection:(DMTraitCollection *)traitCollection animated:(BOOL)animated {
|
||||
NSMutableArray<UIView *> *snapshotViews = nil;
|
||||
if (animated) {
|
||||
// Create snapshot views to ease the transition
|
||||
snapshotViews = [NSMutableArray array];
|
||||
[views enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
UIView *snapshotView = [view snapshotViewAfterScreenUpdates:NO];
|
||||
if (snapshotView) {
|
||||
[view addSubview:snapshotView];
|
||||
[snapshotViews addObject:snapshotView];
|
||||
}
|
||||
}];
|
||||
[viewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull vc, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (!vc.isViewLoaded)
|
||||
return;
|
||||
|
||||
UIView *snapshotView = [vc.view snapshotViewAfterScreenUpdates:NO];
|
||||
if (snapshotView) {
|
||||
[vc.view addSubview:snapshotView];
|
||||
[snapshotViews addObject:snapshotView];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
[views enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// Let the system propogate the change
|
||||
view.overrideUserInterfaceStyle = traitCollection.uiTraitCollection.userInterfaceStyle;
|
||||
}
|
||||
else {
|
||||
// Propogate the change to subviews
|
||||
[view dmTraitCollectionDidChange:nil];
|
||||
}
|
||||
}];
|
||||
|
||||
[viewControllers enumerateObjectsUsingBlock:^(__kindof UIViewController * _Nonnull vc, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// Let the system propogate the change
|
||||
vc.overrideUserInterfaceStyle = traitCollection.uiTraitCollection.userInterfaceStyle;
|
||||
}
|
||||
else {
|
||||
// Propogate the change to subviews
|
||||
[vc dmTraitCollectionDidChange:nil];
|
||||
}
|
||||
}];
|
||||
|
||||
if (animated) {
|
||||
[UIViewPropertyAnimator runningPropertyAnimatorWithDuration:0.25 delay:0 options:0 animations:^{
|
||||
[snapshotViews enumerateObjectsUsingBlock:^(UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
view.alpha = 0;
|
||||
}];
|
||||
} completion:^(UIViewAnimatingPosition finalPosition) {
|
||||
[snapshotViews enumerateObjectsUsingBlock:^(UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[view removeFromSuperview];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
+ (DMTraitCollection *)traitCollectionWithUserInterfaceStyle:(DMUserInterfaceStyle)userInterfaceStyle {
|
||||
|
@ -23,6 +137,40 @@ static DMTraitCollection *_currentTraitCollection = nil;
|
|||
return traitCollection;
|
||||
}
|
||||
|
||||
+ (DMTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection {
|
||||
DMUserInterfaceStyle style = DMUserInterfaceStyleUnspecified;
|
||||
switch (traitCollection.userInterfaceStyle) {
|
||||
case UIUserInterfaceStyleLight:
|
||||
style = DMUserInterfaceStyleLight;
|
||||
break;
|
||||
case UIUserInterfaceStyleDark:
|
||||
style = DMUserInterfaceStyleDark;
|
||||
break;
|
||||
case UIUserInterfaceStyleUnspecified:
|
||||
default:
|
||||
style = DMUserInterfaceStyleUnspecified;
|
||||
break;
|
||||
}
|
||||
return [self traitCollectionWithUserInterfaceStyle:style];
|
||||
}
|
||||
|
||||
- (UITraitCollection *)uiTraitCollection {
|
||||
UIUserInterfaceStyle style = UIUserInterfaceStyleUnspecified;
|
||||
switch (_userInterfaceStyle) {
|
||||
case DMUserInterfaceStyleLight:
|
||||
style = UIUserInterfaceStyleLight;
|
||||
break;
|
||||
case DMUserInterfaceStyleDark:
|
||||
style = UIUserInterfaceStyleDark;
|
||||
break;
|
||||
case DMUserInterfaceStyleUnspecified:
|
||||
default:
|
||||
style = UIUserInterfaceStyleUnspecified;
|
||||
break;
|
||||
}
|
||||
return [UITraitCollection traitCollectionWithUserInterfaceStyle:style];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
@ -31,4 +179,69 @@ static DMTraitCollection *_currentTraitCollection = nil;
|
|||
return self;
|
||||
}
|
||||
|
||||
// MARK: - Observer Registration
|
||||
+ (void)registerWithApplication:(UIApplication *)application syncImmediately:(BOOL)syncImmediately animated:(BOOL)animated {
|
||||
__weak UIApplication *weakApp = application;
|
||||
_userInterfaceStyleChangeHandler = ^(DMTraitCollection *traitCollection, BOOL animated) {
|
||||
__strong UIApplication *strongApp = weakApp;
|
||||
if (!strongApp)
|
||||
return;
|
||||
|
||||
[self updateUIWithViews:strongApp.windows viewControllers:nil traitCollection:traitCollection animated:animated];
|
||||
};
|
||||
|
||||
if (syncImmediately)
|
||||
[self syncImmediatelyAnimated:animated];
|
||||
}
|
||||
|
||||
+ (void)registerWithViewController:(UIViewController *)viewController syncImmediately:(BOOL)syncImmediately animated:(BOOL)animated {
|
||||
__weak UIViewController *weakVc = viewController;
|
||||
_userInterfaceStyleChangeHandler = ^(DMTraitCollection *traitCollection, BOOL animated) {
|
||||
__strong UIViewController *strongVc = weakVc;
|
||||
if (!strongVc)
|
||||
return;
|
||||
|
||||
[self updateUIWithViews:nil viewControllers:[NSArray arrayWithObject:strongVc] traitCollection:traitCollection animated:animated];
|
||||
};
|
||||
|
||||
if (syncImmediately)
|
||||
[self syncImmediatelyAnimated:animated];
|
||||
}
|
||||
|
||||
+ (void)syncImmediatelyAnimated:(BOOL)animated {
|
||||
if (_userInterfaceStyleChangeHandler)
|
||||
_userInterfaceStyleChangeHandler([self overrideTraitCollection], animated);
|
||||
}
|
||||
|
||||
+ (void)unregister {
|
||||
_userInterfaceStyleChangeHandler = nil;
|
||||
}
|
||||
|
||||
// MARK: - Swizzling
|
||||
+ (void)swizzleUIScreenTraitCollectionDidChange {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
[UIScreen swizzleTraitCollectionDidChangeToDMTraitCollectionDidChangeWithBlock:^(id<UITraitEnvironment> object, UITraitCollection *previousTraitCollection) {
|
||||
if ([DMTraitCollection overrideTraitCollection].userInterfaceStyle != DMUserInterfaceStyleUnspecified) {
|
||||
// User has specified explicit dark mode or light mode
|
||||
return;
|
||||
}
|
||||
|
||||
[self syncImmediatelyAnimated:YES];
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface UIScreen (DMTraitEnvironment) <DMTraitEnvironment>
|
||||
|
||||
- (void)dmTraitCollectionDidChange:(DMTraitCollection *)previousTraitCollection;
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIScreen (DMTraitEnvironment)
|
||||
|
||||
- (void)dmTraitCollectionDidChange:(DMTraitCollection *)previousTraitCollection {}
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,7 +9,12 @@
|
|||
@implementation UIColor (DarkModeKit)
|
||||
|
||||
+ (UIColor *)dm_colorWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor {
|
||||
return (UIColor *)[[DMDynamicColor alloc] initWithLightColor:lightColor darkColor:darkColor];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
|
||||
return traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark ? darkColor : lightColor;
|
||||
}];
|
||||
}
|
||||
return [DMDynamicColor colorWithLightColor:lightColor darkColor:darkColor];
|
||||
}
|
||||
|
||||
+ (UIColor *)dm_namespace:(DMNamespace)namespace
|
||||
|
@ -19,7 +24,12 @@
|
|||
}
|
||||
|
||||
+ (UIColor *)dm_colorWithDynamicProvider:(UIColor *(^)(DMTraitCollection *))dynamicProvider {
|
||||
return (UIColor *)[[DMDynamicColor alloc] initWithDynamicProvider:dynamicProvider];
|
||||
if (@available(iOS 13.0, *)) {
|
||||
return [UIColor colorWithDynamicProvider:^UIColor * _Nonnull(UITraitCollection * _Nonnull traitCollection) {
|
||||
return dynamicProvider([DMTraitCollection traitCollectionWithUITraitCollection:traitCollection]);
|
||||
}];
|
||||
}
|
||||
return [DMDynamicColor colorWithDynamicProvider:dynamicProvider];
|
||||
}
|
||||
|
||||
+ (UIColor *)dm_namespace:(DMNamespace)namespace dynamicProvider:(UIColor *(^)(DMTraitCollection *))dynamicProvider {
|
||||
|
@ -27,9 +37,11 @@
|
|||
}
|
||||
|
||||
- (UIColor *)dm_resolvedColorWithTraitCollection:(DMTraitCollection *)traitCollection {
|
||||
// Here we just need to take care of UIColor that is not DMDynamicColor
|
||||
// since DMDynamicColor methods are all forwarded, simply return self
|
||||
// before we need to bridge iOS 13's color mechanism
|
||||
if (@available(iOS 13.0, *)) {
|
||||
// Here we just need to take care of UIColor that is not DMDynamicColor
|
||||
// since DMDynamicColor methods are all forwarded
|
||||
return [self resolvedColorWithTraitCollection:traitCollection.uiTraitCollection];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,16 +4,23 @@
|
|||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#ifdef SWIFT_PACKAGE
|
||||
#import "DMTraitCollection.h"
|
||||
#else
|
||||
#import <FluentDarkModeKit/DMTraitCollection.h>
|
||||
#endif
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class DMDynamicColor;
|
||||
|
||||
@interface UIView (DarkModeKit)
|
||||
@interface UIView (DarkModeKit) <DMTraitEnvironment>
|
||||
|
||||
+ (void)dm_swizzleSetBackgroundColor;
|
||||
+ (void)dm_swizzleSetTintColor;
|
||||
|
||||
@property (nonatomic, copy, nullable) DMDynamicColor *dm_dynamicBackgroundColor;
|
||||
- (void)dm_updateDynamicColors API_DEPRECATED("dm_updateDynamicColors is deprecated and will not be called on iOS 13.0, use dmTraitCollectionDidChange: instead", ios(11.0, 13.0));;
|
||||
- (void)dm_updateDynamicImages API_DEPRECATED("dm_updateDynamicImages is deprecated and will not be called on iOS 13.0, use dmTraitCollectionDidChange: instead", ios(11.0, 13.0));;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -31,6 +31,27 @@
|
|||
});
|
||||
}
|
||||
|
||||
+ (void)dm_swizzleSetTintColor {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
SEL selector = @selector(setTintColor:);
|
||||
Method method = class_getInstanceMethod(self, selector);
|
||||
if (!method)
|
||||
NSAssert(NO, @"Method not found for [UIView setTintdColor:]");
|
||||
|
||||
IMP imp = method_getImplementation(method);
|
||||
class_replaceMethod(self, selector, imp_implementationWithBlock(^(UIView *self, UIColor *tintColor) {
|
||||
if ([tintColor isKindOfClass:[DMDynamicColor class]]) {
|
||||
self.dm_dynamicTintColor = (DMDynamicColor *)tintColor;
|
||||
}
|
||||
else {
|
||||
self.dm_dynamicTintColor = nil;
|
||||
}
|
||||
((void (*)(UIView *, SEL, UIColor *))imp)(self, selector, tintColor);
|
||||
}), method_getTypeEncoding(method));
|
||||
});
|
||||
}
|
||||
|
||||
- (DMDynamicColor *)dm_dynamicBackgroundColor {
|
||||
return objc_getAssociatedObject(self, _cmd);
|
||||
}
|
||||
|
@ -42,4 +63,44 @@
|
|||
OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
- (DMDynamicColor *)dm_dynamicTintColor {
|
||||
return objc_getAssociatedObject(self, _cmd);
|
||||
}
|
||||
|
||||
- (void)setDm_dynamicTintColor:(DMDynamicColor *)dm_dynamicTintColor {
|
||||
objc_setAssociatedObject(self,
|
||||
@selector(dm_dynamicTintColor),
|
||||
dm_dynamicTintColor,
|
||||
OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
- (void)dmTraitCollectionDidChange:(DMTraitCollection *)previousTraitCollection {
|
||||
if (@available(iOS 13.0, *)) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull view, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[view dmTraitCollectionDidChange:previousTraitCollection];
|
||||
}];
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay];
|
||||
[self dm_updateDynamicColors];
|
||||
[self dm_updateDynamicImages];
|
||||
}
|
||||
|
||||
- (void)dm_updateDynamicColors {
|
||||
UIColor *backgroundColor = [self dm_dynamicBackgroundColor];
|
||||
if (backgroundColor) {
|
||||
[self setBackgroundColor:backgroundColor];
|
||||
}
|
||||
UIColor *tintColor = [self dm_dynamicTintColor];
|
||||
if (tintColor) {
|
||||
[self setTintColor:tintColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dm_updateDynamicImages {
|
||||
// For subclasses to override.
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,29 +9,49 @@ import UIKit
|
|||
#endif
|
||||
|
||||
public final class DarkModeManager: NSObject {
|
||||
public static func setup() {
|
||||
// Colors
|
||||
UIView.swizzleWillMoveToWindowOnce
|
||||
UIView.dm_swizzleSetBackgroundColor()
|
||||
UIView.swizzleSetTintColorOnce
|
||||
UITextField.swizzleTextFieldWillMoveToWindowOnce
|
||||
UILabel.swizzleDidMoveToWindowOnce
|
||||
private static var swizzlingConfigured = false
|
||||
|
||||
// Images
|
||||
UIImage.dm_swizzleIsEqual()
|
||||
UIImageView.swizzleSetImageOnce
|
||||
UIImageView.swizzleInitImageOnce
|
||||
UITabBarItem.swizzleSetImageOnce
|
||||
UITabBarItem.swizzleSetSelectedImageOnce
|
||||
public class func register(with application: UIApplication, syncImmediately: Bool = false, animated: Bool = false) {
|
||||
commonSetup()
|
||||
DMTraitCollection.register(with: application, syncImmediately: syncImmediately, animated: animated)
|
||||
}
|
||||
|
||||
/// Update application's appearance based on current theme (This method is for main app.)
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - application: The application needs to update.
|
||||
/// - animated: Use animation or not.
|
||||
@objc public static func updateAppearance(for application: UIApplication, animated: Bool) {
|
||||
application.updateAppearance(with: application.windows, animated: animated)
|
||||
public class func register(with viewController: UIViewController, syncImmediately: Bool = false, animated: Bool = false) {
|
||||
commonSetup()
|
||||
DMTraitCollection.register(with: viewController, syncImmediately: syncImmediately, animated: animated)
|
||||
}
|
||||
|
||||
public class func unregister() {
|
||||
DMTraitCollection.unregister()
|
||||
}
|
||||
|
||||
private class func commonSetup() {
|
||||
guard !swizzlingConfigured else {
|
||||
return
|
||||
}
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
DMTraitCollection.swizzleUIScreenTraitCollectionDidChange()
|
||||
UIView.swizzleTraitCollectionDidChangeToDMTraitCollectionDidChange()
|
||||
UIViewController.swizzleTraitCollectionDidChangeToDMTraitCollectionDidChange()
|
||||
}
|
||||
else {
|
||||
// Colors
|
||||
UIView.swizzleWillMoveToWindowOnce
|
||||
UIView.dm_swizzleSetBackgroundColor()
|
||||
UIView.dm_swizzleSetTintColor()
|
||||
UITextField.swizzleTextFieldWillMoveToWindowOnce
|
||||
UILabel.swizzleDidMoveToWindowOnce
|
||||
|
||||
// Images
|
||||
UIImage.dm_swizzleIsEqual()
|
||||
UIImageView.swizzleSetImageOnce
|
||||
UIImageView.swizzleInitImageOnce
|
||||
UITabBarItem.swizzleSetImageOnce
|
||||
UITabBarItem.swizzleSetSelectedImageOnce
|
||||
}
|
||||
|
||||
swizzlingConfigured = true
|
||||
}
|
||||
|
||||
// MARK: - Internal
|
||||
|
@ -40,38 +60,3 @@ public final class DarkModeManager: NSObject {
|
|||
return "Method swizzling for theme failed! Class: \(cls), Selector: \(selector)"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension DMTraitEnvironment {
|
||||
/// Trigger `themeDidChange()`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - views: Views visiable by user, will be snapshoted if use animation.
|
||||
/// - animated: Use animation or not.
|
||||
fileprivate func updateAppearance(with views: [UIView], animated: Bool) {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
if animated {
|
||||
var snapshotViews: [UIView] = []
|
||||
views.forEach { view in
|
||||
guard let snapshotView = view.snapshotView(afterScreenUpdates: false) else {
|
||||
return
|
||||
}
|
||||
view.addSubview(snapshotView)
|
||||
snapshotViews.append(snapshotView)
|
||||
}
|
||||
|
||||
dmTraitCollectionDidChange(nil)
|
||||
|
||||
UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.25, delay: 0, options: [], animations: {
|
||||
snapshotViews.forEach { $0.alpha = 0 }
|
||||
}) { _ in
|
||||
snapshotViews.forEach { $0.removeFromSuperview() }
|
||||
}
|
||||
}
|
||||
else {
|
||||
dmTraitCollectionDidChange(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
extension UIApplication: DMTraitEnvironment {
|
||||
open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
windows.forEach { $0.dmTraitCollectionDidChange(previousTraitCollection) }
|
||||
}
|
||||
}
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UIButton {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
[UIControl.State.normal, .highlighted, .disabled, .selected, .focused].forEach { state in
|
||||
if let color = titleColor(for: state)?.copy() as? DynamicColor {
|
||||
|
|
|
@ -51,8 +51,12 @@ extension UIImageView {
|
|||
} as @convention(block) (UIImageView, UIImage?) -> UIImageView), method_getTypeEncoding(method))
|
||||
}()
|
||||
|
||||
override func dm_updateDynamicImages() {
|
||||
super.dm_updateDynamicImages()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
if let dynamicImage = dm_dynamicImage {
|
||||
image = dynamicImage
|
||||
|
|
|
@ -34,6 +34,10 @@ extension UILabel {
|
|||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
guard #available(iOS 12.0, *) else {
|
||||
// Fix for iOS 11.x
|
||||
updateDynamicColorInAttributedText()
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UINavigationBar {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
if let dynamicBarTintColor = barTintColor?.copy() as? DynamicColor {
|
||||
barTintColor = dynamicBarTintColor
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UIPageControl {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
if let dynamicPageIndicatorTintColor = pageIndicatorTintColor?.copy() as? DynamicColor {
|
||||
pageIndicatorTintColor = dynamicPageIndicatorTintColor
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UIProgressView {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
if let dynamicProgressTintColor = progressTintColor?.copy() as? DynamicColor {
|
||||
progressTintColor = dynamicProgressTintColor
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UIScrollView {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
indicatorStyle = {
|
||||
if DMTraitCollection.current.userInterfaceStyle == .dark {
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UISlider {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
if let dynamicMinimumTrackTintColor = minimumTrackTintColor?.copy() as? DynamicColor {
|
||||
minimumTrackTintColor = dynamicMinimumTrackTintColor
|
||||
|
|
|
@ -6,11 +6,12 @@
|
|||
extension UITabBar {
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
items?.forEach { $0.dmTraitCollectionDidChange(previousTraitCollection) }
|
||||
}
|
||||
|
||||
override func dm_updateDynamicImages() {
|
||||
super.dm_updateDynamicImages()
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
items?.forEach { $0.dmTraitCollectionDidChange(previousTraitCollection) }
|
||||
items?.forEach { $0._updateDynamicImages() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UITableView {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
if let dynamicSectionIndexColor = sectionIndexColor?.copy() as? DynamicColor {
|
||||
sectionIndexColor = dynamicSectionIndexColor
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UITextField {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
if let dynamicTextColor = textColor?.copy() as? DynamicColor {
|
||||
textColor = dynamicTextColor
|
||||
|
@ -36,10 +42,7 @@ extension UITextField {
|
|||
class_replaceMethod(UITextField.self, selector, imp_implementationWithBlock({ (self: UITextField, window: UIWindow?) -> Void in
|
||||
let oldIMP = unsafeBitCast(imp, to: (@convention(c) (UITextField, Selector, UIWindow?) -> Void).self)
|
||||
oldIMP(self, selector, window)
|
||||
if window != nil {
|
||||
self.dm_updateDynamicColors()
|
||||
self.dm_updateDynamicImages()
|
||||
}
|
||||
self.dmTraitCollectionDidChange(nil)
|
||||
} as @convention(block) (UITextField, UIWindow?) -> Void), method_getTypeEncoding(method))
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UITextView {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
keyboardAppearance = {
|
||||
if DMTraitCollection.current.userInterfaceStyle == .dark {
|
||||
|
|
|
@ -4,8 +4,14 @@
|
|||
//
|
||||
|
||||
extension UIToolbar {
|
||||
override func dm_updateDynamicColors() {
|
||||
super.dm_updateDynamicColors()
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
dm_updateDynamicColors()
|
||||
|
||||
if let dynamicBarTintColor = barTintColor?.copy() as? DynamicColor {
|
||||
barTintColor = dynamicBarTintColor
|
||||
|
|
|
@ -3,29 +3,6 @@
|
|||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
extension UIView: DMTraitEnvironment {
|
||||
open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
subviews.forEach { $0.dmTraitCollectionDidChange(previousTraitCollection) }
|
||||
setNeedsLayout()
|
||||
setNeedsDisplay()
|
||||
dm_updateDynamicColors()
|
||||
dm_updateDynamicImages()
|
||||
}
|
||||
|
||||
@objc func dm_updateDynamicColors() {
|
||||
if let dynamicBackgroundColor = dm_dynamicBackgroundColor {
|
||||
backgroundColor = dynamicBackgroundColor
|
||||
}
|
||||
if let dynamicTintColor = dm_dynamicTintColor {
|
||||
tintColor = dynamicTintColor
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dm_updateDynamicImages() {
|
||||
// For subclasses to override.
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
static let swizzleWillMoveToWindowOnce: Void = {
|
||||
let selector = #selector(willMove(toWindow:))
|
||||
|
@ -45,29 +22,3 @@ extension UIView {
|
|||
} as @convention(block) (UIView, UIWindow?) -> Void), method_getTypeEncoding(method))
|
||||
}()
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
private struct Constants {
|
||||
static var dynamicTintColorKey = "dynamicTintColorKey"
|
||||
}
|
||||
|
||||
static let swizzleSetTintColorOnce: Void = {
|
||||
let selector = #selector(setter: tintColor)
|
||||
guard let method = class_getInstanceMethod(UIView.self, selector) else {
|
||||
assertionFailure(DarkModeManager.messageForSwizzlingFailed(class: UIView.self, selector: selector))
|
||||
return
|
||||
}
|
||||
|
||||
let imp = method_getImplementation(method)
|
||||
class_replaceMethod(UIView.self, selector, imp_implementationWithBlock({ (self: UIView, tintColor: UIColor) -> Void in
|
||||
self.dm_dynamicTintColor = tintColor as? DynamicColor
|
||||
let oldIMP = unsafeBitCast(imp, to: (@convention(c) (UIView, Selector, UIColor) -> Void).self)
|
||||
oldIMP(self, selector, tintColor)
|
||||
} as @convention(block) (UIView, UIColor) -> Void), method_getTypeEncoding(method))
|
||||
}()
|
||||
|
||||
private var dm_dynamicTintColor: DynamicColor? {
|
||||
get { return objc_getAssociatedObject(self, &Constants.dynamicTintColorKey) as? DynamicColor }
|
||||
set { objc_setAssociatedObject(self, &Constants.dynamicTintColorKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
|
||||
extension UIViewController: DMTraitEnvironment {
|
||||
open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
setNeedsStatusBarAppearanceUpdate()
|
||||
presentedViewController?.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
children.forEach { $0.dmTraitCollectionDidChange(previousTraitCollection) }
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
extension UIWindow {
|
||||
override open func dmTraitCollectionDidChange(_ previousTraitCollection: DMTraitCollection?) {
|
||||
super.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
return
|
||||
}
|
||||
|
||||
rootViewController?.dmTraitCollectionDidChange(previousTraitCollection)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||
) -> Bool {
|
||||
DarkModeManager.setup()
|
||||
|
||||
DarkModeManager.register(with: application)
|
||||
|
||||
window = UIWindow()
|
||||
window?.rootViewController = {
|
||||
|
|
|
@ -18,12 +18,44 @@ class ViewController: UIViewController {
|
|||
}
|
||||
|
||||
@objc private func refresh() {
|
||||
if DMTraitCollection.current.userInterfaceStyle == .dark {
|
||||
DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .light)
|
||||
// Loop throught the available styles
|
||||
DMTraitCollection.setOverride(DMTraitCollection(userInterfaceStyle: DMTraitCollection.override.userInterfaceStyle.next), animated: true)
|
||||
showUserSetInterfaceStyle()
|
||||
}
|
||||
|
||||
private func showUserSetInterfaceStyle() {
|
||||
let alert = UIAlertController(title: DMTraitCollection.override.userInterfaceStyle.description, message: nil, preferredStyle: .alert)
|
||||
if alert.popoverPresentationController != nil {
|
||||
alert.popoverPresentationController?.sourceRect = .zero
|
||||
alert.popoverPresentationController?.sourceView = view
|
||||
}
|
||||
else {
|
||||
DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .dark)
|
||||
present(alert, animated: true, completion: nil)
|
||||
Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { [weak self] _ in
|
||||
self?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension DMUserInterfaceStyle {
|
||||
var description: String {
|
||||
switch self {
|
||||
case .dark:
|
||||
return "dark"
|
||||
case .light:
|
||||
return "light"
|
||||
default:
|
||||
return "unspecified"
|
||||
}
|
||||
}
|
||||
|
||||
var next: DMUserInterfaceStyle {
|
||||
switch self {
|
||||
case .light:
|
||||
return .dark
|
||||
case .dark:
|
||||
return .unspecified
|
||||
default:
|
||||
return .light
|
||||
}
|
||||
DarkModeManager.updateAppearance(for: .shared, animated: true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,18 +9,20 @@ import XCTest
|
|||
final class DarkModeKitTests: XCTestCase {
|
||||
func testSetBackgroundColorSwizzling() {
|
||||
UIWindow.appearance().backgroundColor = .white
|
||||
DarkModeManager.setup()
|
||||
DarkModeManager.register(with: UIApplication.shared)
|
||||
_ = UIWindow()
|
||||
}
|
||||
|
||||
func testColorInitializer() {
|
||||
let color = UIColor(.dm, light: .white, dark: .black)
|
||||
|
||||
DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .light)
|
||||
XCTAssertEqual(color.rgba, UIColor.white.rgba)
|
||||
perform(with: .light) {
|
||||
XCTAssertEqual(color.rgba, UIColor.white.rgba)
|
||||
}
|
||||
|
||||
DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .dark)
|
||||
XCTAssertEqual(color.rgba, UIColor.black.rgba)
|
||||
perform(with: .dark) {
|
||||
XCTAssertEqual(color.rgba, UIColor.black.rgba)
|
||||
}
|
||||
}
|
||||
|
||||
func testImageInitializer() {
|
||||
|
@ -34,11 +36,13 @@ final class DarkModeKitTests: XCTestCase {
|
|||
$0.userInterfaceStyle == .dark ? UIColor.black : UIColor.white
|
||||
}
|
||||
|
||||
DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .light)
|
||||
XCTAssertEqual(color.rgba, UIColor.white.rgba)
|
||||
perform(with: .light) {
|
||||
XCTAssertEqual(color.rgba, UIColor.white.rgba)
|
||||
}
|
||||
|
||||
DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .dark)
|
||||
XCTAssertEqual(color.rgba, UIColor.black.rgba)
|
||||
perform(with: .dark) {
|
||||
XCTAssertEqual(color.rgba, UIColor.black.rgba)
|
||||
}
|
||||
|
||||
// Test color fetched from specific trait collections
|
||||
XCTAssertEqual(color.resolvedColor(.dm, with: DMTraitCollection(userInterfaceStyle: .dark)).rgba, UIColor.black.rgba)
|
||||
|
@ -51,7 +55,12 @@ final class DarkModeKitTests: XCTestCase {
|
|||
let view = UIView()
|
||||
view.backgroundColor = color
|
||||
view.tintColor = color
|
||||
XCTAssertFalse(view.backgroundColor === color)
|
||||
if #available(iOS 13.0, *) {
|
||||
XCTAssertTrue(view.backgroundColor === color)
|
||||
}
|
||||
else {
|
||||
XCTAssertFalse(view.backgroundColor === color)
|
||||
}
|
||||
XCTAssertTrue(view.tintColor === color)
|
||||
|
||||
// UIView subclasses
|
||||
|
@ -110,7 +119,12 @@ final class DarkModeKitTests: XCTestCase {
|
|||
label.shadowColor = color
|
||||
label.highlightedTextColor = color
|
||||
XCTAssertTrue(label.textColor === color)
|
||||
XCTAssertFalse(label.shadowColor === color)
|
||||
if #available(iOS 13.0, *) {
|
||||
XCTAssertTrue(label.shadowColor === color)
|
||||
}
|
||||
else {
|
||||
XCTAssertFalse(label.shadowColor === color)
|
||||
}
|
||||
XCTAssertTrue(label.highlightedTextColor === color)
|
||||
|
||||
let navigationBar = UINavigationBar()
|
||||
|
@ -155,6 +169,23 @@ final class DarkModeKitTests: XCTestCase {
|
|||
XCTAssertTrue(toolbar.barTintColor === color)
|
||||
}
|
||||
}
|
||||
|
||||
func perform(with userInterfaceStyle: DMUserInterfaceStyle, expression: () -> Void) {
|
||||
if #available(iOS 13.0, *) {
|
||||
// On iOS 13, we use the system wide one, while in unit tests there is
|
||||
// no actual views, use UITraitCollection.performAsCurrent to simulate
|
||||
// theme change
|
||||
DMTraitCollection(userInterfaceStyle: userInterfaceStyle).uiTraitCollection.performAsCurrent {
|
||||
expression()
|
||||
}
|
||||
}
|
||||
else {
|
||||
let saved = DMTraitCollection.current
|
||||
DMTraitCollection.setOverride(DMTraitCollection(userInterfaceStyle: userInterfaceStyle), animated: false)
|
||||
expression()
|
||||
DMTraitCollection.setOverride(saved, animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
|
|
|
@ -35,21 +35,25 @@ final class DarkModeKitUITests: XCTestCase {
|
|||
func _test(_ className: String) {
|
||||
let app = XCUIApplication()
|
||||
let refreshButton = app.navigationBars["FluentDarkModeKitExample.MainView"].buttons["Refresh"]
|
||||
refreshButton.tap()
|
||||
refreshButton.tap() // light mode
|
||||
refreshButton.tap() // dark mode
|
||||
|
||||
let uiviewStaticText = app.tables.staticTexts[className]
|
||||
uiviewStaticText.tap()
|
||||
|
||||
sleep(1)
|
||||
|
||||
let screenshot1 = app.screenshot()
|
||||
|
||||
app.navigationBars["FluentDarkModeKitExample.\(className)VC"].buttons["Back"].tap()
|
||||
refreshButton.tap()
|
||||
refreshButton.tap() // unspecified
|
||||
refreshButton.tap() // light mode
|
||||
uiviewStaticText.tap()
|
||||
|
||||
let tabBarsQuery = app.tabBars
|
||||
tabBarsQuery.children(matching: .button).element(boundBy: 1).tap()
|
||||
app.navigationBars["FluentDarkModeKitExample.View"].buttons["Refresh"].tap()
|
||||
tabBarsQuery.children(matching: .button).element(boundBy: 0).tap()
|
||||
tabBarsQuery.children(matching: .button).element(boundBy: 0).tap() // dark mode
|
||||
|
||||
let screenshot2 = app.screenshot()
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче