I've seen this question asked and answered numerous times, but I haven't seen a real answer.
The common "solutions" are:
Add the font to the application bundle and register it in the info.plist file.
Use a custom font parsing and rendering library (like the Zynga's FontLabel).
It cannot be done.
So the question is: How to dynamically load a font under iOS?
Loading the font "dynamically" means loading any given font which is not known at the time of the app's compilation.
Fonts can easily be dynamically loaded from any location or any byte stream. See the article here: http://www.marco.org/2012/12/21/ios-dynamic-font-loading
NSData *inData = /* your font-file data */;
CFErrorRef error;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
if (! CTFontManagerRegisterGraphicsFont(font, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error)
NSLog(#"Failed to load font: %#", errorDescription);
CFRelease(errorDescription);
}
CFRelease(font);
CFRelease(provider);
You don't have to put the font in your bundle.
You don't have to explicitly register the font in your info.plist.
See also:
https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_FontManager_Ref/Reference/reference.html#//apple_ref/doc/uid/TP40008278
https://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CGFont/Reference/reference.html#//apple_ref/c/func/CGFontCreateWithDataProvider
Great time for a recent post from Marco titled Loading iOS fonts dynamically.
NSData *inData = /* your decrypted font-file data */;
CFErrorRef error;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
if (! CTFontManagerRegisterGraphicsFont(font, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error)
NSLog(#"Failed to load font: %#", errorDescription);
CFRelease(errorDescription);
}
CFRelease(font);
CFRelease(provider);
// Note : add "CoreText.framework" into your project to support following code
// Put loadCustomFont function inside app delegate or any shared class to access any where in code...
-(void)loadCustomFont:(NSMutableArray *)customFontFilePaths{
for(NSString *fontFilePath in customFontFilePaths){
if([[NSFileManager defaultManager] fileExistsAtPath:fontFilePath]){
NSData *inData = [NSData dataWithContentsOfFile:fontFilePath];
CFErrorRef error;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
// NSString *fontName = (__bridge NSString *)CGFontCopyFullName(font);
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
NSLog(#"Failed to load font: %#", errorDescription);
CFRelease(errorDescription);
}
CFRelease(font);
CFRelease(provider);
}
}
}
// Use as follow inside your view controller...
- (void)viewDidLoad
{
[super viewDidLoad];
// pass all font files name into array which you want to load dynamically...
NSMutableArray *customFontsPath = [[NSMutableArray alloc] init];
NSArray *fontFileNameArray = [NSArray arrayWithObjects:#"elbow_v001.ttf",#"GothamRnd-MedItal.otf", nil];
for(NSString *fontFileName in fontFileNameArray){
NSString *fileName = [fontFileName stringByDeletingPathExtension];
NSString *fileExtension = [fontFileName pathExtension];
[customFontsPath addObject:[[NSBundle mainBundle] pathForResource:fileName ofType:fileExtension]];
}
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// load custom font into memory...
[appDel loadCustomFont:customFontsPath];
// Use font as below
[lblName setFont:[UIFont fontWithName:#"Elbow v100" size:15.0]];
[lblName2 setFont:[UIFont fontWithName:#"Gotham Rounded" size:20.0]];
}
Downloading a TTF file from server?
IF you are downloading a TTF file then you can do following to register your custom fonts with iOS Font Manager, this piece of code also takes care of TTF file updates (font updates):
+(void)registerFontsAtPath:(NSString *)ttfFilePath
{
NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:ttfFilePath] == YES)
{
[UIFont familyNames];//This is here for a bug where font registration API hangs for forever.
//In case of TTF file update : Fonts are already registered, first de-register them from Font Manager
CFErrorRef cfDe_RegisterError;
bool fontsDeregistered = CTFontManagerUnregisterFontsForURL((__bridge CFURLRef)[NSURL fileURLWithPath:ttfFilePath], kCTFontManagerScopeNone, &cfDe_RegisterError);
//finally register the fonts with Font Manager,
CFErrorRef cfRegisterError;
bool fontsRegistered= CTFontManagerRegisterFontsForURL((__bridge CFURLRef)[NSURL fileURLWithPath:ttfFilePath], kCTFontManagerScopeNone, &cfRegisterError);
}
Here is an updated answer of #mt81 for Swift 3:
guard
let path = "Path to some font file",
let fontFile = NSData(contentsOfFile: path)
else {
print "Font file not found?"
}
guard let provider = CGDataProvider(data: fontFile)
else {
print "Failed to create DataProvider"
}
let font = CGFont(provider)
let error: UnsafeMutablePointer<Unmanaged<CFError>?>? = nil
guard CTFontManagerRegisterGraphicsFont(font, error) else {
guard
let unError = error?.pointee?.takeUnretainedValue(),
let description = CFErrorCopyDescription(unError)
else {
print "Unknown error"
}
print description
}
Here the swift version:
let inData: NSData = /* your font-file data */;
let error: UnsafeMutablePointer<Unmanaged<CFError>?> = nil
let provider = CGDataProviderCreateWithCFData(inData)
if let font = CGFontCreateWithDataProvider(provider) {
if (!CTFontManagerRegisterGraphicsFont(font, error)) {
if let unmanagedError = error.memory {
let errorDescription = CFErrorCopyDescription(unmanagedError.takeUnretainedValue())
NSLog("Failed to load font: \(errorDescription)");
}
}
}
Related
I am trying to add a feature to an existing iOS app so that documents "copied" within Mail (say a PDF) can be pasted into my app.
After a PDF document is viewed in Mail and "Copy" chosen from the Action menu, within my app's code, when I inspect:
[[UIPasteboard generalPasteboard] items]
I can see the document as per:
{
"com.adobe.pdf" = {length = 875113, bytes = 0x25504446 2d312e35 0d0a25b5 b5b5b50d ... 300d0a25 25454f46 };
}
)
And further retrieve it via:
po [[UIPasteboard generalPasteboard] valueForPasteboardType:#"com.adobe.pdf"]
<OS_dispatch_data: data[0x2820e5c00] = { leaf, size = 875113, buf = 0x1125e0000 }>
However, is there any way to get the document's name? In the Files.app when I paste the same document it gets pasted as the original file name.
Should I be using the pasteboard for this, or if there another API which can get access to the copied document with the file name?
Even if this question is a little older I happened to search for the same and finally found the solution. So for future reference:
Starting with iOS 11 UIPasteboard contains an array itemProviders which gives you all the information about the objects that are available. The same is used for Drag/Drop.
So with the following logic you can save the items to a certain path:
NSArray *items = [[UIPasteboard generalPasteboard] itemProviders];
for (NSInteger i = 0; i < itemProvider.count; i++) {
NSItemProvider *itemProvider = itemProvider[i];
NSString *identifier = itemProvider.registeredTypeIdentifiers[0];
[itemProvider loadDataRepresentationForTypeIdentifier:identifier completionHandler:^(NSData * _Nullable data, NSError * _Nullable error) {
NSMutableDictionary *result;
if (!error) {
NSString *ext = (__bridge NSString *)(UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(identifier), kUTTagClassFilenameExtension));
if ([ext isEqualToString:#"jpeg"]) {
ext = #"jpg";
}
NSString *mime = (__bridge NSString *)(UTTypeCopyPreferredTagWithClass((__bridge CFStringRef _Nonnull)(identifier), kUTTagClassMIMEType));
if (!mime) {
mime = #"application/*";
}
NSString *filename = itemProvider.suggestedName;
NSString *path = [folderPath stringByAppendingPathComponent:filename];
[data writeToFile:path options:NSDataWritingAtomic error:&error];
}
}
}
I have a custom built font that I've added to the Xcode project. I've followed all the instructed steps in adding the font and when I set a UILabel or UITextField as the custom font in IB it works great, however when I set the custom font at runtime it appears as the iOS default font instead. I've checked the UIAppFonts list and have them print out with NSLog where it shows the full name so I know that when I call [UIFont fontWithName:#"font name" size:s] it's the correct name. What could be the issue?
The font name finding method below (This method provided the incorrect names to use in custom UIFont instantiation):
NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary];
NSArray* fontFiles = [infoDict objectForKey:#"UIAppFonts"];
for (NSString *fontFile in fontFiles) {
NSURL *url = [[NSBundle mainBundle] URLForResource:fontFile withExtension:NULL];
NSData *fontData = [NSData dataWithContentsOfURL:url];
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)fontData);
CGFontRef loadedFont = CGFontCreateWithDataProvider(fontDataProvider);
NSString *fullName = CFBridgingRelease(CGFontCopyFullName(loadedFont));
CGFontRelease(loadedFont);
CGDataProviderRelease(fontDataProvider);
NSLog(#"FULL NAME:%#",fullName);
}
Try executing this code:
for (NSString* family in [UIFont familyNames]) {
NSLog(#"%#", family);
for (NSString* name in [UIFont fontNamesForFamilyName: family]) {
NSLog(#" %#", name);
}
}
Do you see the font name you are trying to use?
Below is the code I have written to change the name of any file stored in an iOS device "Documents" directory.
When renaming the file using this code, the prefix resolves to...
"file:/localhost/private/var/mobile/Applications/.../Documents/"
instead of the correctly formatted...
"file://localhost/private/var/mobile/Applications/.../Documents/"
I am losing a slash, and so the rename method fails!!!
I have rewritten this for both NSString and NSURL formats, and both methods present the identical error. I have tested this using the iOS Simulator and on device, and both tests present the identical error.
I suspect I am overlooking something simple, but cannot figure what it is - any help please?
- (void)alterFileNameOrExtension {
NSError *errorMove = nil;
NSError __autoreleasing *error = nil;
//note below - entity attribute document.dStringURL has a data type NSString
NSString *file = [document.dStringURL lastPathComponent];
NSString *fileName = [file stringByDeletingPathExtension];
NSString *fileExtension = [file pathExtension];
NSString *fileNameNew = nil;
//note below - self.tempFileComponent, self.tempFileName & self.tempFileExtension
//note below - are set in the (IBAction) method textFieldDidEndEditing:
//note below - self.tempFileComponent determines whether the user is attempting to change the file name or the file extension
if (self.tempFileComponent == 1) {
fileNameNew = [[self.tempFileName stringByAppendingPathExtension:fileExtension] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
} else if (self.tempFileComponent == 2) {
fileNameNew = [fileName stringByAppendingPathExtension:self.tempFileExtension];
} else {
NSLog(#"%# - %# - attempt to determine tempFileComponent has (possible undefined) E~R~R~O~R: %#_", self.className, NSStringFromSelector(_cmd), error.localizedDescription);
}
NSString *filePath = [document.dStringURL stringByDeletingLastPathComponent];
NSString *filePathNew = [filePath stringByAppendingPathComponent:fileNameNew];
BOOL move = [[NSFileManager defaultManager] moveItemAtPath:document.dStringURL toPath:filePathNew error:&errorMove];
if (!move) {
//handle error
} else {
//complete process
}
}
#
With assistance from #Mario, I have adjusted my code and include the working version below for the benefit of others...
#
NSError *errorMove = nil;
NSError __autoreleasing *error = nil;
NSString *file = [document.dStringURL lastPathComponent];
NSString *fileName = [file stringByDeletingPathExtension];
NSString *fileExtension = [file pathExtension];
NSString *fileNameNew = nil;
if (self.tempFileComponent == 1) {
fileNameNew = [self.tempFileName stringByAppendingPathExtension:fileExtension];
} else if (self.tempFileComponent == 2) {
fileNameNew = [fileName stringByAppendingPathExtension:self.tempFileExtension];
} else {
NSLog(#"%# - %# - attempt to determine tempFileComponent has (possible undefined) E~R~R~O~R: %#_", self.className, NSStringFromSelector(_cmd), error.localizedDescription);
}
NSURL *fileURLOld = [NSURL URLWithString:document.dStringURL];
NSURL *fileURLPrefix = [fileURLOld URLByDeletingLastPathComponent];
NSURL *fileURLNew = [fileURLPrefix URLByAppendingPathComponent:fileNameNew];
BOOL move = [[NSFileManager defaultManager] moveItemAtURL:fileURLOld toURL:fileURLNew error:&errorMove];
if (!move) {
//handle error
} else {
//complete process
}
The NSString path manipulation methods like stringByAppendingPathComponent expects file paths not URLs. It will remove double slashes as this is not a valid file path (although it is a valid URL).
does anyone know how to get a list of custom fonts from the 'Fonts provided by application' key in the info.plist file in Xcode?
Thanks
The following code reads the list of custom font files from the Info.plist,
and extracts the full font name from the font file.
(Parts of the code is copied from https://stackoverflow.com/a/17519740/1187415
with small modifications and ARC adjustments).
Objective-C
NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary];
NSArray* fontFiles = [infoDict objectForKey:#"UIAppFonts"];
for (NSString *fontFile in fontFiles) {
NSLog(#"file name: %#", fontFile);
NSURL *url = [[NSBundle mainBundle] URLForResource:fontFile withExtension:NULL];
NSData *fontData = [NSData dataWithContentsOfURL:url];
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)fontData);
CGFontRef loadedFont = CGFontCreateWithDataProvider(fontDataProvider);
NSString *fullName = CFBridgingRelease(CGFontCopyFullName(loadedFont));
CGFontRelease(loadedFont);
CGDataProviderRelease(fontDataProvider);
NSLog(#"font name: %#", fullName);
}
Swift 3 equivalent:
if let infoDict = Bundle.main.infoDictionary,
let fontFiles = infoDict["UIAppFonts"] as? [String] {
for fontFile in fontFiles {
print("file name", fontFile)
if let url = Bundle.main.url(forResource: fontFile, withExtension: nil),
let fontData = NSData(contentsOf: url),
let fontDataProvider = CGDataProvider(data: fontData) {
let loadedFont = CGFont(fontDataProvider)
if let fullName = loadedFont.fullName {
print("font name", fullName)
}
}
}
}
You can add your costume font http://www.danielhanly.com/blog/tutorial/including-custom-fonts-in-ios/
But, I don't, know how to get this list, sorry.
But, may be you can look on it in IB, in UILabel attributes.
I'm using this piece of code to try to retrieve the last modified date of a file:
NSError *error = nil;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath: myFilePath error:&error];
if (attributes != nil) {
NSDate *date = (NSDate*)[attributes objectForKey: NSFileModificationDate];
NSLog(#"Date modiifed: %#", [date description]);
}
else {
NSLog(#"Not found");
}
This works well for files in the main bundle but not if the file is located in a subdirectory of the app's document folder, with myFilePath like this:
/Users/User/Library/Application Support/iPhone Simulator/6.0/Applications/The App ID Number/Documents/mySubdirectory/My Saved File
It keeps returning "not found".
I know the file is there, as I can view it with finder. I also tried removing the spaces in the file name but this had no effect.
The error log says no such file or directory, so it looks like something must've gone wrong when I tried to copy the file to the document directory.
Weird thing is, iterating through the document sub directory with contentsOfDirectoryAtPath shows the file as being present.
I've tried hard-coding the path and retrieving it programmatically, with:
*myFolder = [documentsDirectory stringByAppendingPathComponent:#"myFolder"];
*myFilePath = [myFolder stringByAppendingPathComponent:theFileName];
Can anyone see where I'm going wrong?
Swift 3 solution:
func fileModificationDate(url: URL) -> Date? {
do {
let attr = try FileManager.default.attributesOfItem(atPath: url.path)
return attr[FileAttributeKey.modificationDate] as? Date
} catch {
return nil
}
}
Try this. I had same problem and solved with something like next:
NSURL *fileUrl = [NSURL fileURLWithPath:myFilePath];
NSDate *fileDate;
[fileUrl getResourceValue:&fileDate forKey:NSURLContentModificationDateKey error:&error];
if (!error)
{
//here you should be able to read valid date from fileDate variable
}
hope it helped ;)
Here is a Swift like solution of #zvjerka24 answer:
func lastModified(path: String) -> NSDate? {
let fileUrl = NSURL(fileURLWithPath: path)
var modified: AnyObject?
do {
try fileUrl.getResourceValue(&modified, forKey: NSURLContentModificationDateKey)
return modified as? NSDate
} catch let error as NSError {
print("\(#function) Error: \(error)")
return nil
}
}
If you get the error:
"CFURLCopyResourcePropertyForKey failed because it was passed this URL which has no scheme"
You can try to solve this by appending "file:///" to your NSString file path before converting it to NSURL, it worked in my case.
Can also do:
NSURL* file = ...
NSError* error;`
NSDate *creationDate = [[NSFileManager defaultManager] attributesOfItemAtPath:file.path error:&error].fileCreationDate;
For any file in macOS system we can easily get modification date by using any of below mentioned options:
Way 1:
NSString *path = #"path to file";
NSError *err = nil;
NSDictionary *dic2 = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err];
NSLog(#"File modification Date:%#", dic2[NSFileModificationDate]);
Way 2:
MDItemRef itemRef = MDItemCreate(kCFAllocatorDefault, (__bridge CFStringRef)path);
NSArray *attributeNames = (__bridge NSArray *)MDItemCopyAttributeNames(itemRef);
NSDictionary *attributes = (__bridge NSDictionary *) MDItemCopyAttributes(itemRef, (__bridge CFArrayRef) attributeNames);
CFDateRef modifDate = MDItemCopyAttribute(itemRef, kMDItemContentModificationDate);
NSDate* modificationDate = (__bridge NSDate*) modifDate;
NSLog(#"Modification Date%#", modificationDate);
You can also print various other attributes provided by MDItem :
NSLog(#"All attributes%#", attributes);