系统字体 "Hiragino Sans" 显示为剪裁的上升和下降

System font "Hiragino Sans" is show with clipped ascender and descenders

我需要在我的应用程序中使用 iOS 的系统字体 "Hiragino Sans W3",但无论我选择哪个 size/style 或 UILabel 的尺寸,字体的上行和下行总是被剪掉:

这似乎可以通过创建 UILabel 的子类并将方法 textRectForBounds:limitedToNumberOfLines: 覆盖为 return "correct" 值来解决。所以下面的代码...

- (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines
{
    CGRect result = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines];
    result = CGRectInset(result, 0, -5);
    return result;
}

...导致上部和下部不再被剪裁:

我知道也可以使用外部编辑器来调整字体的升序和降序位置。 但是这是系统字体,不修改不应该能正常工作吗?我在这里遗漏了什么吗?

提前感谢您的回答。

我认为这个问题归结为字体本身 ("Hiragino Sans")。使用字体编辑器可以看到字形超出了上升值和下降值,这就是 iOS 似乎假设显示文本的垂直 "bounding box"。

由于缺乏更好的解决方案,我一直在使用(非常丑陋的)技巧来修改 UIFont read-only 属性 ascender 和 [=14= 的值].

文件UIFont+Alignment.h:

#import <UIKit/UIKit.h>

@interface UIFont (Alignment)

- (void)ensureCorrectFontAlignment;

@end

文件UIFont+Alignment.m:

#import "UIFont+Alignment.h"
#import <objc/runtime.h>
#import <objc/message.h>

static NSHashTable *adjustedFontsList;

@implementation UIFont (Alignment)

- (void)ensureCorrectFontAlignment
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        adjustedFontsList = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
    });

    @synchronized (adjustedFontsList) {

        if ([adjustedFontsList containsObject:self]) {
            return;
        }
        else if ([self.fontName containsString:@"Hiragino"]) {

            SEL originalAscenderSelector = @selector(ascender);
            Method originalAscenderMethod = class_getInstanceMethod([UIFont class], originalAscenderSelector);
            SEL originalLineHeightSelector = @selector(lineHeight);
            Method originalLineHeightMethod = class_getInstanceMethod([UIFont class], originalLineHeightSelector);

            id result = method_invoke(self, originalAscenderMethod, nil);
            CGFloat originalValue = [[result valueForKey:@"ascender"] floatValue];
            [result setValue:@(originalValue * 1.15) forKey:@"ascender"];

            result = method_invoke(self, originalLineHeightMethod, nil);
            originalValue = [[result valueForKey:@"lineHeight"] floatValue];
            [result setValue:@(originalValue * 1.25) forKey:@"lineHeight"];

            [adjustedFontsList addObject:self];
        }
    }
}

@end

要应用,只需导入 header 并在创建新字体实例后调用 [myUIFont ensureCorrectFontAlignment]

帮助了我,但是我遇到了错误

Too many arguments to function call, expected 0, have 2 method_invoke

我替换了

id result = method_invoke(self, originalAscenderMethod, nil);

id (*function)(id, Method) = (id (*)(id, Method)) method_invoke; id result = function(self, originalAscenderMethod);

result = method_invoke(self, originalLineHeightMethod, nil);

result = function(self, originalLineHeightMethod);

Swift 使用方法调配的版本:

extension UIFont {

    static func swizzle() {
        method_exchangeImplementations(
                class_getInstanceMethod(self, #selector(getter: ascender))!,
                class_getInstanceMethod(self, #selector(getter: swizzledAscender))!
        )
        method_exchangeImplementations(
                class_getInstanceMethod(self, #selector(getter: lineHeight))!,
                class_getInstanceMethod(self, #selector(getter: swizzledLineHeight))!
        )
    }

    private var isHiragino: Bool {
        fontName.contains("Hiragino")
    }

    @objc private var swizzledAscender: CGFloat {
        if isHiragino {
            return self.swizzledAscender * 1.15
        } else {
            return self.swizzledAscender
        }
    }

    @objc private var swizzledLineHeight: CGFloat {
        if isHiragino {
            return self.swizzledLineHeight * 1.25
        } else {
            return self.swizzledLineHeight
        }
    }

}

调用 UIFont.swizzle() 一次,例如在 application(_:didFinishLaunchingWithOptions:).