DKNightVersion implementation and dknightversion implementation
In fact, many software programs that require heavy reading or watching at night will regard the night mode as a feature required by an App. on the basis of not changing the original architecture or even the original code, adding the night mode elegantly becomes a problem that many applications have to face in the process of development.
It is the driving force of the above things that enables me to think about how to use an elegant and concise method to solve this problem.
DKNightVersion is my solution.
So far, most of the work on this framework has been completed. Maybe it is not perfect yet, but I will continue to maintain this framework, help engineers who suffer from achieving the nighttime model to solve this problemA hard nut to crackRequirement.
Implementation
Now I have time.Water and waterWrite a blog to illustrate how this framework achieves the nighttime model and what features it has.
For a long period of time, I was wondering how to avoid overwriting.UIKit
Add the night mode for the iOS App.objc/runtime
Does not overwrite meUIKit
Hope to achieve this goal.
Add to UIKit Control
nightColor
Attribute
Because we will not subclass the UIKit control and then use@property
Add attributes to its subclass. Instead, use the magic Category andobjc/runtime
To add properties for controls in the UI series.
Useobjc/runtime
I believe many people know how to add attributes to categories and often use them in development. If you do not know them, please refer to here.
DKNightVersion is commonly usedcolor
For example:backgroundColor
tintColor
Are addednight
The color in the night mode,nightBackgroundColor
nightTintColor
.
- (UIColor *)nightBackgroundColor { return objc_getAssociatedObject(self, &nightBackgroundColorKey) ? :self.backgroundColor);}- (void)setNightBackgroundColor:(UIColor *)nightBackgroundColor { objc_setAssociatedObject(self, &nightBackgroundColorKey, nightBackgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}
We create this attribute to save the color in the night mode. In this way, when the topic of the application is switched to the night modenightColor
The color of attribute storage is assigned to the correspondingcolor
But there is a problem. When the application switches back to the normal mode, we lose the original normal mode.color
.
Add
normalColor
Storage color
To solve this problem, we added another property for the UIKit control.normalColor
To save the color in normal mode.
- (UIColor *)normalBackgroundColor { return objc_getAssociatedObject(self, &normalBackgroundColorKey);}- (void)setNormalBackgroundColor:(UIColor *)normalBackgroundColor { objc_setAssociatedObject(self, &normalBackgroundColorKey, normalBackgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}
However, the time to save the color is very important. At the very beginning, I chose to directly overwrite the color.setter
Method, stored before saving the colornormalColor
.
- (void)setBackgroundColor:(UIColor *)backgroundColor { self.normalBackgroundColor = backgroundColor; _backgroundColor = backgroundColor;}
However, this seemingly operationalsetter
In fact, the view will not be colored.color
Normal colors do not respond, but the background color of the view is dark.
Because the above method does not work, I want to use the observer mode to storenormalColor
, Register the instancecolor
Attribute observer, whencolor
When the property changes, notify the UIKit control and save the propertynormalColor
Attribute.
However, when can I register myself as an Observer? I gave up this solution and finally chose a method to solve the problem.color
Storage problems.
The usage is adjusted to the original attributesetter
Add a hook to the method. Store the property before calling the method and use it to switch backnormal
Mode, assign a value to the attribute.
This issetter
Hook method for adjustment:
- (void)hook_setBackgroundColor:(UIColor*)backgroundColor { if ([DKNightVersionManager currentThemeVersion] == DKThemeVersionNormal) { [self setNormalBackgroundColor:backgroundColor]; } [self hook_setBackgroundColor:backgroundColor];}
If the currentnormal
Mode, it will storecolor
If not, values will be assigned directly. If you don't understand why it seems to cause infinite recursion, please refer to it and explain in detail how the method adjustment is used.
DKNightVersionManager implementation
color
Switch
We have added the UIKit ControlnormalColor
AndnightColor
Next, we need to implementcolor
Switch between the two, andDKNightVersionManager
It is the class for switching the processing mode.
PassDKNightVersionManager
Create a singleton for processingMode Conversion
,Use default color
,Animation time
.
WhenDKNightVersionManager
Class MethodnightFalling
OrdawnComing
First, we obtain the globalUIWindow
And then recursively callchangeColor
Method to make the responsechangeColor
Change the color of the method view.
- (void)changeColor:(id <DKNightVersionChangeColorProtocol>)object { if ([object respondsToSelector:@selector(changeColor)]) { [object changeColor]; } if ([object respondsToSelector:@selector(subviews)]) { if (![object subviews]) { // Basic case, do nothing. return; } else { for (id subview in [object subviews]) { // recursice darken all the subviews of current view. [self changeColor:subview]; if ([subview respondsToSelector:@selector(changeColor)]) { [subview changeColor]; } } } }}
Because I have not introducedcategory
The compiler does not knowid
The type has these two methods, so I declare a protocol to makechangeColor
To meet the requirements of the two methods.changeColor
AndsubViews
. Do not let the compiler prompt an error.
@protocol DKNightVersionChangeColorProtocol <NSObject>- (void)changeColor;- (NSArray *)subviews;@end
Then let all the UIKit controls follow this Protocol. Of course, we can also follow this protocol explicitly, as long as it can respond to these two methods.
Default color
We need to implement the default night color scheme in DKNightVersion to reduce developers' workload.
Howevercolor
This is only implemented once in the parent class, so that the subclass can inherit the implementation of the parent class, but also does not want the UIKit subclass 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) { return UIColorFromRGB(0x343434); } else { UIColor *resultColor = self.normalBackgroundColor ?: [UIColor clearColor]; return resultColor; }}
UseisMemberOfClass:
Method to Determine whether the instance is an instance of the current class, rather than an instance of this class subclass. then the default color is returned. however, non-UIKit subclasses can inherit this feature. Therefore, use this code to determine whether the instance is a non-UIKit subclass:
[self isKindOfClass:[UIView class]] && ![NSStringFromClass(self.class) hasPrefix:@"UI"]
We passNSStringFromClass(self.class) hasPrefix:@"UI"
Cleverly achieve this purpose.
Use
erb
Generate Objective-C code
Most of the work of this framework is repeated, but I don't want to write almost the same code for every class repeatedly. Such code is very difficult to read and maintain, so I usederb
File to provide a template for the generated Objective-C code, parse the metadata and pass in each template to dynamically generate all the code, add all the files to the directory using another script.
The implementation of DKNightVersion is not complicated.erb
And Ruby scripts reduce a lot of work and useobjc/runtime
To add the night mode for iOS apps.