美文网首页iOS开发iOS Developer
iconfont 实践及使用优化

iconfont 实践及使用优化

作者: 木木烈少 | 来源:发表于2017-07-24 21:46 被阅读125次

    公司在前一段时间将大量图标进行了iconfont的替换,大大缩减了app的size。这几天,寻思着在自己app上也使用 iconfont,并且在使用上进行优化,使其在xib和 storyboard 上能像 UIImageView 那样所写即所见,所见即所得。

    iconfont 即通过自体形式进行 icon 的展示。其最大特点就是矢量性,即放大图标不虚化。

    下面先快速介绍下从创建到使用的过程以及对其使用进行优化,具体如下几点:

    1. 创建 iconfont 图标;
    2. 创建 iconfont 字体裤;
    3. 使用图标字体库;
    4. 在 iOS 使用上优化

    创建 iconfont 图标

    作为一个程序员,说真的我不会画图标,也觉得画起来特别烦,琐。好在阿里巴巴为我们提供了极大的便利。上网站:http://www.iconfont.cn

    在这个网站你不仅可以上传自己设计的字体图标,更重要的是这里有超多牛逼设计师已经设计好的 icon,你只需要根据自己app的需要进行搜索,然后聚合起来,再生成一个字体库就能使用了。当然了,如果你对某个 icon 觉得不满意,你完全可以直接对它进行简单编辑哈。使用起来绝对666。

    网站首页

    下面介绍下在别人创建好的 icon 上进行修改和保存。

    首先在首页上随便选择一个图标库,进入图标库详情后,挑选一个想修改的图标并将其加入购物车,然后添加到自己的项目上,接着在你创建的项目上即可看到这个图标。光标移动到图标,点击编辑即可。

    add icon

    生成字体库

    在选择了需要的图标并添加到项目后,点击【下载至本地】即可生成图标字体库。顺便说一下,创建项目时会让你指定字体库名称(默认是 iconfont),可以根据自己需要进行命名(在使用时会用到这个字体库名称)。

    new project

    下载到本地后,我们其目录结构如下:

    目录结构

    使用图标字体库

    因为后面会在 iOS 使用上进行优化,所以这里主要介绍在 iOS 上的使用。在android 和 web 上的使用可以参考官网的介绍: http://iconfont.cn/help/detail?spm=a313x.7781069.1998910419.d8cf4382a&helptype=code

    iOS上的使用

    其实这就是导入自定义字体,步骤是一模一样。

    1.创建一个项目,选择 Single View Application,填写项目名称,完成。如下图:

    (步骤太简单,图片不好意思显示)

    2.把下载的字体库 iconfont.ttf 文件拖入项目中,勾上 Copy Items If Needed,点击【Finish】;
    (步骤太简单,图片不好意思显示)

    3.选择 Info.plist 文件,添加一个 key,名称为 UIAppFonts,回车。

    添加字体key

    4.这时新建的这一项应该是个数组类型,在 item0 的 Value 项填上字体名称(注意是字体名称,不是 ttf 文件名称,即上面提到的在创建字体的时候所填的名称)

    添加自定义字体

    5.在 storyboard 上放置一个 UILabel 或 UIButton,设置为自定义字体,选择新添加的字体

    创建label

    6.ctrl + 拖动控件,关联属性

    关联属性

    7.设置字体图标 Unicode
    在我们下载的字体目录中,有个 demo_unicode.html 文件,打开它可以看到每个图标对呀的 Unicode 编号。

    图标Unicode

    给 label 的 text 设置上相应的值即可。如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // 灯泡
        _iconLabel.text = @"\U0000e881";
    }
    

    8.编译运行

    运行结果

    优化部分

    以上就是创建和使用 iconfont 的这个过程。使用起来倒不麻烦,就是有几个缺点:

    1. 每次使用图标都得查一下对应的 Unicode 编号;
    2. 对应着一堆 "\U0000xxxx",如果不写注释,真不知道是什么东西;
    3. 如果是使用存代码创建label,那通过代码设置图标还说得过去,但是使用 xib 或 storyboard 的时候还得用过一行代码来设置就有点说不过去了;

    这么说确实感觉挺麻烦的,有问题就有解决的答案。下面,介绍下我是怎么逐一解决这些问题的。

    问题2和3:能不能看着编码就知道是什么图标呢?怎样在 xib 上创建 label 的时候同时给设置上图标值呢?
    解决方案: 通过继承 label,结合 IB_DESIGNABLE 就能办到。

    首先创建 UILabel 子类 IconLabel,添加属性 iconName,顺便拓展下 IconLabel 功能,多添加个属性 selectedIconName,这样 IconLabel 就可以直接通过改变 selected 状态来改变预先设置好的图标了,如下代码:

    @interface IconLabel : UILabel
    
    /// 设置选中状态
    @property(nonatomic, assign) BOOL selected;
    /// 普通状态下的图片名称(只需要后面的4位 16 进制, 如:f3f7)
    @property(nonatomic, copy, nullable) NSString *iconName;
    /// 被选中状态下的图片名称(只需要后面的4位 16 进制,如:f3f7)
    @property(nonatomic, copy, nullable) NSString *selectedIconName;
    
    @end
    

    重写 iconName 和 selectedIconName setter 方法:

    #pragma mark - setter and getter
    
    - (void)setIconName:(NSString *)iconName {
        NSString *iName = iconUnicodeWithName(iconName);
        if (iName != _iconName) {
            _iconName = iName;
        }
    }
    
    - (void)setSelectedIconName:(NSString *)selectedIconName {
        NSString *iName = iconUnicodeWithName(selectedIconName);
        if (iName != _selectedIconName) {
            _selectedIconName = iName;
        }
    }
    

    其中 iconUnicodeWithName,是通过 NSScanner 将16进制字符串进行扫描,并转换为Unicode,如下:

    NSString *iconUnicodeWithName(NSString *name) {
        NSScanner *scanner = [NSScanner scannerWithString:name];
        unsigned int code;
        [scanner scanHexInt:&code];
        return [NSString stringWithFormat:@"%C", (unsigned short)code];
    }
    

    最后,因为我们要在 xib 上直接进行值的设置,所有需要为 IconLabel 类和其想在 xib 上显示的属性设置上关键字,如下:

    IB_DESIGNABLE
    
    @interface IconLabel : UILabel
    
    /// 设置选中状态
    @property(nonatomic, assign) BOOL selected;
    /// 普通状态下的图片名称(只需要后面的4位 16 进制, 如:f3f7)
    @property(nonatomic, copy, nullable) IBInspectable NSString *iconName;
    /// 被选中状态下的图片名称(只需要后面的4位 16 进制,如:f3f7)
    @property(nonatomic, copy, nullable) IBInspectable NSString *selectedIconName;
    
    @end
    

    其中 IB_DESIGNABLE 标识该类为可设计类, IBInspectable 标识属性为可视察属性(下面就能看到效果)。

    为了能让设置的值对应的图标实时显示,我们需要重写 UILabel 的 prepareForInterfaceBuilder 方法,为了不需要手动设置字体名称,我们需要在 awakeFromNib 方法上进行默认字体的设置,如下:

    #define IconFontName @"my_like_icon"
    
    @implementation IconLabel
    
    - (void)awakeFromNib {
        [super awakeFromNib];
        
        [self setupFontWithSize:self.font.pointSize];
        [self reloadView];
    }
    
    - (void)prepareForInterfaceBuilder {
        [super prepareForInterfaceBuilder];
        
        [self setupFontWithSize:self.font.pointSize];
        [self reloadView];
    }
    
    // 初始化字体
    - (void)setupFontWithSize:(CGFloat)fontSize {
        self.font = [UIFont fontWithName:IconFontName size:fontSize];
    }
    
    - (void)reloadView {
        self.text = _selected ? _selectedIconName : _iconName;
    }
    
    @end
    

    类的自定义这样就算完成了。现在,切换到 Main.storyboard,同样添加一个 UILabel 组件,并修改类为 IconLabel

    设置label类

    接着,切换到 Show the Attributes inspector 选择,你会发现多了两个可填框

    多出的框

    设置一下值,看看效果

    设置值

    so cool,如此美妙。现在你只需在 Icon Name 出设置上图标对应的字符串编号即可实时显示图标了。问题2和3解决。

    接着我们看看问题1:能不能不需要查图标对应的 Unicode 编号就能设置图标呢?
    等等,什么?你说问题2只解决了一半?虽然在 storyboard 上可以实时显示对应编码图标,但是如果我就用纯代码呢,不还是要注释是什么图标吗。而且,通过 storyboard 设置值又衍生出另一个问题了。
    问题4: 突然有一天产品说:把所有收藏图标从红心都换成五角星吧... 我的刀呢

    是的,如果只是上面这点实现,你还是要写注释说明。而问题4其实我们只要通过全局搜索再替换就行了(你就不想全局替换?那就听我慢慢道来哈)

    下面在解决问题1的同时,会相应的把 问题2和4解决。
    不查编码就能设置图标,其实很简单,你只需要将图标编码记住,就不用查了。

    逗我吗

    哈,还是进入正题吧。。。

    要想做到这点,其实一个映射表就能解决了(有没有顿悟的感觉)。映射表的格式很简单,key 值是图标名称(命名时当然是要做到见名称如见图标啦),而且这个名称是要中文还是英文完全取决于你,当然,你想让两者并存也完全没问题。而 value 的值就是图标对应的编号了。如下,我新建了个 plist 文件进行管理:

    IconMap

    怎么样,看到 key 可以很形象的想到是什么图标吧。
    技巧: 有个技巧可以快速往这个文件填充内容,就是通过选择文件然后 右击,然后选择 Open As -> Source Code,前面说到在下载下来的文件中有个叫 demo_unicode.html 的文件,通过 atom 或者 sublime 编辑器将其打开;然后复制需要的代码(即:图标编码那一块代码);接着,command + n 新建文件,将内容粘贴;然后还是见下图 gif 好了:

    快速设置内容

    最后将其粘贴到新建的plist 文件即可。

    粘贴内容

    接下来要做的就是通过 key 来映射相应的值,修改一下前面重写的 setter 方法,如下:

    - (void)setIconName:(NSString *)iconName {
        NSString *iName = IconNameSelector(iconName);
        if (iName.length) {
            iName = iconUnicodeWithName(iName);
        } else {
            iName = iconUnicodeWithName(iconName);
        }
        if (iName != _iconName) {
            _iconName = iName;
        }
    }
    
    - (void)setSelectedIconName:(NSString *)selectedIconName {
        NSString *iName = IconNameSelector(selectedIconName);
        if (iName.length) {
            iName = iconUnicodeWithName(iName);
        } else {
            iName = iconUnicodeWithName(selectedIconName);
        }
        if (iName != _selectedIconName) {
            _selectedIconName = iName;
        }
    }
    

    这里做了下兼容,如果 IconNameSelector 返回的内容是空的,则认为 iconName 就是图标的编码,直接进行字符串编码扫描;如果返回结果非空,则说明映射成功,把映射结果进行编码扫描。 这里 IconNameSelector 所做的就是读取映射文件并返回值。

    下面看看改进后的使用方法:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        IconLabel *iconLabel = [[IconLabel alloc] initWithIconName:@"灯泡" fontSize:30];
        iconLabel.frame = CGRectMake(100, 100, iconLabel.frame.size.width, iconLabel.frame.size.height);
        [self.view addSubview:iconLabel];
    }
    

    这下是不是感觉简单明了了。�再看看 storyboard的使用:

    storyboard使用改进

    这下,不需要查看图标编码就可以设置图标了。
    先看看 IconNameSelector 的实现:

    #define SRCROOT [[NSString stringWithCString:__FILE__ encoding:(NSUTF8StringEncoding)] stringByReplacingOccurrencesOfString:@"Classes/IconFontMap.h" withString:@""]
    
    static NSDictionary *iconFontMap = nil;
    
    static inline NSString * _Nullable iconfontWithName(NSString * __nonnull name) {
    #if DEBUG
        NSString *fullPath = [SRCROOT stringByAppendingString:@"IconFontMap.plist"];
        iconFontMap = [NSDictionary dictionaryWithContentsOfFile:fullPath];
    #endif
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSString *path = [[NSBundle mainBundle] pathForResource:@"IconFontMap" ofType:@"plist"];
            iconFontMap = [NSDictionary dictionaryWithContentsOfFile:path];
        });
        return iconFontMap[name];
    }
    
    #define IconNameSelector(key) iconfontWithName(key)
    
    

    注意到 #if Debug ... #endif 包起来的代码。其实,因为将映射表放在文件上,通过 [NSBundle mainBundle] 获取的路径在编译时是没法读取到的,所以为了在 storyboard 的使用中能实时显示图标,我稍微做了黑科技,本来想通过代码获取 ${SRCROOT} 环境变量的路径的,不过没找到方法,所有通过 FILE 这个编译器内置宏来获取当前文件路径,再将后面路径替换掉来获取到项目路径。
    如果你觉得这个方法有点矬,你可以尝试用另一种方法:
    通过脚本获取 ${SRCROO} 路径,然后编译后动态去定义
    另外,其实你完全可有直接定义一个 NSDictionary 对象,直接将代码贴上:

    static NSDictionary *iconFontMap = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        iconFontMap = @{
                        @"key1": @"name1",
                        @"key2": @"name2"
                        };
    });
    

    以上,即在使用 iconfont 时的基本优化过程。当然,在实际用途中,还封装了 IconButton 和 IconImageView,项目地址:
    https://github.com/linshaolie/IconFontExtension
    如有发现什么问题,欢迎提出,谢谢。

    想获得第一手精彩文章,欢迎关注我的微信公众号:"iOS和Android干货"

    扫一扫发现更多精彩文章

    相关文章

      网友评论

      • Leopx:彩色的复杂图片可以填充吗??
        Misaki_yuyi:同求彩色图标解决方案
      • 阿飞_1217:非常6。 有swift版本的demo吗?
      • MarkNote:挺实用的。谢谢分享。

      本文标题:iconfont 实践及使用优化

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