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