I. Preface:

When it comes to major holidays, such as Christmas, Spring Festival, etc., various apps will carry out skin peeling to foil the festive atmosphere. Shopping apps will also change their own special clothes on 618 or Double 11. After analyzing several apps, there are roughly the following three types:

  1. Image resources are directly put into the APP package, and the interface controls whether to display
  2. The interface returns the address of the picture, and the APP gets the picture according to the address of the picture
  3. Download the package, decompress it and replace the image

The first way will increase the package volume of the APP. For the sake of user experience, we should try not to increase the burden on users. Flexible replacement is also an issue, depending heavily on the release.

In the second way, the address of the picture is independent, and the picture is downloaded separately, which is easy to appear incomplete. For example, if a tabbar picture fails, it is not half of the skin change.

Based on the above considerations, the third method of using compressed packages is currently recommended.

Below is a detailed description of the peel process of the free APP for us.

Second, the actual combat

2.1 Skin peening process

The flow chart of skin replacement is as follows:

After the APP starts, it directly loads the corresponding skin file and asynchronously requests the background skin interface. The interface returns a compressed package link. After decompression, it parses the config.json file in the package, and then triggers the dering through notification. The logic that controls whether the skin is displayed is completely controlled by the background, which returns skinSign as null to close the skin peels.

2.2 Implementation Mode

Skin management components are divided into network module, management module, and Category

The skin manager is separate from the Cocoapods component, and the business layer depends on the skin peel component.

Here’s an example of the contents of the config.json file:

{ "home_navi": { "colors": { "color_background": "#ffffff" }, "images": { "image_logo": "home_topLogo" } }, "home_tabbar": { "colors": { "color_background": "#F9F9F9", "color_button_normal": "#999999", "color_button_selected": "#444444" }, "images": { "image_one_button_normal": "Image_two_button_normal ": "image_two_button_normal": "image_two_button_normal": "Image_two_button_selected ":" image_two_Button_selected ": "image_three_button_normal": "Image_three_button_selected ":" TAB button 2 ", "image_three_button_selected": "TAB button 2 ", "values": {"value_one_button": "TAB two_button":" TAB two_button", "value_two_button": "TAB two_button", "value_three_button":" TAB two_button", "value_three_button": "TAB two_button"}}," Loading ": {"resources": { "resource_refreshImage" : "refresh.gif" } } }Copy the code

This section provides examples for home page navigation (home_navi), home page Tabbar (home_tabbar), and loading(Loading). Under each business module, there can be four functional modules, namely colors, images, values, and Resources, which can be added according to your own needs. Colors controls the color, so I’m using a hexadecimal value here. Images control images, the most common PNG files. Values control values. Resources controls resource files, such as JSON, GIF, and so on.

We create a Category for UIView and add methods to this Category as follows:

- (void)configSkinMapModule:(NSString *)module skinMap:(NSDictionary *)skinMap; - (void)configSkinMapModule:(NSString *)module skinMap:(NSDictionary *)skinMap { if (! [ZRSkinManager sharedInstance].isOpenZRSkinManager) { return; } NSMutableDictionary *tempDic = [skinMap mutableCopy]; for (NSUInteger i = 0; i < tempDic.allKeys.count; i ++) { NSString *key = tempDic.allKeys[i]; NSString *value = tempDic[key]; tempDic[key] = [NSString stringWithFormat:@"%@.%@",module,value]; } self.skinMap = [tempDic copy]; }Copy the code

Then we can register with the module that needs to be skinned. For example, we can add the skin function to the first button of the Tabbar. The code is as follows:

[_tabbarButton configSkinMapModule:kSkin_MODULE_HOME_TABBAR skinMap:
     @{kSkinMapKey_button_image : @"image_one_button_normal",
       kSkinMapKey_button_selectedImage : @"image_one_button_selected",
       kSkinMapKey_button_titleColor : @"color_button_normal",
       kSkinMapKey_button_titleSelectedColor : @"color_button_selected",
       kSkinMapKey_button_title : @"value_one_button"
       }];
Copy the code

What happens after the above code is executed? I’m going to add NSNotificationCenter to this _tabbarButton in the set method of skinMap

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(skinChanged) name:kZRSkinDidChangeNotification  object:nil];Copy the code

When you want to change skin, we will trigger kZRSkinDidChangeNotification notice.

So what does the skinChanged method do?

I’m going to create a SkinConstants file to define the replacement mode identification.

Static NSString * const kSkinMapKey_button_image = @"kSkinMapKey_button_image"; static NSString * const kSkinMapKey_button_highlightedImage = @"kSkinMapKey_button_highlightedImage"; static NSString * const kSkinMapKey_button_selectedImage = @"kSkinMapKey_button_selectedImage"; static NSString * const kSkinMapKey_button_disabledImage = @"kSkinMapKey_button_disabledImage"; static NSString * const kSkinMapKey_button_titleColor = @"kSkinMapKey_button_titleColor"; static NSString * const kSkinMapKey_button_titleHighlightedColor = @"kSkinMapKey_button_titleHighlightedColor"; static NSString * const kSkinMapKey_button_titleSelectedColor = @"kSkinMapKey_button_titleSelectedColor"; static NSString * const kSkinMapKey_button_titleDisabledColor = @"kSkinMapKey_button_titleDisabledColor"; static NSString * const kSkinMapKey_button_title = @"kSkinMapKey_button_title"; Static NSString * const kSkinMapKey_label_text = @"kSkinMapKey_label_text"; static NSString * const kSkinMapKey_label_textColor = @"kSkinMapKey_label_textColor"; static NSString * const kSkinMapKey_label_backgroundColor = @"kSkinMapKey_label_backgroundColor"; Static NSString * const kSkinMapKey_imageView_image = @"kSkinMapKey_imageView_image"; static NSString * const kSkinMapKey_imageView_gif = @"kSkinMapKey_imageView_gif"; Static NSString * const kSkinMapKey_imageView_backgroundColor = @"kSkinMapKey_imageView_backgroundColor";Copy the code

As you can tell from the name, each definition is a method in UIKit.

Then I’ll talk about the method I just added to the Category. The module corresponds to the business module in config.json, such as home_navi. The key in skinMap is the replacement mode id, which is exactly what is defined in SkinConstants, and the value is the key value of the corresponding module in config.json. Modify the normal mode image (kSkinMapKey_button_image) and select the selected mode image (kSkinMapKey_button_selectedImage) of a button in the home_navi business module , normal mode text color (kSkinMapKey_button_titleColor), modify selected mode image (kSkinMapKey_button_selectedImage), modify text value (kSkinMapKey_button_title).

We use the following code in the notification trigger method to perform the replacement procedure

- (void)changeSkin { NSDictionary *map = self.skinMap; if ([self isKindOfClass:[UIButton class]]) { UIButton *obj = (UIButton *)self; if (map[kSkinMapKey_button_image]) { [obj setImage:SkinImage(map[kSkinMapKey_button_image]) forState:UIControlStateNormal]; } if (map[kSkinMapKey_button_highlightedImage]) { [obj setImage:SkinImage(map[kSkinMapKey_button_highlightedImage]) forState:UIControlStateHighlighted]; } if (map[kSkinMapKey_button_selectedImage]) { [obj setImage:SkinImage(map[kSkinMapKey_button_selectedImage]) forState:UIControlStateSelected]; } if (map[kSkinMapKey_button_disabledImage]) { [obj setImage:SkinImage(map[kSkinMapKey_button_disabledImage]) forState:UIControlStateDisabled]; } if (map[kSkinMapKey_button_titleColor]) { [obj setTitleColor:SkinColor(map[kSkinMapKey_button_titleColor]) forState:UIControlStateNormal]; }... The following ellipsis... }Copy the code

I also have a localconfig. json file that manages the local modules that need to be replaced with skins. The content is exactly the same as config.json. It just takes the local default skin resource configuration. SkinImage handles the images module, and this macro is defined as the pngResourceForSign: method macro to figure out which image file to load. I won’t go into the colors, resources, and other modules, but they’re pretty much the same.

// get Png resource - (UIImage *)pngResourceForSign:(NSString *)sign; { NSArray *array = [sign componentsSeparatedByString:@"."]; NSString *module = array.firstObject; NSString *key = array.lastObject; NSDictionary *moduleDic = self.configData[module]; NSDictionary *imageDic = moduleDic[@"images"]; NSString *value = imageDic[key]; If self. Path has a value, it is the background skin. If no value, it is the local default skin. if (! self.path.length) { return [UIImage imageNamed:value]; } NSString *filePath = [self.path stringByAppendingFormat:@"/%@",value]; UIImage *image = [UIImage imageWithContentsOfFile:filePath]; return image; }Copy the code

So that’s the core idea of skin peels, which is to make every UIView capable of skin peels by way of Category, and then trigger it by way of NSNotificationCenter. Skin download, skin management and other parts are not an introduction.

Iii. Conclusion:

There are thousands of ways to skin, but the features based on iOS are inseparable from the Category, if you have other solutions, welcome to communicate.

References:

  1. Making ThemeManager,
  2. Making · SwiftTheme
  3. IOS skin solution
  4. Making · EasyTheme
  5. “Festival skin” general technical solution __iOS end implementation

Author: Qu Yin, Research and development Center of Freely Big Front End

Recruitment information

New students are wanted in the research and development Center!

FE/iOS/Android engineer

Company benefits:

  • Full five insurance and one housing fund, and additional purchase of commercial insurance
  • Free gym + annual physical
  • 10% discount on rent near the company
  • 2 promotion opportunities per year

Office: Beijing Jiuxianqiao Putian Industrial science and Technology Park welcome to have a persistent love of technology you join us! Resume please send to [email protected], or add wechat V-nice-v details chat!