iOS 中利用 runtime 一键改变字体

忙忙忙!!!好久没写博客了,前段时间实在是每天满满的,回去了累了也不想写了,只是躺床上看一会东西。最近公司要在 5 月份举办个大型的发布会,所以在这之前要把版本稳定,界面提升,所以有很多细活要干。


不过,趁前两天版本刚提交上线,这两天稍微闲一点,就把之前说的利用 runtime 一键改变字体的方法分享出来。有人会说,改变字体不是很简单吗,我直接找到字体名替换一下不就好了?客官不要急,先坐下来吃点瓜子,听我慢慢给你说来。

准备

我们新建一个项目名叫 ChangeFont ,然后我就随便找了个名叫 loveway.ttf 的字体库拖进去,里面的工程目录大概就是这样的

目录
现在我们就简单的直接在 storyboard 上拖了一个 label 一个 button ,约束好,像这样

storyboard
嗯,就这样,很简单,运行

运行结果
好的显示正常,没什么问题,接下来改变字体。

改变字体

我们之前已经把 loveway.ttf这个文件拖进去了,现在在 plist 文件里面配置一下。打开 plist 然后加入名为 Fonts provided by application 的一行,在 item 里把我们的字体名字加进去

plist
最后我们需要保证我们确确实实是加进来了

phases
这个时候也许你已经迫不及待了,赶紧改字体,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
//  ViewController.m
//  ChangeFont
//
//  Created by HenryCheng on 16/4/27.
//  Copyright © 2016年 HenryCheng. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *myLabel;
@property (weak, nonatomic) IBOutlet UIButton *myButton;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    _myLabel.font = [UIFont fontWithName:@"loveway.ttf" size:17.0f];
    _myButton.titleLabel.font = [UIFont fontWithName:@"loveway" size:17.0f];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

运行。。。oh no !怎么没变,还是原来的样子


肯定是姿势不对,于是百度了一下(虽然我一般都用谷歌),的确这种方法不对


于是改变思路,先找出字体的名字,Like this,代码改成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewDidLoad {
    [super viewDidLoad];
    
    for(NSString *familyName in [UIFont familyNames]){
        NSLog(@"Font FamilyName = %@",familyName); //*输出字体族科名字

        for(NSString *fontName in [UIFont fontNamesForFamilyName:familyName]) {
            NSLog(@"\t%@",fontName);         //*输出字体族科下字样名字
        }
    }
    _myLabel.font = [UIFont fontWithName:@"loveway.ttf" size:17.0f];
    _myButton.titleLabel.font = [UIFont fontWithName:@"loveway" size:17.0f];
}

运行一看控制台

输出的字体名称部分截图
这什么鬼,我哪知道我刚加进去的字体名称是什么,这咋找


于是想出来个办法,再建一个工程,不加入 loveway.ttf 这个字体,打印出来,一个个对比,多的那个不就是了吗!bingo,于是花了一会功夫终于找出来了,是 FZLBJW--GB1-0 ,不管了,先试试看行不行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
![](https://upload-images.jianshu.io/upload_images/571495-b0d97825e5d33a8a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- (void)viewDidLoad {
    [super viewDidLoad];
    /*
    for(NSString *familyName in [UIFont familyNames]){
        NSLog(@"Font FamilyName = %@",familyName); //输出字体族科名字

        for(NSString *fontName in [UIFont fontNamesForFamilyName:familyName]) {
            NSLog(@"\t%@",fontName);         //输出字体族科下字样名字
        }
    }
     */
    _myLabel.font = [UIFont fontWithName:@"FZLBJW--GB1-0" size:17.0f];
    _myButton.titleLabel.font = [UIFont fontWithName:@"FZLBJW--GB1-0" size:17.0f];
}

运行,结果如下

改变字体后的运行结果
OK!达到效果了,虽然有点挫,但是效果达到了,还不错

到这里,基本的改变字体效果已达到。

查找字体的一种简单的方法

在上面我们可以看到,通过对比的方法找到了 FZLBJW--GB1-0 这个名字,这里,有一种简单的方法,
我们在 Finder 里面找到这个 ttf ,双击打开(在 Xcode 里面双击打开没效果),这时候系统就会用苹果自带的字体册打开,如下

使用字体册打开 `.rtf`
这样我们就可以看到了这个字体的族科名字,我们看到的是 FZLiBian-S02S ,于是我们在刚才输出全部字体名的控制台搜索一下这个族科名,就可以知道具体的字体名了

搜索 `FZLiBian-S02S`
这样就比上面简单多了。

进一步的思考

上面例子中简单的说了一下改变字体的方法,虽然成功了,但是我们不得不思考一下。上面只是两个简单的控件,那么我要是有一堆控件怎么办?或者你可以说我也可用这种方法一个个加,你要是纯代码写的还好,你要是 xib 写的,难道还要把一个个无用的只是显示一下的 label 或者 button 拉出来这样写吗?这样的话,效率肯定会非常低,尤其是那些写到一半的大工程,感觉这种方法肯定是行不通的。
这里利用 runtime 的 class_addMethod 、 class_replaceMethod 、method_exchangeImplementations 这几个方法,然后根据 + (void)load 这个方法的特性实现(关于 + (void)load 这个方法后面会说,或者不懂得童鞋可以先查查资料),代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//
//  UILabel+FontChange.m
//  LiquoriceDoctorProject
//
//  Created by HenryCheng on 15/12/7.
//  Copyright © 2015年 iMac. All rights reserved.
//

#import "UILabel+FontChange.h"
#import <objc/runtime.h>

#define CustomFontName @"FZLBJW--GB1-0"

@implementation UILabel (FontChange)

+ (void)load {
    //方法交换应该被保证,在程序中只会执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获得viewController的生命周期方法的selector
        SEL systemSel = @selector(willMoveToSuperview:);
        //自己实现的将要被交换的方法的selector
        SEL swizzSel = @selector(myWillMoveToSuperview:);
        //两个方法的Method
        Method systemMethod = class_getInstanceMethod([self class], systemSel);
        Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
        
        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (isAdd) {
            //如果成功,说明类中不存在这个方法的实现
            //将被交换方法的实现替换到这个并不存在的实现
            class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        } else {
            //否则,交换两个方法的实现
            method_exchangeImplementations(systemMethod, swizzMethod);
        }
        
    });
}

- (void)myWillMoveToSuperview:(UIView *)newSuperview {
    
    [self myWillMoveToSuperview:newSuperview];
//    if ([self isKindOfClass:NSClassFromString(@"UIButtonLabel")]) {
//        return;
//    }
    if (self) {
        if (self.tag == 10086) {
            self.font = [UIFont systemFontOfSize:self.font.pointSize];
        } else {
            if ([UIFont fontNamesForFamilyName:CustomFontName])
                self.font  = [UIFont fontWithName:CustomFontName size:self.font.pointSize];
        }
    }
}

@end

然后不加任何代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//
//  ViewController.m
//  ChangeFont
//
//  Created by HenryCheng on 16/4/27.
//  Copyright © 2016年 HenryCheng. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *myLabel;
@property (weak, nonatomic) IBOutlet UIButton *myButton;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
//    for(NSString *familyName in [UIFont familyNames]){
//        NSLog(@"Font FamilyName = %@",familyName); //输出字体族科名字
//
//        for(NSString *fontName in [UIFont fontNamesForFamilyName:familyName]) {
//            NSLog(@"\t%@",fontName);         //输出字体族科下字样名字
//        }
//    }
    
//    _myLabel.font = [UIFont fontWithName:@"FZLBJW--GB1-0" size:17.0f];
//    _myButton.titleLabel.font = [UIFont fontWithName:@"FZLBJW--GB1-0" size:17.0f];
//    _myLabel.tag = 10086;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

运行


我们可以看到字体改变了。
如果有人说我有的想改变字体有的不想改变字体怎么办,我这里有个简单的办法就是设置 tag ,比如我设置 labeltag10086(随便起的),就让他字体不改变


运行结果


注意:
1、如果你是代码写控件,你不想改变字体,你只需在创建的时候设置 tag10086
2、上面代码中注释了一行

1
2
3
//    if ([self isKindOfClass:NSClassFromString(@"UIButtonLabel")]) {
//        return;
//    }

这个是当时写的时候不改变 buttontitle 字体设置的,在这里你可以判断那种类型的改哪种不改,比如说你不想改 button 的字体,把这一句解注释即可
3、如果你是 xib 拉的控件,你不想改变字体,你必须在 xib 界面设置 tag10086 ,不可加载完毕后在 - (void)viewDidLoad 里面设置,这还是因为 + (void)load 这个方法:

  • 在一个程序(main函数)运行之前,所用到的库被加载到 runtime 之后,被添加到的 runtime 系统的各种类和 category 的 +load 方法就被调用;(关于这点很容易通过打印语句来验证);
  • 如果父类和子类的 +load 方法都被调用,父类的调用一定在子类之前,这是系统自动完成的,子类 +load 中没必要显式调用 [super load] ;
    这里只是简单的说一下,具体不理解的可以翻翻官方文档

最后

关于代码的解释,在工程里都已经注释的非常清楚了,这里就不多说了,不清楚的童鞋可以给我留言。具体用法很简单,你只需要将 UILabel+FontChange.hUILabel+FontChange.m 拉进你的工程即可。
需要下载更多字体的可以在 字体库下载,所有的代码都可以在 这里下载。
最近在看 swift ,做了一下笔记,后面会为大家分享总结的一些 swift tips
最后,如果你有什么建议或者指正的地方请给我留言,如果喜欢或者对你有帮助的话,就请 star 一下吧,谢谢!

若您觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏!
------------- 本文结束 感谢您的阅读 -------------