# runtime实战之一键改变字体

语言：zh-CN
原文：[runtime实战之一键改变字体](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao43>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao43>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-05-04-objective-c-runtime-in-practice-changing-fonts-throughout-an-interface/>)
发布于: 2016-05-04T00:00:00+08:00
内容更新于: 2016-05-04T00:00:00+08:00
署名：HenryCheng

从注册自定义字体和查找字体名称开始，利用 Objective-C 运行时方法处理界面字体替换，并讨论代码创建控件与 storyboard 控件在统一设置时的差别。

原文署名HenryCheng，Bison 编辑收录。

[原文所列来源](<http://www.jianshu.com/p/b9fdd17c525e>)

**阅读前** 这是 2015–2017 年间的技术记录，本次整理未重新运行验证示例。涉及的 API、依赖和工具版本请结合发表日期阅读。

编辑：[Bison](<https://allluckly.cn/>)  
 投稿：[HenryCheng](<http://www.jianshu.com/p/b9fdd17c525e>)

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/1.jpg>)

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

## 1、准备

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/2.png>)

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/3.png>)

嗯，就这样，很简单，运行

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/4.png>)

好的显示正常，没什么问题，接下来改变字体。

## 2、改变字体

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/5.png>)

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/6.png>)

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

```
//
//  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 ！怎么没变，还是原来的样子

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/7.gif>)

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/8.jpg>)

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

```
- (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];
}
```

运行一看控制台

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/9.png>)

这什么鬼，我哪知道我刚加进去的字体名称是什么，这咋找

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/10.jpg>)

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

```
- (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];
}
```

运行，结果如下

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/11.png>)

改变字体后的运行结果

OK！达到效果了，虽然有点挫，但是效果达到了，还不错

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/12.gif>)

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

## 3、查找字体的一种简单的方法

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/13.png>)

这样我们就可以看到了这个字体的族科名字，我们看到的是FZLiBian-S02S，于是我们在刚才输出全部字体名的控制台搜索一下这个族科名，就可以知道具体的字体名了

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/14.png>)

这样就比上面简单多了。

## 4、进一步的思考

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

```
//
//  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
```

然后不加任何代码如下

```
//
//  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
```

运行

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/15.png>)

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

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/16.png>)

运行结果

![1](<https://allluckly.cn/images/blog/tuogao/tougao43/17.png>)

### 注意：

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

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

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

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

## 5、最后

关于代码的解释，在工程里都已经注释的非常清楚了，这里就不多说了，不清楚的童鞋可以给我留言。具体用法很简单，你只需要将UILabel+FontChange.h和UILabel+FontChange.m拉进你的工程即可。 需要下载更多字体的可以在 [字体库下载](<http://www.webpagepublicity.com/free-fonts.html>)，所有的代码都可以在 [这里](<https://github.com/Loveway/HWChangeFont>)下载。  
 最近在看swift，做了一下笔记，后面会为大家分享总结的一些swift tips。  
 最后，如果你有什么建议或者指正的地方请给我留言，如果喜欢或者对你有帮助的话，就请star一下吧，谢谢！

---

好文推荐：[iOS开发之超链接富文本](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tuogao42>)

