关于字体适配的那些事

作者: ColaBean | 来源:发表于2016-01-18 00:23 被阅读2700次

    前言

    之前做过很多项目都没考虑过字体适配问题。相信绝大多数人在做项目时,都没仔细去考虑这件事。一般都是根据UI出的图做个估算,有耐心的估计会自己拿工具测量下。如今,考虑到iPhone机型的多样性,UI设计师不可能针对每一款iPhone的屏幕出一套UI图。一般而言,都是基于5s的标准出UI。当我们在设置字体时,往往都是基于UI并且针对不同的屏幕字体也都是绝对的。那么问题来了,细心的同学可能会注意到,相同大小的字体在5s或6上也许差别不大,但在6p上字体有缩小的现象,其原因由分辨率导致。
    在6出来不久,曾看过有关适配的文章,其中关于iPhone尺寸规格如下:

    设备 对角线 逻辑分辨率 scale Factor 设备分辨率 PPI
    3GS 2.4inch 4.5inch 3.5inch 320x480 @1x 320x480 163
    4(s) 2.31inch 4.5inch 3.5inch 320x480 @2x 640x960 326
    5c 2.33inch 4.90inch 4inch 320x568 @2x 640x1136 326
    5(s) 2.31inch 4.87inch 4inch 320x568 @2x 640x1136 326
    6 2.64inch 5.44inch 4.7inch 375x667 @2x 750x1334 326
    6p 3.06inch 6.22inch 5.5inch 414x736 @3x 1242x2208 401

    从iPhone3GS/iPhone4(s)过渡到iPhone5(s)时,在逻辑上宽度不变高度稍高,之前旧的素材和布局通过AutoresizingFlexible简单适配即可运行得很好,但由于高宽比增大,上下两端出现黑粗边(典型如LaunchImage)。从分辨率的角度来看,除了需要提供LaunchImage这种满屏图,其他基本沿用二倍图(@2x);从屏幕尺寸角度来看,需要对纵向排版略加调整。
    从iPhone5(s)发展到iPhone6(+),由于高宽比保持不变,iOS对图标、图片、字体进行等比放大自适应,清晰度会有所降低。同时,绝对坐标布局会导致在大屏下出现偏左偏上的问题。从分辨率的角度来看,iPhone6沿用二倍图(@2x),但需为iPhone6+提供更高的三倍图(@3x);从屏幕尺寸角度来看,需要重新对UI元素尺寸和布局进行适配,以期视觉协调。

    字体适配

    以上属于科普类的东西,下面来点实际的。

    关于字体适配有2种方案。

    • 方案一:
      设置一个大小区域范围,比如10~30pointSize的范围(pointSize为UIFont的一个CGFloat类型的属性),然后for循环降序遍历此范围设置一个临时的UIFont变量,根据此变量计算当前文本的大小,与当前UILabelheight作比较找出合适的字体。
    
    #define ADAPTIVE__FONT_SIZE_MINIMUM_VALUE 20
    #define ADAPTIVE_FONT_SIZE_MAXIMUM_VALUE 30
    
    -(UIFont *) adjustFontSizeToFillItsContents
    {
        NSString* text = self.text;
        
        for (int i = ADAPTIVE_FONT_SIZE_MAXIMUM_VALUE; i>ADAPTIVE__FONT_SIZE_MINIMUM_VALUE; i--) {
            
            UIFont *font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
            NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: font}];
            
            CGRect rectSize = [attributedText boundingRectWithSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
            
            if (rectSize.size.height <= self.frame.size.height) {
                return [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
                break;
            }
        }
        return self.font;
    }
    
    
    • 方案二:
      计算出一个scale重新设置UIFont,伪代码如下:
    CGFloat scale = ([UIScreen mainScreen].bounds.size.width / 320);
    NSLog(@"before : %.1f", [font pointSize]);
    font = [UIFont fontWithName:[font fontName] size:fontSize * scale];
    NSLog(@"after : %.1f", [font pointSize]);
    

    既然需要重新设置UIFont,那么我们不可避免的要hookUIFont的类方法``fontWithName:size:做个函数交换的处理。 函数的交换我们需要用到runtime`机制。

    void bd_exchageClassMethod(Class aClass, SEL oldSEL, SEL newSEL)
    {
        Method oldClsMethod = class_getClassMethod(aClass, oldSEL);
        assert(oldClsMethod);
        Method newClsMethod = class_getClassMethod(aClass, newSEL);
        assert(newClsMethod);
        method_exchangeImplementations(oldClsMethod, newClsMethod);
    }
    

    然后,我们给UIFont创建一个Categroy文件,文件名为AdaptiveFont。在实现文件代码如下:

    @implementation UIFont (AdaptiveFont)
    
    + (void)hook
    {
        bd_exchageClassMethod([UIFont class], @selector(fontWithName:size:), @selector(hook_fontWithName:size:));
    }
    
    + (UIFont *)hook_fontWithName:(NSString *)fontName size:(CGFloat)fontSize
    {
        NSLog(@"before : %.1f", fontSize);
        CGFloat scale = ([UIScreen mainScreen].bounds.size.width / 320);
        NSLog(@"scale : %f", scale);
        UIFont *font = [self hook_fontWithName:fontName size:fontSize * scale];
        NSLog(@"after : %.1f", [font pointSize]);
        printf("<--------------------->\n");
        return font;
    }
    
    @end
    

    接口文件暴漏相关方法如下:

    @interface UIFont (AdaptiveFont)
    
    + (void)hook;
    + (UIFont *)hook_fontWithName:(NSString *)fontName size:(CGFloat)fontSize;
    
    @end
    

    相对比较而言,我还是倾向于方法二。方法一的前提条件是height要适配好,不能是绝对值,否效果。当然,方法二也一样,只不过height若是绝对值,会出现文字显示不全的问题。
    在用法上,方法一只需调用adjustFontSizeToFillItsContents,而方法二需在application:didFinishLaunchingWithOptions:函数调用下hook

    当然,这并不是最终也不是最好的适配方案。个人觉得根据PPI适配字体,限于经历有限只能研究到这。
    欢迎纠错,有什么好的字体适配方案也可以在下方评论进行探讨。

    Demo地址:https://github.com/keleyundou/AdaptiveFontDemo

    相关文章

      网友评论

        本文标题:关于字体适配的那些事

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