天天看點

關于字型适配的那些事

前言

之前做過很多項目都沒考慮過字型适配問題。相信絕大多數人在做項目時,都沒仔細去考慮這件事。一般都是根據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~30

    pointSize

    的範圍(pointSize為

    UIFont

    的一個CGFloat類型的屬性),然後

    for

    循環降序周遊此範圍設定一個臨時的

    UIFont

    變量,根據此變量計算目前文本的大小,與目前

    UILabel

    height

    作比較找出合适的字型。
#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 / );
NSLog(@"before : %.1f", [font pointSize]);
font = [UIFont fontWithName:[font fontName] size:fontSize * scale];
NSLog(@"after : %.1f", [font pointSize]);
           

既然需要重新設定

UIFont

,那麼我們不可避免的要

hook

UIFont

的類方法

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