美文网首页iOS程序员读书iOS开发
iOS中利用 runtime 一键改变字体

iOS中利用 runtime 一键改变字体

作者: HenryCheng | 来源:发表于2016-04-27 21:58 被阅读9857次

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


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

    1、准备


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

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

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

    运行结果

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

    2、改变字体


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

    plist

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

    phases

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

    //
    //  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,代码改成这样

    - (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,不管了,先试试看行不行
    ![](https://img.haomeiwen.com/i571495/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!达到效果了,虽然有点挫,但是效果达到了,还不错



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

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


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

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

    这样就比上面简单多了。

    4、进一步的思考


    上面例子中简单的说了一下改变字体的方法,虽然成功了,但是我们不得不思考一下。上面只是两个简单的控件,那么我要是有一堆控件怎么办?或者你可以说我也可用这种方法一个个加,你要是纯代码写的还好,你要是xib写的,难道还要把一个个无用的只是显示一下的label或者button拉出来这样写吗?这样的话,效率肯定会非常低,尤其是那些写到一半的大工程,感觉这种方法肯定是行不通的。
    这里利用runtimeclass_addMethodclass_replaceMethodmethod_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
    

    运行


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

    运行结果

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

    //    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];;
      这里只是简单的说一下,具体不理解的可以翻翻官方文档

    5、最后

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

    相关文章

      网友评论

      • 飞飞飞鱼哥:卖的一手好萌
      • 羊羊羊的洋:我在Xib中设置的tag值 但是字体还是被统一的分类修改了字体
      • 4f6a2617054e:swift 不能用load方法怎么搞
      • ShenYj:有什么免费字体下载的连接吗?
      • PGOne爱吃饺子:您好,写的很不错,我想请假一下,你是不是给lbael添加了一个类别的方法,然后在里面重新修改了willMoveToSuperview这个方法,实现了lable的字体变化,如果是的我想请问一下,为什么不是直接修改willMoveToSuperview这个方法来修改呢,当然也是调用一下super的,
      • 码代码的李二狗:不错 不错
      • 风茗夜雨:具有局限性
      • 空转风:楼主你好,这样的话只是在打开app的时候改变字体,但是如果是这样一个需求“打开app的时候显示系统字体,点击某个按钮才改变字体”这样要如何用runtime实现?求教
        阿文灬:@小蚊子叮迎行 使用runtime让UILabel监听字体改变的通知
        阿文灬:通知
      • 天蝎座沫沫: if ([self.superview isKindOfClass:NSClassFromString(@"UINavigationButton")]) {
        return;
        }需要在方法里添加此判断,否则在项目运行中会崩溃.正好升级iOS10,好多用xib创建的label文字显示不全,谢谢作者了.还有不明白的地方 [self myWillMoveToSuperview:newSuperview];为什么调用
      • 金康帅:runtime的利用 :smile:
      • JihanWen:感觉没什么用 一键如何更换?
        JihanWen:@JihanWen 疑惑 求解答
      • FindCrt:老老实实用继承吧,这种说不定自己都忘了还有个10086的逻辑
      • Stark_Dylan:可以直接替换UIFont的systemFontOfSize与fontWithName:方法
      • 329f45f57341:我这会出现这种崩溃,有遇到过吗? Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_UIFontCacheKey objectForKey:]: unrecognized selector sent to instance
      • d4d13ddb95dd:哇 试试去~ :whale:
        HenryCheng:@朴素妍 韩国思密达
      • 横穿撒哈拉的骆驼:这两段代码没看懂 , 有点疑惑的地方
        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        //这里为啥是swizzMethod的implementation方法
        if (isAdd) {
        //如果成功,说明类中不存在这个方法的实现
        //将被交换方法的实现替换到这个并不存在的实现
        class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        } else {
        //否则,交换两个方法的实现
        method_exchangeImplementations(systemMethod, swizzMethod);
        }
      • yj603:学习了
        HenryCheng:@yj603 :smile:
      • 8b858db87184:第三段展示的代码,上有一处md显示图片的语句编辑失误了,图没显示出来:kissing_heart::kissing_heart:
        8b858db87184:@HenryCheng 你再看一下,平均不能放图:smile:
        HenryCheng:@快乐的木木金冈 啊?哪里
      • 6239961ce796:还有一个问题,如果是webView里面的字体有应该怎么改呢?
        6239961ce796:@HenryCheng 不过那个字体库好大,楷体就70Mb,网易需要下载,不过也就3-4Mb。
        6239961ce796:@HenryCheng 我问了我们PHP,好像是可以改,然后再把手机端的改了,就和网易一样的功能了。
        HenryCheng:@6239961ce796 前端改 :smile:
      • 6239961ce796:如果是StoryBoard的cell里面的一些label,这些label我已经加了tag来区分了怎么破。。。if self.tag == 10086 || self.tag == 10000 || self.tag == 10001 ?
        HenryCheng:@6239961ce796 目前只是一个临时的解决办法,后面优化的话,我想写一个Label继承于UILabel,然后区分一下,不知道你还有什么好的建议没
      • FengxinLi:请问一下楼主 SEL systemSel = @selector(willMoveToSuperview:); 这个方法是在什么时候调用,为什么设置字体需要替换这个方法呢? 感觉字面意思是将移动父视图
        HenryCheng:@Fengxinliju 你说的如果是程序里面有个按钮可以改变字体的话肯定就不是这种方法了
        FengxinLi:@HenryCheng 那设置字体有可能 是在添加到父视图之后呢?
        HenryCheng:@Fengxinliju 在一个子视图将要被添加到另一个视图的时候会调用此方法
      • Chars:很诙谐的方式阐述呀
        HenryCheng:@Chars :smile:
      • 杀不死bill:不用import这个拓展类吗?
        HenryCheng:@杀不死bill 不需要的
      • qxy:我以为只有我会用 10086 当tag
      • Shawn_Locke:收藏了,感谢分享
        HenryCheng:@Shawn_Locke 谢谢喜欢
      • 飛天江郎:好逗比的程序员,不过老衲喜欢 :flushed:
        HenryCheng:@EzioChen :disappointed_relieved::disappointed_relieved::disappointed_relieved:
      • 8ec87a668df4:写的不错,刚用到
        HenryCheng:@KlausOliver 有用就好
      • Hom_zhang:willMoveToSuperview 为什么是这个呢
        HenryCheng:@Hom_zhang :joy::joy::joy:
        Hom_zhang:@HenryCheng 我是疑问句的,不过后来查下, 这个方法比我以前的好多了, 之前是替换init initWithFrame awakeFromNib 之类的。 你这个在他添加到superView的时候添加更加通用哦 :+1:
        HenryCheng:@Hom_zhang 还有什么好的建议吗
      • UItachi:建议用继承,不要用runtime加Category
        UItachi:@niujun_iOS 至少在这个案例上看,用继承更能统一所有Label的属性。
        niujun_iOS:@UItachi 为什么?rumtime加category有什么不好吗?新人纯学习
        HenryCheng:@UItachi 嗯
      • Seeulater:这种方法 还是不建议 使用 。。。 如果你们的工程在后期 需要通过这样的方法 来 改变一些东西的话 ,那就说明 你们在项目开发的时候 没有考虑好 , 没有做一个好的架构 !
        HenryCheng:@Seeulater 常规到特殊字体再到常规。。。。。。
        Seeulater:@HenryCheng 所以说程序员 真的很苦逼 , 产品设计上所有的坑 到最后 都是 我们 来扛
        HenryCheng:@Seeulater 是的,不过这也是没办法的事
      • kosser小屋:可以给font写个category,然后把那个查询字体名封装一下嘛,不过你的这个效率可能会高一点…
        HenryCheng:@kosser小屋 这个是个思路,不过没试过
      • Mg明明就是你:居然可以这样,那修改背景,比如夜间模式和白天模式,也可以使用runtime来做吗?
        HenryCheng:@Mg明明就是你 可以的
      • 浪子小兵iOS:谢谢den' w
      • Mellong:写得不错,学习了
        HenryCheng:@Mellong 谢谢喜欢
      • 4ced644c80c9:感谢分享
        HenryCheng:@田园小 喜欢就好
      • Keep丶Dream:写的不错
        HenryCheng:@Keep丶Dream 谢谢支持
        HenryCheng:@Keep丶Dream 谢谢支持
      • 回忆那么凶qwer:叼叼叼,老程
        HenryCheng:@你总是笑笑不说话 这是?
      • 何止玉树临风:先收藏,辛苦了
        HenryCheng:@何止玉树临风 谢谢喜欢
      • DSperson:幸苦 了 收藏
        HenryCheng:@Ds_Run 谢谢喜欢

      本文标题:iOS中利用 runtime 一键改变字体

      本文链接:https://www.haomeiwen.com/subject/lvicrttx.html