Swizzle a Font
The solution is generally straightforward if you swizzle the UIFont class. I think it is best to pick a sane font like Helvetica Neue and override that. This allows you to put the whole project back into the standard by pulling the mapping. I had already come up with a swizzle to achieve this goal when I realized someone else did it too so I mashed it up a bit. As NSHipster will tell you swizzling can be dangerous, but in this case the risk is pretty low considering the absolute simplicity of the UIFont API. In my case it was done for an Enterprise app so it was even lower risk.
UIFont+CustomFont.m Category
#import <objc/runtime.h>
static NSString *const kFontMapPlist = @"FontMap";
static NSDictionary *_replacementFontDictionary = nil;
@implementation UIFont (CustomFont)
static void initializeReplacementFonts()
{
static BOOL initialized = NO;
if (initialized)
return;
initialized = YES;
// A Plist with a Dictionary from->to font name mapping
NSURL *replacementFontMapURL = [[NSBundle mainBundle] URLForResource:kFontMapPlist withExtension:@"plist"];
NSDictionary *replacementFontMap = [NSDictionary dictionaryWithContentsOfURL:replacementFontMapURL];
[UIFont setReplacementFontDictionary:replacementFontMap];
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
initializeReplacementFonts();
SEL fontWithNameSizeSelector = @selector(fontWithName:size:);
SEL swizzledFontWithNameSizeSelector = @selector(clp_fontWithName:size:);
SwizzleClassMethod([UIFont class], fontWithNameSizeSelector, swizzledFontWithNameSizeSelector);
SEL fontWithDescriptorSizeSelector = @selector(fontWithDescriptor:size:);
SEL swizzledfontWithDescriptorSelector = @selector(clp_fontWithDescriptor:size:);
SwizzleClassMethod([UIFont class], fontWithDescriptorSizeSelector, swizzledfontWithDescriptorSelector);
});
}
void SwizzleClassMethod(Class class, SEL originalSelector, SEL replacementSelector)
{
Class clazz = objc_getMetaClass(class_getName(class));
Method originalMethod = class_getClassMethod(clazz, originalSelector);
Method replacementMethod = class_getClassMethod(clazz, replacementSelector);
// Add method if it doesn't eixst
BOOL didAddMethod =
class_addMethod(clazz,
originalSelector,
method_getImplementation(replacementMethod),
method_getTypeEncoding(replacementMethod));
if (didAddMethod) {
class_replaceMethod(clazz,
replacementSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, replacementMethod);
}
}
#pragma mark - Swizzled font by descriptor and name calls
+ (UIFont *)clp_fontWithDescriptor:(UIFontDescriptor *)descriptor size:(CGFloat)pointSize
{
NSString *originalFontName = descriptor.fontAttributes[UIFontDescriptorNameAttribute];
NSString *replacementFontName = _replacementFontDictionary[originalFontName];
UIFontDescriptor *replacementFontDescriptor = descriptor;
if (replacementFontName != nil) {
replacementFontDescriptor = [UIFontDescriptor fontDescriptorWithFontAttributes:@{UIFontDescriptorNameAttribute: replacementFontName}];
}
return [self clp_fontWithDescriptor:replacementFontDescriptor size:pointSize];
}
+ (UIFont *)clp_fontWithName:(NSString *)fontName size:(CGFloat)fontSize
{
NSString *replacementFontName = _replacementFontDictionary[fontName];
if (replacementFontName == nil) {
replacementFontName = fontName;
}
return [self clp_fontWithName:replacementFontName size:fontSize];
}
#pragma mark - Replacement Dictionary Getter and Setter
+ (NSDictionary *)replacementDictionary
{
return _replacementFontDictionary;
}
+ (void)setReplacementFontDictionary:(NSDictionary *)replacmentFontDictionary
{
if (replacmentFontDictionary == _replacementFontDictionary) {
return;
}
_replacementFontDictionary = replacmentFontDictionary;
// Validate font existence.
for (NSString *originalFontName in [_replacementFontDictionary allKeys]) {
NSString *replacementFontName = [_replacementFontDictionary objectForKey:originalFontName];
UIFont *replacementFont = [UIFont fontWithName:replacementFontName size:10.0f];
if (replacementFont == nil) {
DDLogError(@"WARNING: replacement font '%@' is not available.", replacementFontName);
}
}
}
@end
The FontMap.plist
For the sake of simplicity we'll pretend we have a font called CustomSans.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>HelveticaNeue-Light</key>
<string>CustomSans-Light</string>
<key>HelveticaNeue-LightItalic</key>
<string>CustomSans-LightItalic</string>
<key>HelveticaNeue-Bold</key>
<string>CustomSans-Bold</string>
<key>HelveticaNeue-BoldItalic</key>
<string>CustomSans-BoldItalic</string>
</dict>
</plist>