欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iconfont 实践及使用优化

程序员文章站 2024-02-16 20:57:04
...

公司在前一段时间将大量图标进行了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。

iconfont 实践及使用优化

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

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

iconfont 实践及使用优化

生成字体库

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

iconfont 实践及使用优化

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

iconfont 实践及使用优化

使用图标字体库

因为后面会在 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,回车。

iconfont 实践及使用优化

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

iconfont 实践及使用优化

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

iconfont 实践及使用优化

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

iconfont 实践及使用优化

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

iconfont 实践及使用优化

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

- (void)viewDidLoad {
    [super viewDidLoad];

    // 灯泡
    _iconLabel.text = @"\U0000e881";
}

8.编译运行

iconfont 实践及使用优化


优化部分

以上就是创建和使用 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

iconfont 实践及使用优化

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

iconfont 实践及使用优化

设置一下值,看看效果

iconfont 实践及使用优化

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

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

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

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

iconfont 实践及使用优化

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

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

iconfont 实践及使用优化

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

iconfont 实践及使用优化

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

iconfont 实践及使用优化

接下来要做的就是通过 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的使用:

iconfont 实践及使用优化

这下,不需要查看图标编码就可以设置图标了。
先看看 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
如有发现什么问题,欢迎提出,谢谢。