The copyright of this article belongs to the public account [an old code farmer].

In the last article we had an overview of what The Runtime can do. In this article I’ll introduce one of the practical applications of the Runtime: how to use the Runtime methods for swapping to dynamically change font sizes based on screen size without modifying the original code, including dragging controls in xib and storyboard.

As we know, code usually sets the font size using several UIFont class methods:

systemFontOfSize

fontWithName:size

boldSystemFontOfSize

italicSystemFontOfSize

...
Copy the code

So since the Runtime can swap methods, we just need to define a custom method, replace the system method can be implemented. Without further ado, we set to work

Implement NSObject class method exchange

Create an NSObject class and add a Method for “Method exchange.” The Method exchange is essentially an exchange of IMP Pointers. So the system gives us a method in C called method_exchangeImplementations that we can swap. The process is as follows:

1. Obtain the method of the method according to the selector of the original method and the target method. If a class method uses class_getClassMethod to get the method, if an object method uses class_getInstanceMethod to get the method

2. After obtaining method, call method_exchangeImplementations function to swap imp Pointers of two methods

#import "NSObject+Category. H "#import <objc/runtime.h> @implementation NSObject (Category @param swizzleSelector @param isClassMethod is a class method, YES is a class method, NO object method * / + (void) runtimeReplaceFunctionWithSelector: (SEL) originselector swizzleSelector swizzleSelector: (SEL) isClassMethod:(BOOL)isClassMethod { Method originMethod; Method swizzleMethod; if (isClassMethod == YES) { originMethod = class_getClassMethod([self class], originselector); swizzleMethod = class_getClassMethod([self class], swizzleSelector); }else{ originMethod = class_getInstanceMethod([self class], originselector); swizzleMethod = class_getInstanceMethod([self class], swizzleSelector); } method_exchangeImplementations(originMethod, swizzleMethod); } @endCopy the code

UIFont sets font class method replacement

Create a new UIFont class and replace the UIFont system method in the +(void)load method

#import "UIFont+Category.h" #import "NSObject+Category.h" @implementation UIFont (Category) // The +(void)load method is automatically called before the main function, there is no need to manually call the +(void)load {// switch systemFontOfSize: [[self class] runtimeReplaceFunctionWithSelector: @ the selector (systemFontOfSize:) swizzleSelector:@selector(customSystemFontOfSize:) isClassMethod:YES]; / / exchange fontWithName: size: method [[self class] runtimeReplaceFunctionWithSelector: @ the selector (fontWithName: size:) swizzleSelector:@selector(customFontWithName:size:) isClassMethod:YES]; } // custom switch method + (UIFont *)customSystemFontOfSize:(CGFloat)fontSize {CGFloat size = [UIFont transSizeWithFontSize:fontSize]; // This does not cause recursion. After the method exchange, call customSystemFontOfSize. Method is called the original systemFontOfSize return [UIFont customSystemFontOfSize: size]; } // custom exchange method + (UIFont *)customFontWithName:(NSString *)fontName size:(CGFloat)fontSize {CGFloat size = [UIFont transSizeWithFontSize:fontSize]; return [UIFont customFontWithName:fontName size:size]; } // if the screen width is greater than 320, add 10 fonts. + (CGFloat)transSizeWithFontSize:(CGFloat)fontSize {CGFloat size = fontSize; CGFloat width = [UIFont getWidth]; if (width > 320) { size += 10; } return size; } / / / get the screen width of the vertical screen mode + (CGFloat) getWidth {for (UIScreen * windowsScenes in UIApplication. SharedApplication. ConnectedScenes)  { UIWindowScene * scenes = (UIWindowScene *)windowsScenes; UIWindow *window = scenes.windows.firstObject; if (scenes.interfaceOrientation == UIInterfaceOrientationPortrait) { return window.frame.size.width; } return window.frame.size.height; } return 0; } @endCopy the code

So now that I’ve done this, I want to dynamically change the font size, what about the controls that xiB and storyboard drag? Let’s move on

Dynamically change the font size for xib and Storyboard controls

Xib and SB drag controls, both call initWithCoder method, so we can define a custom method, replace initWithCoder, and change the control font in this method. Let’s use UILabel as an example, create a classification of UILabel, and then swap initWithCoder methods in the +(void)load method

#import "UILabel+Category.h" #import "NSObject+Category.h" @implementation UILabel (Category) + (void)load { [[self class] runtimeReplaceFunctionWithSelector:@selector(initWithCoder:) swizzleSelector:@selector(customInitWithCoder:) isClassMethod:NO]; } - (instancetype)customInitWithCoder:(NSCoder *)coder { if ([self customInitWithCoder:coder]) { // Call fontWithName:size: customFontWithName:size: self.font = [UIFont fontWithName:self.font.familyName size:self.font.pointSize]; } return self; } @endCopy the code

At this point, we’ve implemented dynamic changes to the font size of UILabel, and we’ve implemented several other controls that are commonly used in development

The classification of UIButton

#import "UIButton+Category.h" #import "NSObject+Category.h" @implementation UIButton (Category) + (void)load { [[self class] runtimeReplaceFunctionWithSelector:@selector(initWithCoder:) swizzleSelector:@selector(customInitWithCoder:) isClassMethod:NO]; } - (instancetype)customInitWithCoder:(NSCoder *)coder { if ([self customInitWithCoder:coder]) { if (self.titleLabel ! = nil) { self.titleLabel.font = [UIFont fontWithName:self.titleLabel.font.familyName size:self.titleLabel.font.pointSize]; } } return self; } @endCopy the code

The classification of UITextField at

#import "UITextField+Category.h"
#import "NSObject+Category.h"

@implementation UITextField (Category)

+ (void)load
{
    [[self class] runtimeReplaceFunctionWithSelector:@selector(initWithCoder:) swizzleSelector:@selector(customInitWithCoder:) isClassMethod:NO];
}

- (instancetype)customInitWithCoder:(NSCoder *)coder
{
    if ([self customInitWithCoder:coder]) {
        self.font = [UIFont fontWithName:self.font.familyName size:self.font.pointSize];
    }
    return self;
}

@end
Copy the code

The classification of UITextView

#import "UITextView+Category.h"
#import "NSObject+Category.h"

@implementation UITextView (Category)

+ (void)load
{
    [[self class] runtimeReplaceFunctionWithSelector:@selector(initWithCoder:) swizzleSelector:@selector(customInitWithCoder:) isClassMethod:NO];
}

- (instancetype)customInitWithCoder:(NSCoder *)coder
{
    if ([self customInitWithCoder:coder]) {
        self.font = [UIFont fontWithName:self.font.familyName size:self.font.pointSize];
    }
    return self;
}

@end
Copy the code

At this point, we are done changing the font size of the controls dynamically. We drag a few controls in our storyboard and create some controls with the code to see what happens before and after the changes

Note: Swift does something similar, but Swift does not allow you to override the +(void)load method. So if it is Swift, + (void) load need to define your own way, and called the AppDelegate didFinishLaunchingWithOptions method.

From the public account: [an old code farming], pay attention to the public account for free learning video

Runtime – Dynamic font size modification