iOS开发-使用网络特殊字体下载CGFontRef

iOS开发-使用网络特殊字体下载CoreText

在开发中遇到需要将字体下载后再显示的情况,这种特殊字体下载后才能正常。

一、字体下载器

在AFNetworking中添加

pod 'Reachability'

字体下载器使用AFNetworking实现将字体文件下载
代码如下

#import "SDFontDownloaderClient.h"
#import "AFNetworking.h"

@implementation SDFontDownloaderClientError

@end

@interface SDFontDownloaderClient ()

@property (nonatomic, strong) AFHTTPSessionManager *httpManager;

@end

@implementation SDFontDownloaderClient

+ (instancetype)sharedInstance {
    static SDFontDownloaderClient *_sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[SDFontDownloaderClient alloc] init];
        _sharedInstance.httpManager = [AFHTTPSessionManager manager];
        _sharedInstance.httpManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", @"text/html", @"text/plain", nil];
    });
    
    return _sharedInstance;
}

- (AFHTTPSessionManager *)httpManager{
    if (!_httpManager) {
        _httpManager = [[AFHTTPSessionManager alloc] init];
        _httpManager.operationQueue.maxConcurrentOperationCount = 6;
        _httpManager.requestSerializer = [AFJSONRequestSerializer serializer];
        [_httpManager.requestSerializer willChangeValueForKey:@"timeoutInterval"];
        [_httpManager.requestSerializer setTimeoutInterval:10];
        [_httpManager.requestSerializer setStringEncoding:NSUTF8StringEncoding];
        _httpManager.responseSerializer = [AFJSONResponseSerializer serializer];

        _httpManager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"multipart/form-data", @"application/json", @"text/html", @"image/jpeg", @"image/png", @"application/octet-stream", @"text/json", @"text/javascript", @"text/html", nil];
        
        _httpManager.requestSerializer.HTTPMethodsEncodingParametersInURI = [NSSet setWithArray:@[@"POST", @"GET", @"HEAD", @"PUT", @"DELETE"]];
    }
    
    [_httpManager.requestSerializer setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

    return _httpManager;
}

#pragma mark - Http Request Failure
- (SDFontDownloaderClientError *)httpRequestFailure:(NSHTTPURLResponse *)response
                            error:(NSError *)error {
    
    SDFontDownloaderClientError *e = [[SDFontDownloaderClientError alloc] init];
    if(error.code == NSURLErrorNotConnectedToInternet || error.code == NSURLErrorCannotFindHost || error.code == NSURLErrorCannotConnectToHost){
        e.message = @"网络连接失败!";
        return e;
    }
    
    if (error.code == NSURLErrorTimedOut){
        e.message = @"网路连接超时!";
        return e;
    }
    
    NSInteger statusCode = response.statusCode;
    if (statusCode == 401) {
        e.message = @"认证失败";
    } else if (statusCode == 400){
        e.message = @"无效请求";
    } else if (statusCode == 404) {
        e.message = @"访问的资源丢失了!";
    } else if (statusCode >= 500){
        e.message = @"服务器居然累倒了!";
    }
    
    #ifdef DEBUG
        @try {
            // 这里只是测试用
            //第一步、首先从error根据NSErrorFailingURLKey拿到value
            NSError *errorFail = [error.userInfo objectForKey:@"NSUnderlyingError"];
            //第二步、通过errorFail根据com.alamofire.serialization.response.error.data拿到value
            NSData *data = nil;
            if (errorFail) {
                data = [errorFail.userInfo objectForKey:@"com.alamofire.serialization.response.error.data"];
            } else {
                data = [error.userInfo objectForKey:@"com.alamofire.serialization.response.error.data"];
            }
            NSLog(@"data:%@",data);
            //第三部、将NSData转成NSString,因为NSString字符串比较直观
            if (data) {
                NSString *errorString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSLog(@"errorString:%@",errorString);
            }
            
        } @catch (NSException *exception) {
            
        } @finally {
            
        }
                
    #else
    #endif
    
    return e;
}


#pragma mark - Http download
/**
 请求下载
 
 @param aUrl aurl
 @param aSavePath aSavePath
 @param aFileName aFileName
 @param aTag aTag
 @param downloadprogress downloadprogress
 @param success success
 @param failure failure
 */
- (void)downloadFileURL:(NSString *)aUrl
               savePath:(NSString *)aSavePath
               fileName:(NSString *)aFileName
                    tag:(NSInteger)aTag
       downloadProgress:(void(^)(CGFloat progress))downloadprogress
                success:(void(^)(NSURLResponse *response,NSString *filePath))success
                failure:(void(^)(SDFontDownloaderClientError * e))failure {
    
    NSFileManager *fileManger = [NSFileManager defaultManager];
    
    if ([fileManger fileExistsAtPath:[aSavePath stringByAppendingPathComponent:aFileName]]) {
        //文件存在
        return;
    }
    
    //2.确定请求的URL地址
    NSString *requestUrl = aUrl;

    NSMutableURLRequest *request = [self.httpManager.requestSerializer requestWithMethod:@"GET" URLString:requestUrl parameters:nil error:nil];
    
    __block NSURLSessionDownloadTask *downloadTask = nil;
    
    downloadTask = [self.httpManager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
        NSLog(@"progress current thread:%@", [NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            
            downloadprogress(1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount);
        });
        
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        NSLog(@"destination current thread:%@", [NSThread currentThread]);
        return [NSURL fileURLWithPath:aSavePath];
        
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        NSLog(@"completionHandler current thread:%@", [NSThread currentThread]);
        if(error == nil) {
            success(response,[filePath path]);
        } else {
            //下载失败
            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
            SDFontDownloaderClientError *e = [self httpRequestFailure:httpResponse error:error];
            failure(e);
        }
    }];
    
    [downloadTask resume];
}

@end

二、字体管理

字体文件在下载后,将注册到CTFontManager

//注册指定路径下的字体文件
- (void)registerFont:(NSString *)fontPath {
    //调整位置
    NSURL *fontUrl = [NSURL fileURLWithPath:fontPath];
    CGDataProviderRef providerRef = CGDataProviderCreateWithURL((__bridge CFURLRef)fontUrl);
    
    CFErrorRef error;
    CGFontRef font = CGFontCreateWithDataProvider(providerRef);
    if(!font){
        CGDataProviderRelease(providerRef);
        CGFontRelease(font);
        return;
    }

    BOOL ctfmrgf = CTFontManagerRegisterGraphicsFont(font, &error);
    if (!ctfmrgf) {
        //注册失败
        CFStringRef errorDescription = CFErrorCopyDescription(error);
        CFRelease(errorDescription);
        if (error) {
            CFRelease(error);
        }
    }

    CGFontRelease(font);
    CFRelease(providerRef);
}

字体管理完整代码如下

#import "SDFontManager.h"

@implementation SDFontLoadError

@end


static SDFontManager *manager = nil;

@interface SDFontManager ()

@end

@implementation SDFontManager

+ (instancetype)shareInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[SDFontManager alloc] init];
    });
    return manager;
}

- (void)downloadAppleFontName:(NSString *)fontName
                     fontSize:(CGFloat)fontSize
               beginLoadBlock:(SDFontBeginLoadBlock)beginLoadBlock
                progressBlock:(SDFontLoadProgressBlock)progressBlock
              completionBlock:(SDFontLoadCompletionBlock)completionBlock {
    
    self.beginLoadBlock = beginLoadBlock;
    self.progressBlock = progressBlock;
    self.completionBlock = completionBlock;
    
    UIFont* aFont = [UIFont fontWithName:fontName size:fontSize];
    // If the font is already downloaded
    if (aFont && ([aFont.fontName compare:fontName] == NSOrderedSame || [aFont.familyName compare:fontName] == NSOrderedSame)) {
        // Go ahead and display the sample text.
        if (self.beginLoadBlock) {
            self.beginLoadBlock();
        }
        
        if (self.progressBlock) {
            self.progressBlock(1.0);
        }
        
        if (self.completionBlock) {
            self.completionBlock(aFont, nil);
        }
        return;
    }
    
    // Create a dictionary with the font's PostScript name.
    NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithObjectsAndKeys:fontName, kCTFontNameAttribute, nil];
    
    // Create a new font descriptor reference from the attributes dictionary.
    CTFontDescriptorRef desc = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)attrs);
    
    NSMutableArray *descs = [NSMutableArray arrayWithCapacity:0];
    [descs addObject:(__bridge id)desc];
    CFRelease(desc);
    
    __block BOOL errorDuringDownload = NO;
    
    // Start processing the font descriptor..
    // This function returns immediately, but can potentially take long time to process.
    // The progress is notified via the callback block of CTFontDescriptorProgressHandler type.
    // See CTFontDescriptor.h for the list of progress states and keys for progressParameter dictionary.
    CTFontDescriptorMatchFontDescriptorsWithProgressHandler( (__bridge CFArrayRef)descs, NULL,  ^(CTFontDescriptorMatchingState state, CFDictionaryRef progressParameter) {
        
        //NSLog( @"state %d - %@", state, progressParameter);
        
        double progressValue = [[(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingPercentage] doubleValue];
        
        if (state == kCTFontDescriptorMatchingDidBegin) {
            dispatch_async( dispatch_get_main_queue(), ^ {
                // Show an activity indicator
                // 开始下载字体,显示加载进度
                if (self.beginLoadBlock) {
                    self.beginLoadBlock();
                }
                NSLog(@"Begin Matching");
            });
        } else if (state == kCTFontDescriptorMatchingDidFinish) {
            dispatch_async( dispatch_get_main_queue(), ^ {
                // Remove the activity indicator
                if (!errorDuringDownload) {
                    NSLog(@"%@ downloaded", fontName);
                }
                
                if(self.progressBlock){
                    self.progressBlock(1.0f);
                }
                
                // 完成下载字体
                if (self.completionBlock) {
                    if ([self isAvaliableFont:fontName fontSize:fontSize]) {
                        [self saveAppleFontPathWithFontName:fontName];
                        UIFont *aFont = [UIFont fontWithName:fontName size:fontSize];
                        self.completionBlock(aFont, nil);
                    } else {
                        NSLog(@"font %@ is Unavaliable", fontName);
                    }
                }
            });
        } else if (state == kCTFontDescriptorMatchingWillBeginDownloading) {
            dispatch_async( dispatch_get_main_queue(), ^ {
                // Show a progress bar
                if(self.progressBlock){
                    self.progressBlock(0.0f);
                }
                NSLog(@"Begin Downloading");
            });
        } else if (state == kCTFontDescriptorMatchingDidFinishDownloading) {
            dispatch_async( dispatch_get_main_queue(), ^ {
                // Remove the progress bar
                if(self.progressBlock){
                    self.progressBlock(1.0f);
                }
                NSLog(@"Finish downloading");
            });
        } else if (state == kCTFontDescriptorMatchingDownloading) {
            dispatch_async( dispatch_get_main_queue(), ^ {
                // Use the progress bar to indicate the progress of the downloading
                if(self.progressBlock){
                    self.progressBlock(progressValue / 100.0);
                }
                NSLog(@"Downloading %.0f%% complete", progressValue);
            });
        } else if (state == kCTFontDescriptorMatchingDidFailWithError) {
            // An error has occurred.
            // Get the error message
            NSError *error = [(__bridge NSDictionary *)progressParameter objectForKey:(id)kCTFontDescriptorMatchingError];
            NSString *errorMessage = nil;
            if (error != nil) {
                errorMessage = [error description];
            } else {
                errorMessage = @"ERROR MESSAGE IS NOT AVAILABLE!";
            }
            // Set our flag
            errorDuringDownload = YES;
            
            dispatch_async( dispatch_get_main_queue(), ^ {
                if (self.completionBlock) {
                    SDFontLoadError *error = [[SDFontLoadError alloc] init];
                    error.errorMessage = errorMessage;
                    self.completionBlock(nil, error);
                }
                NSLog(@"Download error: %@", errorMessage);
            });
        }
        
        return (bool)YES;
    });
}

- (void)downloadCustomFontName:(NSString *)fontName
               fontDownloadUrl:(NSString *)fontDownloadUrl
                      fontSize:(CGFloat)fontSize
                beginLoadBlock:(SDFontBeginLoadBlock)beginLoadBlock
                 progressBlock:(SDFontLoadProgressBlock)progressBlock
               completionBlock:(SDFontLoadCompletionBlock)completionBlock {
    
    self.beginLoadBlock = beginLoadBlock;
    self.progressBlock = progressBlock;
    self.completionBlock = completionBlock;
    
    UIFont* aFont = [UIFont fontWithName:fontName size:fontSize];
    // If the font is already downloaded
    if (aFont && ([aFont.fontName compare:fontName] == NSOrderedSame || [aFont.familyName compare:fontName] == NSOrderedSame)) {
        // Go ahead and display the sample text.
        if (self.beginLoadBlock) {
            self.beginLoadBlock();
        }
        
        if (self.progressBlock) {
            self.progressBlock(1.0);
        }
        
        if (self.completionBlock) {
            self.completionBlock(aFont, nil);
        }
        return;
    }
    
    //如果不存在,重新下载解压
    NSString *savefontDirectoryPath = [self fontDirectoryPath];

    //下载成功
    NSString *afileName = [[NSURL URLWithString:fontDownloadUrl] lastPathComponent];
    NSString *afilePath = [NSString pathWithComponents:@[savefontDirectoryPath, afileName]];
    
    //下载成功
    NSString *aFontFileName = [[NSURL URLWithString:fontDownloadUrl] lastPathComponent];

    BOOL exsit = [self exsitCustomFontFileWithFontName:aFontFileName];
    if (exsit) {  //如果已经下载过了
        // 检查字体是否可用
        [self registerFont:afilePath];
        UIFont *font = [self fontWithPath:afilePath fontSize:fontSize];

        //更新UI
        if (self.progressBlock) {
            self.progressBlock(1.0);
        }
        
        if (self.completionBlock) {
            self.completionBlock(font, nil);
        }
        
        return;
    }
    
    __weak typeof(self) weakSelf = self;
    [[SDFontDownloaderClient sharedInstance] downloadFileURL:fontDownloadUrl savePath:afilePath fileName:afilePath tag:[afilePath hash] downloadProgress:^(CGFloat progress) {
        if (self.progressBlock) {
            self.progressBlock(progress);
        }
    } success:^(NSURLResponse *response, NSString *filePath) {
        NSLog(@"filePath:%@",filePath);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSString *fontPath = filePath;
            [weakSelf registerFont:fontPath];  //注册字体文件
            UIFont *font = [weakSelf fontWithPath:fontPath fontSize:fontSize];
            if (weakSelf.completionBlock) {
                weakSelf.completionBlock(font, nil);
            }
        });
    } failure:^(SDFontDownloaderClientError *e) {
        dispatch_async(dispatch_get_main_queue(), ^{
            SDFontLoadError *error = [[SDFontLoadError alloc] init];
            error.errorMessage = e.message;
            if (weakSelf.completionBlock) {
                weakSelf.completionBlock(nil, error);
            }
        });
        
    }];
}

//注册苹果字体并保存路径(这里苹果的字体不在沙盒目录,无法注册)
- (void)saveAppleFontPathWithFontName:(NSString *)fontName{

    CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)fontName, 0., NULL);
    CFURLRef fontURL = CTFontCopyAttribute(fontRef, kCTFontURLAttribute);
    NSURL *fontPathURL = (__bridge NSURL*)(fontURL);

    //把苹果的字体路径保存起来
    [self registerFont:fontPathURL.path];  //注册字体

    CFRelease(fontURL);
    CFRelease(fontRef);
}

//注册指定路径下的字体文件
- (void)registerFont:(NSString *)fontPath {
    //调整位置
    NSURL *fontUrl = [NSURL fileURLWithPath:fontPath];
    CGDataProviderRef providerRef = CGDataProviderCreateWithURL((__bridge CFURLRef)fontUrl);
    
    CFErrorRef error;
    CGFontRef font = CGFontCreateWithDataProvider(providerRef);
    if(!font){
        CGDataProviderRelease(providerRef);
        CGFontRelease(font);
        return;
    }

    BOOL ctfmrgf = CTFontManagerRegisterGraphicsFont(font, &error);
    if (!ctfmrgf) {
        //注册失败
        CFStringRef errorDescription = CFErrorCopyDescription(error);
        CFRelease(errorDescription);
        if (error) {
            CFRelease(error);
        }
    }

    CGFontRelease(font);
    CFRelease(providerRef);
}

- (UIFont *)fontWithPath:(NSString *)fontPath fontSize:(CGFloat)fontSize {
    NSURL *fontUrl = [NSURL fileURLWithPath:fontPath];
    CGDataProviderRef providerRef = CGDataProviderCreateWithURL((__bridge CFURLRef)fontUrl);
    CGFontRef font = CGFontCreateWithDataProvider(providerRef);
    if(!font){
        CGDataProviderRelease(providerRef);
        CGFontRelease(font);
        return nil;
    }
    CGDataProviderRelease(providerRef);
    CTFontManagerUnregisterGraphicsFont(font,nil);
    CTFontManagerRegisterGraphicsFont(font, NULL);
    
    NSString *newFamilyName = CFBridgingRelease(CGFontCopyPostScriptName(font));
    UIFont *uifont = [UIFont fontWithDescriptor:[UIFontDescriptor fontDescriptorWithName:newFamilyName size:fontSize] size:fontSize];

    CGFontRelease(font);
    return uifont;
}

//判断字体是否可用
- (BOOL)isAvaliableFont:(NSString *)fontName fontSize:(CGFloat)fontSize {
    UIFont *aFont = [UIFont fontWithName:fontName size:fontSize];
    return aFont && ([aFont.fontName compare:fontName] == NSOrderedSame || [aFont.familyName compare:fontName] == NSOrderedSame);
}

//是否存在fontFileName
- (BOOL)exsitCustomFontFileWithFontName:(NSString *)fontName {
    NSString *fontPath = [[self fontDirectoryPath] stringByAppendingPathComponent:fontName];
    BOOL exsit = [[NSFileManager defaultManager] fileExistsAtPath:fontPath];
    return exsit;
}

// 字体存储的路径path
- (NSString *)fontDirectoryPath {
    NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *fontDirectoryPath = [documentsDirectory stringByAppendingPathComponent:@"fonts"];

    [self createDirectoryIfNotExsitPath:fontDirectoryPath];   //创建目录

    return fontDirectoryPath;
}

//创建目录
- (BOOL)createDirectoryIfNotExsitPath:(NSString *)path {
    BOOL success = YES;
    if(![[NSFileManager defaultManager] fileExistsAtPath:path]){  //如果则创建文件夹
        NSError * error = nil;
        success = [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error];
        if (!success || error) {
            NSLog(@"Error! %@", error);
        } else {
            NSLog(@"Create fonts directory Success!");
        }
    }
    return success;
}

//依fontName构建font
- (UIFont *)fontWithFontName:(NSString *)fontName fontSize:(CGFloat)fontSize{
    if ([self isAvaliableFont:fontName fontSize:fontSize]) {
        return [UIFont fontWithName:fontName size:fontSize];
    }
    return nil;
}

@end

三、使用下载注册后的字体

我这里使用下载注册后的字体

- (void)lookButtonAction {
    [[SDFontManager shareInstance] downloadCustomFontName:@"猫啃网糖圆体" fontDownloadUrl:@"https://j1-common-bucket.s3.cn-northwest-1.amazonaws.com.cn/as/2020/12/16/oLmJQK1608104260841.ttf" fontSize:20 beginLoadBlock:^{
        NSLog(@"beginLoadBlock");
    } progressBlock:^(CGFloat progress) {
        NSLog(@"progressBlock:%f", progress);
    } completionBlock:^(UIFont *font, SDFontLoadError *error) {
        NSLog(@"completionBlock font:%@, error:%@", font, error);
        if (font && !error) {
            self.titleLabel.font = font;
        }
    }];
}

#pragma mark - lazy
- (UILabel *)titleLabel {
    if (!_titleLabel) {
        _titleLabel  = [[UILabel alloc] initWithFrame:CGRectZero];
        _titleLabel.backgroundColor = [UIColor clearColor];
        _titleLabel.textColor = [UIColor blackColor];
        _titleLabel.font = [UIFont systemFontOfSize:11];
        _titleLabel.textAlignment = NSTextAlignmentCenter;
    }
    return _titleLabel;
}

四、小结

iOS开发-使用网络特殊字体下载CGFontRef

在开发中遇到需要将字体下载后再显示的情况,这种特殊字体下载后才能正常。。

学习记录,每天不停进步。

你可能感兴趣的:(移动开发,iphone开发,Objective-c,ios,cocoa,macos,自定义字体,字体下载,CFFont)