I. Preface:

I had this article ready by Christmas, but for a variety of reasons it was never finished. Today is my first article of 2018. Hello, 2018!

In the past, Christmas was a festival for all kinds of apps to put on heavy make-up to show their clothes. This year’s Christmas seems to be a lot less crowded. I only saw a few apps to change the skin, so I will analyze it.

Ii. Analysis:

I think there are three main types of skin changing at present: one is to return the address of the picture, and the APP will retrieve the picture according to the picture log; the other is to download the ZIP package and decompress it to replace the icon; the other is to put the picture resources into the package, and the interface will control whether the image is displayed.

2.1 Implementation Method 1:

I found that beaver home is this way, why first to beaver home for example? Because my friend said it was too cool! So I started with that.

I have checked the sandbox of the Beaver APP on my jailbreak phone and found no skin files stored locally.

So I started to use Charles to capture packets, and I found the configuration information that seemed to be a skin file in this interface.

As shown in figure:



So I sent the requested skin file from the img prefix domain, as shown here, which is the tabbar background image.

It is commendable that the PNG image of Herijia has been compressed by WebP, which is also a mainstream image format in APP terminal at present.

Therefore, the scheme of the Beaver family is that the interface returns the configuration information of the skin, which contains the address information of the picture, and then gets the picture through the picture caching framework.

In this case, I think we must do some processing, so that all the pictures are cached, and then display, otherwise there may be a picture flashed one by one, or even in the case of bad network, a picture can not be displayed. I’ve seen this happen on another APP, but I can’t remember which one (I’ve tested so many apps…).

2.2 Implementation Mode 2:

Here I take the micro store buyers version for an example, as shown in the picture, this is the Micro store buyers Version Christmas skin.

I also looked to see if there were any skin files in the sandbox after installing the APP, but I didn’t find any either. Let’s go straight to the interface. I found a suspicious ZIP file package in the assets domain.

As shown in figure:

When you unzip the zip file, you find tabbar’s image resources. I also found the same file in the sandbox of the program.

As shown in figure:

Image resources are available, so how are they replaced? I will take the micro store buyers version for example.

After I got the ipA shell, I used Hopper Disassembler and class-dump respectively to analyze the main program. Finally, the following information is found:

You can see that it uses Category and KVO to implement the process of replacing the skin. Add a Category to UIButton and other system classes, add a method to set the skin, through KVO to achieve trigger control. In addition, it is suggested that skin change can be triggered immediately after the completion of skin download. When I tested Baidu Nuomi APP, I found that it was replaced only when it was started for the second time, probably because it is a high-frequency APP.

2.3 Implementation Mode three:

I did not find this method in several apps I tested. I heard from a friend that a certain APP had used this method. This way is before the release of the skin file stored in the package, through the background interface control to display. The advantage of this situation is easy to control, small failure rate. The downside is that the bags are bulky and rely heavily on Apple dad’s approval.

Iii. My implementation method:

Recently I have also done skin-related functions, and I will talk about my implementation ideas below. So let’s take a look at my APP control logic.

My implementation ideas are similar to the implementation of the micro shop. But INSTEAD of using KVO, I used notification registration. After the APP started, the corresponding skin file was directly loaded, while another thread requested the background skin interface, which returned a link to zip package, downloaded zip package, decompressed, and analyzed the config.json file inside. Then I used notification to trigger the skin change. The specific logic of the idea is believed to have been drawn clearly on the flow chart. The logic controlling whether or not the skin is displayed is completely controlled by the background, and the background returns skinSign empty to close the skin.

Let’s look at the format of my 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": "TAB button 1 image "," image_one_BUTTon_selected ": "TAB button 1 selected image ", "image_two_button_normal": "TAB button 2 image "," image_two_BUTTon_selected ": "TAB button 2 selected image ", "image_three_button_normal": }, "values": {"value_one_button": {"value_one_button": {"value_one_button": }}, "loading": {"resources": {"resources": { "resource_refreshImage" : "refresh.gif" } } }Copy the code

The configuration file is divided into three service modules: home_navi, home_tabbar, and loading loading. Under each business module, there can be four functional modules: Colors, images, values and Resources. These four modules can be added according to their own needs. Colors controls the color, so I’ll use the hexadecimal value. Images controls images, the most common PNG file. Values control values. Resources controls resource files, such as JSON, GIF, and so on.

I created a UIView Category, and in this Category I added a method like this:

- (void)configSkinMapModule:(NSString *)module skinMap:(NSDictionary *)skinMap;
Copy the code

Suppose I need to add skin to the navigation bar, I just add the following code:

    [_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

I’m going to create a SkinConstants file to define the alternative way identity.

// 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"; // GIF animation 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 methods in that Category, where module corresponds to the business module in config.json, such as home_navi. The key in skinMap is a replacement identification is defined in SkinConstants, and value is the key of the corresponding module in config.json. Modify normal mode image (kSkinMapKey_button_image) and selected mode image (kSkinMapKey_button_selectedImage) for a button in the home_navi business module. , normal mode text color (kSkinMapKey_button_titleColor), modify the selected mode picture (kSkinMapKey_button_selectedImage), modify the text value (kSkinMapKey_button_title) function.

We use the following code in the notification trigger method to perform the substitution process

- (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 is omitted... }Copy the code

I also have a localconfig. json stored locally to manage local modules that need to be skinned. The content is exactly the same as config.json. It just takes the default local skin configuration. SkinImage handles the images module, and this macro definition is the pngResourceForSign: method macro that determines which image file to load. I won’t go through the colors, Resources and other modules, they are all similar.

- (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

The above example is _tabbarButton perform configSkinMapModule: skinMap: method to register a notice, whether the background is enabled, start the avi is loaded config. The json file, No localconfig. json native default skin is loaded. The above is a way of thinking that I realize skin change.

Iv. Summary:

Each of the above implementation methods has its own benefits, and my implementation method also needs to be optimized, for example, time control can be added to the background interface, and the cache scheme can be implemented in advance, instead of changing under the user’s eyes every time. If you have a better implementation plan, welcome to communicate with us.

Github ·SwiftTheme 3. IOS skin change scheme 4. Github ·EasyTheme 5. “Holiday skin” general technical scheme __iOS end implementation

To play to admire