diff --git a/DarkModeKit.xcodeproj/project.pbxproj b/DarkModeKit.xcodeproj/project.pbxproj index 415a468..26d65d8 100644 --- a/DarkModeKit.xcodeproj/project.pbxproj +++ b/DarkModeKit.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 8CAFD9E723716054001A63B8 /* DarkModeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C1915CE2361EFDB004A606A /* DarkModeKit.framework */; }; 8CAFD9E823716054001A63B8 /* DarkModeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8C1915CE2361EFDB004A606A /* DarkModeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8CAFD9ED2371606D001A63B8 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CAFD9EC2371606D001A63B8 /* NavigationController.swift */; }; + 8CB63E41238551F3008ABCE2 /* UIView+DarkModeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CB63E3F238551F3008ABCE2 /* UIView+DarkModeKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8CB63E42238551F3008ABCE2 /* UIView+DarkModeKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CB63E40238551F3008ABCE2 /* UIView+DarkModeKit.m */; }; 8CDA628C2366DAA9004895B5 /* DarkModeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CDA62702366DAA9004895B5 /* DarkModeKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8CDA628D2366DAA9004895B5 /* DMTraitCollection.h in Headers */ = {isa = PBXBuildFile; fileRef = 8CDA62712366DAA9004895B5 /* DMTraitCollection.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8CDA628E2366DAA9004895B5 /* DMTraitCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 8CDA62722366DAA9004895B5 /* DMTraitCollection.m */; }; @@ -91,6 +93,8 @@ 8CAFD9E023715FAB001A63B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 8CAFD9E223715FAB001A63B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8CAFD9EC2371606D001A63B8 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; + 8CB63E3F238551F3008ABCE2 /* UIView+DarkModeKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+DarkModeKit.h"; sourceTree = ""; }; + 8CB63E40238551F3008ABCE2 /* UIView+DarkModeKit.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+DarkModeKit.m"; sourceTree = ""; }; 8CDA62702366DAA9004895B5 /* DarkModeKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DarkModeKit.h; sourceTree = ""; }; 8CDA62712366DAA9004895B5 /* DMTraitCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DMTraitCollection.h; sourceTree = ""; }; 8CDA62722366DAA9004895B5 /* DMTraitCollection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DMTraitCollection.m; sourceTree = ""; }; @@ -175,6 +179,8 @@ 8CDA62722366DAA9004895B5 /* DMTraitCollection.m */, 8C071AEA23683BF5001AB7B2 /* NSObject+DarkModeKit.h */, 8C071AEB23683BF5001AB7B2 /* NSObject+DarkModeKit.m */, + 8CB63E3F238551F3008ABCE2 /* UIView+DarkModeKit.h */, + 8CB63E40238551F3008ABCE2 /* UIView+DarkModeKit.m */, ); path = DarkModeCore; sourceTree = ""; @@ -272,6 +278,7 @@ 8C071AEC23683BF5001AB7B2 /* NSObject+DarkModeKit.h in Headers */, 8CDA628D2366DAA9004895B5 /* DMTraitCollection.h in Headers */, 8CDA628C2366DAA9004895B5 /* DarkModeKit.h in Headers */, + 8CB63E41238551F3008ABCE2 /* UIView+DarkModeKit.h in Headers */, 8CDA62912366DAA9004895B5 /* DMDynamicImage.h in Headers */, 8CDA62A52366DAA9004895B5 /* DMDynamicColor.h in Headers */, ); @@ -409,6 +416,7 @@ buildActionMask = 2147483647; files = ( 8CDA62972366DAA9004895B5 /* UIScrollView+Theme.swift in Sources */, + 8CB63E42238551F3008ABCE2 /* UIView+DarkModeKit.m in Sources */, 8CDA62A12366DAA9004895B5 /* UITextField+Theme.swift in Sources */, 8CDA62952366DAA9004895B5 /* UIWindow+Theme.swift in Sources */, 8CDA629E2366DAA9004895B5 /* UIViewController+Theme.swift in Sources */, diff --git a/Sources/DarkModeCore/DarkModeKit.h b/Sources/DarkModeCore/DarkModeKit.h index 0cd1b61..7bd93bf 100644 --- a/Sources/DarkModeCore/DarkModeKit.h +++ b/Sources/DarkModeCore/DarkModeKit.h @@ -7,6 +7,7 @@ #import #import #import +#import //! Project version number for DarkModeKit. FOUNDATION_EXPORT double DarkModeKitVersionNumber; diff --git a/Sources/DarkModeCore/UIView+DarkModeKit.h b/Sources/DarkModeCore/UIView+DarkModeKit.h new file mode 100644 index 0000000..6f2be65 --- /dev/null +++ b/Sources/DarkModeCore/UIView+DarkModeKit.h @@ -0,0 +1,20 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class DMDynamicColor; + +@interface UIView (DarkModeKit) + ++ (void)dm_swizzleSetBackgroundColor; + +@property (nonatomic, copy, nullable) DMDynamicColor *dm_dynamicBackgroundColor; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sources/DarkModeCore/UIView+DarkModeKit.m b/Sources/DarkModeCore/UIView+DarkModeKit.m new file mode 100644 index 0000000..2baf65c --- /dev/null +++ b/Sources/DarkModeCore/UIView+DarkModeKit.m @@ -0,0 +1,45 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// + +#import "UIView+DarkModeKit.h" +#import "DMDynamicColor.h" + +@import ObjectiveC; + +@implementation UIView (DarkModeKit) + +static void (*dm_original_setBackgroundColor)(UIView *, SEL, UIColor *); + +static void dm_setBackgroundColor(UIView *self, SEL _cmd, UIColor *color) { + if ([color isKindOfClass:[DMDynamicColor class]]) { + self.dm_dynamicBackgroundColor = (DMDynamicColor *)color; + } else { + self.dm_dynamicBackgroundColor = nil; + } + dm_original_setBackgroundColor(self, _cmd, color); +} + +// https://stackoverflow.com/questions/42677534/swizzling-on-properties-that-conform-to-ui-appearance-selector ++ (void)dm_swizzleSetBackgroundColor { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Method method = class_getInstanceMethod(self, @selector(setBackgroundColor:)); + dm_original_setBackgroundColor = (void *)method_getImplementation(method); + method_setImplementation(method, (IMP)dm_setBackgroundColor); + }); +} + +- (DMDynamicColor *)dm_dynamicBackgroundColor { + return objc_getAssociatedObject(self, _cmd); +} + +- (void)setDm_dynamicBackgroundColor:(DMDynamicColor *)dm_dynamicBackgroundColor { + objc_setAssociatedObject(self, + @selector(dm_dynamicBackgroundColor), + dm_dynamicBackgroundColor, + OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +@end diff --git a/Sources/DarkModeKit/DarkModeManager.swift b/Sources/DarkModeKit/DarkModeManager.swift index b1e4b0f..c6eb4d2 100644 --- a/Sources/DarkModeKit/DarkModeManager.swift +++ b/Sources/DarkModeKit/DarkModeManager.swift @@ -38,7 +38,7 @@ public final class DarkModeManager: NSObject { // Colors UIView.swizzleWillMoveToWindowOnce - UIView.swizzleSetBackgroundColorOnce + UIView.dm_swizzleSetBackgroundColor() UIView.swizzleSetTintColorOnce UITextField.swizzleTextFieldWillMoveToWindowOnce UILabel.swizzleDidMoveToWindowOnce diff --git a/Sources/DarkModeKit/ThemeableExtensions/UIView+Theme.swift b/Sources/DarkModeKit/ThemeableExtensions/UIView+Theme.swift index 3d7d2d0..19718ca 100644 --- a/Sources/DarkModeKit/ThemeableExtensions/UIView+Theme.swift +++ b/Sources/DarkModeKit/ThemeableExtensions/UIView+Theme.swift @@ -17,7 +17,7 @@ extension UIView: Themeable { } @objc func _updateDynamicColors() { - if let dynamicBackgroundColor = _dynamicBackgroundColor { + if let dynamicBackgroundColor = dm_dynamicBackgroundColor { backgroundColor = dynamicBackgroundColor } if let dynamicTintColor = _dynamicTintColor { @@ -48,37 +48,20 @@ extension UIView { extension UIView { private struct Constants { - static var dynamicBackgroundColorKey = "dynamicBackgroundColorKey" static var dynamicTintColorKey = "dynamicTintColorKey" } - static let swizzleSetBackgroundColorOnce: Void = { - if !dm_swizzleInstanceMethod(#selector(setter: backgroundColor), to: #selector(outlookSetBackgroundColor)) { - assertionFailure(DarkModeManager.messageForSwizzlingFailed(class: UIView.self, selector: #selector(setter: backgroundColor))) - } - }() - static let swizzleSetTintColorOnce: Void = { if !dm_swizzleInstanceMethod(#selector(setter: tintColor), to: #selector(outlookSetTintColor)) { assertionFailure(DarkModeManager.messageForSwizzlingFailed(class: UIView.self, selector: #selector(setter: tintColor))) } }() - private var _dynamicBackgroundColor: DynamicColor? { - get { return objc_getAssociatedObject(self, &Constants.dynamicBackgroundColorKey) as? DynamicColor } - set { objc_setAssociatedObject(self, &Constants.dynamicBackgroundColorKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) } - } - private var _dynamicTintColor: DynamicColor? { get { return objc_getAssociatedObject(self, &Constants.dynamicTintColorKey) as? DynamicColor } set { objc_setAssociatedObject(self, &Constants.dynamicTintColorKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) } } - @objc private dynamic func outlookSetBackgroundColor(_ color: UIColor) { - _dynamicBackgroundColor = color as? DynamicColor - outlookSetBackgroundColor(color) - } - @objc private dynamic func outlookSetTintColor(_ color: UIColor) { _dynamicTintColor = color as? DynamicColor outlookSetTintColor(color) diff --git a/Tests/DarkModeKitTests/DarkModeKitTests.swift b/Tests/DarkModeKitTests/DarkModeKitTests.swift index 831c20a..ca78fbc 100644 --- a/Tests/DarkModeKitTests/DarkModeKitTests.swift +++ b/Tests/DarkModeKitTests/DarkModeKitTests.swift @@ -6,4 +6,10 @@ import XCTest @testable import DarkModeKit -final class DarkModeKitTests: XCTestCase {} +final class DarkModeKitTests: XCTestCase { + func testSetBackgroundColorSwizzling() { + UIWindow.appearance().backgroundColor = .white + DarkModeManager.setup(updateAppearance: { _ in }) + _ = UIWindow() + } +}