Skin changing (day and night)
One: The first is to use a simple method to achieve
Do not remember who said, China's users are probably the world's most like a multi-skin features of users. I hate to write Android programs, graphical interface design tools and difficult to use, not as good as handwriting, editor slow like snail, smart tips always keep up with the speed I entered, the same function, Android code is at least three times times the iOS, each line of code, feel their fingers in the blood. But Android's flexible and unified style function is really intimate! Before 5, there was no better way to achieve the same functionality on the iOS platform.
Apple binds all the interface components to a protocol called Uiappearance, and you can simply set the overall style of the component through uiappearance.
For example, I want to set all the UIButton title to white:
[[UIButton appearance] Settitlecolor:[uicolor Whitecolor] forstate:uicontrolstatenormal];
Or, I think all the UIButton have a uniform background image.
[[UIButton appearance] Setbackgroundimage:abackgroundimage Forstate:uicontrolstatenormal];
Of course, the actual project, the different style of the component, should be defined as a separate class, and then in its Initialize method set its uiappearance, for example, I define a Afkbutton class, I can write the following, The button for all Afkbutton classes in this application is a style.
1 @implementationAfkbutton2 ......3+ (void) Initialize {4 if(Self = =[Afkbutton Self]) {5 [[Self appearance] Settitlecolor:[uicolor Whitecolor] forstate:uicontrolstatenormal];6 [self appearance] setbackgroundimage:abackgroundimage forstate:uicontrolstatenormal];7 }8 }9 ......Ten @end
To modify the uiappearance, there is a limitation, that is, if you want it to take effect, it must be loaded into the app's main window the next time, so if you want to dynamically modify the style of the component through uiappearance, we need to implement the following code in Uiappdelegate
1 uiviewcontroller *rootviewcontroller = Self.window.rootViewController; 2 Self.window.rootViewController = Nil; 3 4 5[[UIButton appearance] Setbackgroundimage:abackgroundimage Forstate:uicontrolstatenormal] ; 6 7 Self.window.rootViewController = Rootviewcontroller;
As described above, I designed the style of the dynamic switch skin as follows:
- 1. First build the corresponding component class according to style, for example, you have several buttons, you can implement several button classes on inheritance.
- 2. Set the global style flag.
- 3. Trigger the style modification where the message is sent through the global broadcast.
- 4.UIAppDelegate Reload Window's Rootviewcontroller
Second: Use of third-party
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
The pit of a forced
Demand.
Realize
And now I finally have time to
Water One Water
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
.
1 -(Uicolor *) nightbackgroundcolor {2 return objc_getassociatedobject (self , &nightbackgroundcolorkey)? : Self.backgroundcolor); 3 }4 -(void) Setnightbackgroundcolor: (Uicolor *) nightbackgroundcolor {5 objc_setassociatedobject (self, &Nightbackgroundcolorkey, Nightbackgroundcolor, objc_ association_retain_nonatomic); 6 }
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.
1 -(Uicolor *) normalbackgroundcolor {2 return objc_getassociatedobject ( Self, &normalbackgroundcolorkey); 3 }4 -(void) Setnormalbackgroundcolor: (Uicolor *) normalbackgroundcolor {5 objc_setassociatedobject (self, &Normalbackgroundcolorkey, Normalbackgroundcolor, objc_ association_retain_nonatomic); 6 }
But the time to save this color is very important, and at the very beginning, my choice is to overwrite the setter
method directly before storing the color normalColor
.
-(void) SetBackgroundColor: (Uicolor *) backgroundcolor { = 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:
1 -(void) Hook_setbackgroundcolor: (uicolor*) backgroundcolor {2 if ([dknightversionmanager currentthemeversion] = = dkthemeversionnormal) {3 [self Setnormalbackgroundcolor:backgroundcolor]; 4 }5 [self hook_setbackgroundcolor:backgroundcolor]; 6 }
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.
1- (void) ChangeColor: (ID<DKNightVersionChangeColorProtocol>)Object {2 if([Objectrespondstoselector: @selector (changecolor)]) {3[ObjectChangeColor];4 }5 if([Objectrespondstoselector: @selector (subviews)]) {6 if(! [ObjectSubviews]) {7 //Basic case, does nothing.8 return;9}Else {Ten for(IDSubviewinch[ObjectSubviews]) { One //Recursice Darken all the subviews's current view. A [self changecolor:subview]; - if([Subview respondstoselector: @selector (ChangeColor)]) { - [Subview ChangeColor]; the } - } - } - } +}
Because I did not introduce it in this class category
, the compiler does not know id
that the type has both methods. So I declare a protocol that enables the changeColor
methods in the method to satisfy both 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 controls follow this protocol, and of course we can also not show that following 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:[uiviewclass]] &&! [Nsstringfromclass (self.class) Hasprefix:@"UI"]; if([Self Ismemberofclass:[uiviewclass]] ||Notuikitsubclass) { returnUicolorfromrgb (0x343434); } Else{Uicolor*resultcolor = Self.normalbackgroundcolor?: [Uicolor Clearcolor]; returnResultcolor; }}
Use the isMemberOfClass:
method to determine if the instance is an instance of the current class, not an instance of the class subclass. The default color is then returned. But subclasses of non-UIKit can inherit this attribute, so use this code to determine if the instance is a subclass of non-UIKit:
class]] &&! [Nsstringfromclass (self. Class) Hasprefix:@ "UI"]
We have NSStringFromClass(self.class) hasPrefix:@"UI"
achieved this through ingenious means.
Using erb
generate OBJECTIVE-C code
Most of the work in this framework is repetitive, but I don't want to write almost 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, only parse the original data and pass in each 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. It not only uses the
erb
and Ruby scripts reduce a lot of work and use
objc/runtime
Features to change the UIKit component to the effect of adding night mode to IOS apps.
iOS development-Using technology OC Tablets & Skin Care (day and night)