iOS Development – Download CoreText Using Network Special Fonts
During the development, when the font needs to be downloaded and then displayed, this special font can only be normal after downloading.
1. Font Downloader
Add in AFNetworking
pod 'Reachability'
The font downloader uses AFNetworking to download font files
code show as below
#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( & amp;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 = @"Network connection failed!"; return e; } if (error.code == NSURLErrorTimedOut){<!-- --> e.message = @"Network connection timed out!"; return e; } NSInteger statusCode = response. statusCode; if (statusCode == 401) {<!-- --> e.message = @"Authentication failed"; } else if (statusCode == 400){<!-- --> e.message = @"Invalid request"; } else if (statusCode == 404) {<!-- --> e.message = @"The accessed resource is lost!"; } else if (statusCode >= 500){<!-- --> e.message = @"The server is exhausted!"; } #ifdef DEBUG @try {<!-- --> // here is just for testing //The first step, first get the value from the error according to NSErrorFailingURLKey NSError *errorFail = [error. userInfo objectForKey:@"NSUnderlyingError"]; //The second step, get the value according to com.alamofire.serialization.response.error.data through errorFail 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); //The third part, convert NSData to NSString, because NSString string is more intuitive 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 /** request 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]]) {<!-- --> // file exists return; } //2. Determine the URL address of the request 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 {<!-- --> //download failed NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; SDFontDownloaderClientError *e = [self httpRequestFailure:httpResponse error:error]; failure(e); } }]; [downloadTask resume]; } @end
2. Font management
After the font file is downloaded, it will be registered to CTFontManager
//Register the font file under the specified path - (void)registerFont:(NSString *)fontPath {<!-- --> //Adjust position 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) {<!-- --> //registration failed CFStringRef errorDescription = CFErrorCopyDescription(error); CFRelease(errorDescription); if (error) {<!-- --> CFRelease(error); } } CGFontRelease(font); CFRelease(providerRef); }
The complete code for font management is as follows
#import "SDFontManager.h" @implementation SDFontLoadError @end static SDFontManager *manager = nil; @interface SDFontManager() @end @implementation SDFontManager + (instancetype)shareInstance { static dispatch_once_t onceToken; dispatch_once( & amp;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 & amp; & amp; ([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 // Start downloading fonts and display the loading progress 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); } // Finish downloading fonts 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 & amp; & amp; ([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; } //If it does not exist, re-download and decompress NSString *savefontDirectoryPath = [self fontDirectoryPath]; //download successful NSString *afileName = [[NSURL URLWithString:fontDownloadUrl] lastPathComponent]; NSString *afilePath = [NSString pathWithComponents:@[savefontDirectoryPath, afileName]]; //download successful NSString *aFontFileName = [[NSURL URLWithString:fontDownloadUrl] lastPathComponent]; BOOL exsit = [self exsitCustomFontFileWithFontName:aFontFileName]; if (exsit) { //if already downloaded // check if the font is available [self registerFont:afilePath]; UIFont *font = [self fontWithPath:afilePath fontSize:fontSize]; //Update the 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]; //register font file 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); } }); }]; } //Register Apple fonts and save the path (here Apple's fonts are not in the sandbox directory and cannot be registered) - (void)saveAppleFontPathWithFontName:(NSString *)fontName{ CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)fontName, 0., NULL); CFURLRef fontURL = CTFontCopyAttribute(fontRef, kCTFontURLAttribute); NSURL *fontPathURL = (__bridge NSURL*)(fontURL); //Save Apple's font path [self registerFont:fontPathURL.path]; //register font CFRelease(fontURL); CFRelease(fontRef); } //Register the font file under the specified path - (void)registerFont:(NSString *)fontPath {<!-- --> //Adjust position 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) {<!-- --> //registration failed 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; } / / Determine whether the font is available - (BOOL)isAvaliableFont:(NSString *)fontName fontSize:(CGFloat)fontSize { UIFont *aFont = [UIFont fontWithName:fontName size:fontSize]; return aFont & amp; & amp; ([aFont.fontName compare:fontName] == NSOrderedSame || [aFont.familyName compare:fontName] == NSOrderedSame); } //Whether fontFileName exists - (BOOL)exsitCustomFontFileWithFontName:(NSString *)fontName { NSString *fontPath = [[self fontDirectoryPath] stringByAppendingPathComponent:fontName]; BOOL exit = [[NSFileManager defaultManager] fileExistsAtPath:fontPath]; return exit; } // The path path where the font is stored - (NSString *)fontDirectoryPath { NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0]; NSString *fontDirectoryPath = [documentsDirectory stringByAppendingPathComponent:@"fonts"]; [self createDirectoryIfNotExsitPath:fontDirectoryPath]; //create directory return fontDirectoryPath; } //Create a directory - (BOOL)createDirectoryIfNotExsitPath:(NSString *)path { BOOL success = YES; if(![[NSFileManager defaultManager] fileExistsAtPath:path]){ //if create folder 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; } //Construct font according to fontName - (UIFont *)fontWithFontName:(NSString *)fontName fontSize:(CGFloat)fontSize{ if ([self isAvaliableFont:fontName fontSize:fontSize]) { return [UIFont fontWithName:fontName size:fontSize]; } return nil; } @end
3. Use downloaded and registered fonts
I use downloaded and registered fonts here
- (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 & amp; & amp; !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; }
4. Summary
iOS development – download CGFontRef using network special fonts
During the development, when the font needs to be downloaded and then displayed, this special font can only be normal after downloading. .
Learning records, keep improving every day.