From 13e0d9db28b5b41668f050518c6aaaf369d995a3 Mon Sep 17 00:00:00 2001 From: Levin Li Date: Thu, 11 Jun 2020 11:11:04 +0800 Subject: [PATCH] Dynamic color implementation (#76) --- Sources/DarkModeCore/DMDynamicColor.h | 6 +++ Sources/DarkModeCore/DMDynamicColor.m | 52 ++++++++++++++----- Sources/DarkModeCore/UIColor+DarkModeKit.h | 9 ++++ Sources/DarkModeCore/UIColor+DarkModeKit.m | 19 +++++++ .../DarkModeKitTests.swift | 16 ++++++ 5 files changed, 89 insertions(+), 13 deletions(-) diff --git a/Sources/DarkModeCore/DMDynamicColor.h b/Sources/DarkModeCore/DMDynamicColor.h index 392f899..9eaf68a 100644 --- a/Sources/DarkModeCore/DMDynamicColor.h +++ b/Sources/DarkModeCore/DMDynamicColor.h @@ -4,6 +4,11 @@ // @import UIKit; +#ifdef SWIFT_PACKAGE +#import "DMTraitCollection.h" +#else +#import +#endif NS_ASSUME_NONNULL_BEGIN @@ -14,6 +19,7 @@ NS_SWIFT_NAME(DynamicColor) @property (nonatomic, readonly) UIColor *darkColor; - (instancetype)initWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor; +- (instancetype)initWithDynamicProvider:(UIColor * (^)(DMTraitCollection *traitCollection))dynamicProvider; @end diff --git a/Sources/DarkModeCore/DMDynamicColor.m b/Sources/DarkModeCore/DMDynamicColor.m index 6a7ee5e..30d912e 100644 --- a/Sources/DarkModeCore/DMDynamicColor.m +++ b/Sources/DarkModeCore/DMDynamicColor.m @@ -4,12 +4,11 @@ // #import "DMDynamicColor.h" -#import "DMTraitCollection.h" +#import "DMNamespace.h" @interface DMDynamicColorProxy : NSProxy -@property (nonatomic, strong) UIColor *lightColor; -@property (nonatomic, strong) UIColor *darkColor; +@property (nonatomic, strong) UIColor *(^dynamicProvider)(DMTraitCollection *); @property (nonatomic, readonly) UIColor *resolvedColor; @@ -19,25 +18,48 @@ // TODO: We need a more generic initializer. - (instancetype)initWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor { - self.lightColor = lightColor; - self.darkColor = darkColor; + return [self initWithDynamicProvider:^(DMTraitCollection *traitCollection){ + return traitCollection.userInterfaceStyle == DMUserInterfaceStyleDark ? darkColor : lightColor; + }]; +} +- (instancetype)initWithDynamicProvider:(UIColor * (^)(DMTraitCollection *traitCollection))dynamicProvider { + self.dynamicProvider = dynamicProvider; return self; } - (UIColor *)resolvedColor { - if (DMTraitCollection.currentTraitCollection.userInterfaceStyle == DMUserInterfaceStyleDark) { - return self.darkColor; - } else { - return self.lightColor; - } + return [self resolvedColorWithTraitCollection:DMTraitCollection.currentTraitCollection]; +} + +- (UIColor *)resolvedColorWithTraitCollection:(DMTraitCollection *)traitCollection { + return self.dynamicProvider(traitCollection); } // MARK: UIColor - (UIColor *)colorWithAlphaComponent:(CGFloat)alpha { - return [[DMDynamicColor alloc] initWithLightColor:[self.lightColor colorWithAlphaComponent:alpha] - darkColor:[self.darkColor colorWithAlphaComponent:alpha]]; + return [[DMDynamicColor alloc] initWithDynamicProvider:^UIColor *(DMTraitCollection *traitCollection) { + return [self.dynamicProvider(traitCollection) colorWithAlphaComponent:alpha]; + }]; +} + +// MARK: Methods that do not need forwarding + +- (UIColor *)lightColor { + return [self resolvedColorWithTraitCollection:[DMTraitCollection traitCollectionWithUserInterfaceStyle:DMUserInterfaceStyleLight]]; +} + +- (UIColor *)darkColor { + return [self resolvedColorWithTraitCollection:[DMTraitCollection traitCollectionWithUserInterfaceStyle:DMUserInterfaceStyleDark]]; +} + +- (UIColor *)dm_resolvedColorWithTraitCollection:(DMTraitCollection *)traitCollection { + return [self resolvedColorWithTraitCollection:traitCollection];; +} + +- (UIColor *)dm_namespace:(DMNamespace)namespace resolvedColorWithTraitCollection:(DMTraitCollection *)traitCollection { + return [self dm_resolvedColorWithTraitCollection:traitCollection]; } // MARK: NSProxy @@ -68,7 +90,7 @@ } - (id)copyWithZone:(NSZone *)zone { - return [[DMDynamicColorProxy alloc] initWithLightColor:self.lightColor darkColor:self.darkColor]; + return [[DMDynamicColorProxy alloc] initWithDynamicProvider:[self.dynamicProvider copy]]; } @end @@ -81,6 +103,10 @@ return (DMDynamicColor *)[[DMDynamicColorProxy alloc] initWithLightColor:lightColor darkColor:darkColor]; } +- (instancetype)initWithDynamicProvider:(UIColor * _Nonnull (^)(DMTraitCollection * _Nonnull))dynamicProvider { + return (DMDynamicColor *)[[DMDynamicColorProxy alloc] initWithDynamicProvider:dynamicProvider]; +} + - (UIColor *)lightColor { NSAssert(NO, @"This should never be called"); return nil; diff --git a/Sources/DarkModeCore/UIColor+DarkModeKit.h b/Sources/DarkModeCore/UIColor+DarkModeKit.h index 0de1239..cf9a340 100644 --- a/Sources/DarkModeCore/UIColor+DarkModeKit.h +++ b/Sources/DarkModeCore/UIColor+DarkModeKit.h @@ -6,8 +6,10 @@ #import #ifdef SWIFT_PACKAGE #import "DMNamespace.h" +#import "DMTraitCollection.h" #else #import +#import #endif NS_ASSUME_NONNULL_BEGIN @@ -16,11 +18,18 @@ NS_ASSUME_NONNULL_BEGIN + (UIColor *)dm_colorWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor NS_SWIFT_UNAVAILABLE("Use init(_:light:dark:) instead."); ++ (UIColor *)dm_colorWithDynamicProvider:(UIColor * (^)(DMTraitCollection *traitCollection))dynamicProvider +NS_SWIFT_UNAVAILABLE("Use init(_:dynamicProvider:) instead."); +- (UIColor *)dm_resolvedColorWithTraitCollection:(DMTraitCollection *)traitCollection +NS_SWIFT_UNAVAILABLE("Use resolvedColor(_:with:) instead."); #if __swift__ + (UIColor *)dm_namespace:(DMNamespace)namespace colorWithLightColor:(UIColor *)lightColor darkColor:(UIColor *)darkColor NS_SWIFT_NAME(init(_:light:dark:)); ++ (UIColor *)dm_namespace:(DMNamespace)namespace + dynamicProvider:(UIColor *(^)(DMTraitCollection *traitCollection))dynamicProvider NS_SWIFT_NAME(init(_:dynamicProvider:)); +- (UIColor *)dm_namespace:(DMNamespace)namespace resolvedColorWithTraitCollection:(DMTraitCollection *)traitCollection NS_SWIFT_NAME(resolvedColor(_:with:)); #endif @end diff --git a/Sources/DarkModeCore/UIColor+DarkModeKit.m b/Sources/DarkModeCore/UIColor+DarkModeKit.m index 11a6261..dd0c859 100644 --- a/Sources/DarkModeCore/UIColor+DarkModeKit.m +++ b/Sources/DarkModeCore/UIColor+DarkModeKit.m @@ -18,4 +18,23 @@ return [UIColor dm_colorWithLightColor:lightColor darkColor:darkColor]; } ++ (UIColor *)dm_colorWithDynamicProvider:(UIColor *(^)(DMTraitCollection *))dynamicProvider { + return (UIColor *)[[DMDynamicColor alloc] initWithDynamicProvider:dynamicProvider]; +} + ++ (UIColor *)dm_namespace:(DMNamespace)namespace dynamicProvider:(UIColor *(^)(DMTraitCollection *))dynamicProvider { + return [self dm_colorWithDynamicProvider:dynamicProvider]; +} + +- (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 + return self; +} + +- (UIColor *)dm_namespace:(DMNamespace)namespace resolvedColorWithTraitCollection:(DMTraitCollection *)traitCollection { + return [self dm_resolvedColorWithTraitCollection:traitCollection]; +} + @end diff --git a/Tests/FluentDarkModeKitTests/DarkModeKitTests.swift b/Tests/FluentDarkModeKitTests/DarkModeKitTests.swift index ad8d98f..1f2111c 100644 --- a/Tests/FluentDarkModeKitTests/DarkModeKitTests.swift +++ b/Tests/FluentDarkModeKitTests/DarkModeKitTests.swift @@ -29,6 +29,22 @@ final class DarkModeKitTests: XCTestCase { _ = UIImage(.dm, light: lightImage, dark: darkImage) } + func testDynamicColor() { + let color = UIColor(.dm) { + $0.userInterfaceStyle == .dark ? UIColor.black : UIColor.white + } + + DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .light) + XCTAssertEqual(color.rgba, UIColor.white.rgba) + + DMTraitCollection.current = DMTraitCollection(userInterfaceStyle: .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) + XCTAssertEqual(color.resolvedColor(.dm, with: DMTraitCollection(userInterfaceStyle: .light)).rgba, UIColor.white.rgba) + } + func testColorPropertySetters() { let color = UIColor(.dm, light: .white, dark: .black)