In many of the software that reads or needs to be watched at night, the nightly pattern is a feature that the APP needs. And how to add the night mode gracefully to the application without changing the original architecture or even changing the original code is an issue that has to be faced in many application development processes.
It is the driving of these things that makes me think about how to use an elegant and concise way to solve this problem.
And Dknightversion is the solution I bring.
So far, most of the work on this framework has been done, and perhaps it is not perfect now, but I will continue to maintain this framework to help engineers who are suffering from night-time patterns to solve this hole's demand.
Realize
Now I finally have time to water a water to write a blog about how this framework implements night mode, and what features it has.
For a long time I was wondering how to UIKit
add Night mode to the IOS App without having to override the controls. and objc/runtime
I have the hope that I UIKit
can achieve this goal without the written words.
Adding a UIKit control
nightColor
Property
Because we do not CBD the UIKit control, and then use the @property
Add property for its subclasses. Instead, use the Magic category in Objective-c and objc/runtime
add properties to the controls for the UI family.
Use objc/runtime
to add attributes to a category it is believed that many people know and are often used in development. If you don't know, you can look here.
Dknightversion for most commonly used color
, say: backgroundColor
tintColor
all added to the night
beginning of the night mode in the color nightBackgroundColor
nightTintColor
.
-(Uicolor*) Nightbackgroundcolor{ReturnObjc_getassociatedobject(Self,&nightbackgroundcolorkey)?:Self. backgroundcolor);}- (void) Setnightbackgroundcolor : (Uicolor *{ Objc_setassociatedobject (self&nightbackgroundcolorkey ;}
We create this property to save the color in the night mode so that when the theme of the app is switched to night mode, the nightColor
property stores the color assigned to the corresponding color
, but there is a problem. When the app is re-switched back to normal mode, we lose the original normal mode color
.
Add to
normalColor
Store color
To solve this problem, we added another property for the UIKit control normalColor
to hold the color in normal mode.
-(Uicolor*) Normalbackgroundcolor{ReturnObjc_getassociatedobject(Self,&normalbackgroundcolorkey);}- (void) Setnormalbackgroundcolor : (Uicolor *{ Objc_setassociatedobject (self&normalbackgroundcolorkey ;}
But the time to save this color is very important, at the very beginning, my choice is to overwrite the setter
method, save the before saving the color; normalcolor
.
-void" Setbackgroundcolor: (UIColor *) backgroundcolor { self.normalbackgroundcolor = backgroundcolor = Backgroundcolor;}
However, this seemingly operational results in the setter
view will not be colored, the settings color
including normal color will not have any reaction, but the background color of the view is dark.
Since this method does not work, I would like to use the Observer pattern to store normalColor
, register the instance itself as an color
observer of the property, when the color
property changes, notify the UIKit control itself, and then save the property to the normalColor
property.
But the question of when to register myself as an observer has made me abandon this solution. Finally, the method is chosen to solve color
the original storage problem.
Use the method to add hooks to the original property, setter
store the property before the method call, and normal
assign a value to the property when switching back to mode.
This is the setter
hook method to be used with the dispensing:
-(void) Hook_setbackgroundcolor:(Uicolor*) BackgroundColor{if ( Span class= "token punctuation" >[dknightversionmanager currentthemeversion == dkthemeversionnormal) { [self setnormalbackgroundcolor:backgroundcolor] [self Hook_setbackgroundcolor:backgroundcolor] /span>
If the current normal
mode, it will be stored color
, if not it will be directly assigned, if you do not understand why this seems to cause infinite recursion, see here, detailed explanation of how the method is used.
Dknightversionmanager implementation
color
Switch
We have added and for the UIKit normalColor
control nightColor
, and then we need to implement the color
switch between the two, and this DKNightVersionManager
is the class to handle the mode switch.
By DKNightVersionManager
creating a singleton to process, and 模式转换
使用默认颜色
动画时间
so on.
When invoking DKNightVersionManager
a class method nightFalling
or dawnComing
, we first get the global UIWindow
, and then recursively call the method to changeColor
changeColor
change the color of the view that responds to the method.
-(void) ChangeColor:(ID<dknightversionchangecolorprotocol>) object{If([Object Respondstoselector:@selector(ChangeColor)]){[Object ChangeColor];}If([Object Respondstoselector:@selector(Subviews)]){If(![Object Subviews]){Basic case, does nothing.Return;}Else{For(ID SubviewInch[Object Subviews]){Recursice Darken all the subviews's current view.[Self ChangeColor:subview] span class= "token keyword keyword-if" >if ([subview Respondstoselector: @selector Span class= "token punctuation" > (Changecolor]< Span class= "token punctuation") {[subview Changecolor]; } } } }
Because I did not introduce in this class; category
, compiler does not know ID
type has both methods. So I declare a protocol that makes ChangeColor
methods to satisfy the two methods ChangeColor
and subviews
. Do not let the compiler prompt for errors.
@protocol DKNightVersionChangeColorProtocol <NSObject>- (void)changeColor;- (NSArray *)subviews;@end
Then let all the UIKit control follow this protocol, and of course we can not explicitly follow this protocol, as long as it can respond to both methods is also possible.
Implement default Colors
We want to implement the default night-mode color matching in dknightversion to reduce the developer's workload.
But because we color
implement each one only once in the parent class, this allows subclasses to inherit the implementation of the parent class, but also does not want the UIKit class to inherit the default color of the parent class.
-(Uicolor*) Defaultnightbackgroundcolor{BOOL Notuikitsubclass=[Self Iskindofclass:[UIView Class]]&&![Nsstringfromclass(Self. class) Hasprefix:@ "UI"];If([Self Ismemberofclass:[UIView Class]]|| Notuikitsubclass){Returnuicolorfromrgb ( else { Uicolor *resultcolor = self.normalbackgroundcolor ?[uicolor clearcolor]< Span class= "token punctuation"; return resultcolor}}
by using Ismemberofclass:
method to determine that the instance is not an instance of the current class, not an instance of the class subclass . then returns the default color. but not UIKit. Can inherit this attribute, so use this code to determine if the instance is a subclass of UIKit:
[self isKindOfClass:[UIView class]] && ![NSStringFromClass(self.class) hasPrefix:@"UI"]
We have NSStringFromClass(self.class) hasPrefix:@"UI"
achieved this through ingenious means.
Use
erb
Generate OBJECTIVE-C Code
Most of the work in this framework is repetitive, but I don't want to write nearly the same code for each class, which is very difficult to read and maintain, so I used a erb
file to provide a template for the generated objective-c code, parse the metadata and pass in every template, Generate all the code dynamically, and then add all the files to the directory through another script.
The implementation of dknightversion is not complicated. Not only did it use the erb
Ruby script to reduce the amount of work, but also used objc/runtime
the feature to change the UIKit component to the effect of adding night mode to IOS apps.
Dknightversion implementation---How to add night mode to IOS apps