Non-bundle external fonts for iOS Application - ios

How to use non-bundle custom fonts in my iOS Application? I want to add font to app via iCloud or from internet and use it inside.

Yes you can do this via
CTFontManagerRegisterGraphicsFont
(https://developer.apple.com/documentation/coretext/1499499-ctfontmanagerregistergraphicsfon)
You only need a reference to a file or data reference. The code should be self-explained:
guard let fontData = NSData(contentsOfFile: pathForResourceString) else {
fatalError("[UIFont] Could not get data of font")
}
guard let dataProvider = CGDataProvider(data: fontData) else {
fatalError("[UIFont] Could not get dataprovider of font")
}
guard let fontRef = CGFont(dataProvider) else {
fatalError("[UIFont] Could not create font")
}
var errorRef: Unmanaged<CFError>?
if CTFontManagerRegisterGraphicsFont(fontRef, &errorRef) == false {
fatalError("[UIFont] Could not register font")
}
Then you can access the font over its name, like it were in the bundle.

Related

Registered font not getting after reopen application

I am using Font from UIDocumentPickerViewController and I am successfully registered font and also get UIFont.families. But once I kill the application and then reopen application then I can not get that font in UIFont.families. I used bellow code
func handleFileSelection(inUrl:URL) -> Void {
do {
// inUrl is the document's URL
let fontData = try Data(contentsOf: inUrl) // Getting file data here
let dataProvider = CGDataProvider(data: fontData as CFData)
let cgFont = CGFont(dataProvider
var error: Unmanaged<CFError>?
if !CTFontManagerRegisterGraphicsFont(cgFont!, &error)
{
print("Error loading Font!")
} else {}
let fontName = cgFont!.postScriptName
self.lblText.font = UIFont.init(name: "\(fontName!)", size: 25.0)
self.lblText.text = "\(fontName!)"
} catch {
print("document loading error")
}
}
If I am missing something please let me know.

Loading images from external storage using Core Graphics not working iOS 13

I am attempting to load photos located on external storage (SD card) using core graphics in iOS 13 (beta). The code below works fine when the files are on the device. When the files are on external storage however it fails returning nil and I don't know why.
I believe I am using the correct security scoping.
I loaded the file URLs from a security scoped folder url as per Providing Access to Directories
guard folderUrl.startAccessingSecurityScopedResource() else {
return nil
}
defer { folderUrl.stopAccessingSecurityScopedResource() }
guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, options) else {
throw Error.failedToOpenImage(message: "Failed to open image at \(imageURL)")
}
So... for my own project, where I ran into the same issue, I now have the following function to give me a thumbnail, going from elegant and quick to brute force.
static func thumbnailForImage(at url: URL, completion: (Result<UIImage, Error>) -> Void) {
let shouldStopAccessing = url.startAccessingSecurityScopedResource()
defer { if shouldStopAccessing { url.stopAccessingSecurityScopedResource() } }
let coordinator = NSFileCoordinator()
var error: NSError?
coordinator.coordinate(readingItemAt: url, options: .withoutChanges, error: &error) { url in
var thumbnailImage: UIImage?
var storedError: NSError?
var imageSource: CGImageSource?
print("Strategy 1: Via URL resource key")
do {
let resourceKeys = Set([URLResourceKey.thumbnailDictionaryKey])
let resources = try url.resourceValues(forKeys: resourceKeys)
if let dict = resources.thumbnailDictionary, let resource = dict[URLThumbnailDictionaryItem.NSThumbnail1024x1024SizeKey] {
thumbnailImage = resource
} else {
throw "No thumbnail dictionary"
}
} catch let error {
storedError = error as NSError
}
let options = [kCGImageSourceCreateThumbnailFromImageIfAbsent: true, kCGImageSourceShouldAllowFloat: true, kCGImageSourceCreateThumbnailWithTransform: true]
if thumbnailImage == nil {
print("Strategy 2: Via CGImageSourceCreateWithURL")
imageSource = CGImageSourceCreateWithURL(url as CFURL, options as CFDictionary)
}
if thumbnailImage == nil && imageSource == nil {
print("Strategy 3: Via CGImageSourceCreateWithData")
let data = try? Data.init(contentsOf: url)
if let data = data {
imageSource = CGImageSourceCreateWithData(data as CFData, options as CFDictionary)
}
}
if let imageSource = imageSource, thumbnailImage == nil {
print("Attempting thumbnail creation from source created in strategy 2 or 3")
if let image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
thumbnailImage = UIImage(cgImage: image)
}
}
if let thumbnailImage = thumbnailImage {
print("Success")
completion(.success(thumbnailImage))
} else {
print("Failure")
if let error = storedError { completion(.failure(error)) }
else { completion(.failure("Everything just fails...")) }
}
}
if let error = error { completion(.failure(error)) }
}
Basically it works by trying to get a thumbnail via the URL resources first. This is the quickest and nicest way, of it works. If that fails, I try CGImageSourceCreateWithURL. That works most of the time, except on remote storage. I suspect that's still a bug and submitted a feedback ticket to apple for this. I suggest you do the same. Last attempt, just try to read the entire file using NSData and creating an image source via CGImageSourceCreateWithData...
So far, if it's an image file I, this seems to produce a thumbnail most of the time. It can be quite slow though, having to read the entire file.

iOS 11 Share Extensions Broken

Before iOS 11 came out I created a share extension to my social media app. It worked perfectly fine. Once iOS 11 came out, the share extension quit working. I searched and debugged the extension until I found the source of the problem. When looping through the attachments inside the extensionContext.inputItems[0].attachments, none of the attachments has an item conforming to kUTTypeImage. So none of my code was running from that point on. I also had another strange outcome yesterday. This is part of my code inside the didSelectPost function.
guard let content = extensionContext?.inputItems[0] as? NSExtensionItem else { return }
guard let contentAttachments = content.attachments as? [NSItemProvider] else { return }
let skyName = self.textView.text
for attachment in contentAttachments {
if attachment.hasItemConformingToTypeIdentifier(imageType) {
attachment.loadItem(forTypeIdentifier: imageType, options: nil) { (data, error) in
guard error == nil, let url = data as? NSURL else { return }
self.imageFromAsset(url: url as URL)
if !self.selectedType.isEmpty {
do {
let imageData = try Data(contentsOf: url as URL)
self.skyImage = UIImage(data: imageData)
self.saveSkyImage()
guard let skyOriginalImageURL = self.skyOriginalImageURL else { return }
guard let skyImageURL = self.skyImageURL else { return }
let newSky = Sky(name: skyName ?? "Another Sky",
type: self.selectedType,
date: self.date,
location: self.location,
picture: CKAsset(fileURL: skyImageURL),
likes: 0, flags: 0,
likedBy: [CKReference](), flaggedBy: [CKReference](),
originalImage: CKReference(record: CKRecord(recordType: "SkyImage"), action: .none))
let newSkyImage = SkyImageFullResolution(picture: CKAsset(fileURL: skyOriginalImageURL))
self.saveSky(sky: newSky, skyImage: newSkyImage)
} catch {
print(error.localizedDescription)
self.closePostWindow()
}
}
}
}
}
defer {
closePostWindow()
}
I don't have a direct answer to your problem but recently in iOS 11 I have resolved a problem to display a share extension involving PDF files.
My problem was that my expected type identifiers were not found among the attachments.
NSItemProvider has an instance property registeredTypeIdentifiers to show you the type identifiers that can be found when your extension is activated.
That's what I do :
1) I use TRUEPREDICATE as NSExtensionActivationRule to force display my share extension in the context of my interest.
2) After you select your share extension, your extension code will be triggered.
Then you print all the type registeredTypeIdentifiers of each attachment, by looping through your contentAttachments.
Once you have identified all the identifiers, you will be able to find a solution to your problem.

Unable to register font programmatically in Swift 3

I am trying to register font programmatically in swift 3. Here is the piece of code i am using to install fonts. The font i am trying to install is Oswald
func installFonts(_ fonts: [String]){
for i in 0 ..< fonts.count{
let font = fonts[i].components(separatedBy: ".")
let fontPath = Bundle.main.url(forResource: font[0], withExtension: font[1])!
var error: Unmanaged<CFError>?
if let dataProvider = CGDataProvider(url: fontPath as CFURL) {
let font = CGFont(dataProvider)
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
print(error.debugDescription)
}
}
}
}
None of the fonts get installed and error description states:
Optional(Swift.Unmanaged<__ObjC.CFError>(_value: Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=105 \"Could not register the CGFont \'<CGFont (0x6100000d9f30): Oswald-Bold>\'\" UserInfo={NSDescription=Could not register the CGFont \'<CGFont (0x6100000d9f30): Oswald-Bold>\', CTFailedCGFont=<CGFont (0x6100000d9f30): Oswald-Bold>}))
Error code 105 means that the font is already registered (ref: https://developer.apple.com/documentation/coretext/ctfontmanagererror/alreadyregistered)
It could be that your font isn't working. I tried something very similar and it worked fine.
Downloaded a TTF font: http://dl.dafont.com/dl/?f=comicate
Dragged into Xcode, added to the target
Ran the following code:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if (installFont("comicate")) {
print("Font installed")
}
}
#discardableResult
func installFont(_ font:String) -> Bool {
guard let fontUrl = Bundle.main.url(forResource: font, withExtension: "ttf") else {
return false
}
let fontData = try! Data(contentsOf: fontUrl)
if let provider = CGDataProvider.init(data: fontData as CFData) {
var error: Unmanaged<CFError>?
let font:CGFont = CGFont(provider)
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
print(error.debugDescription)
return false
} else {
return true
}
}
return false
}
}
I do in fact see "Font installed" printed.
I had the same error, and couldn't work out why the font was still appearing correctly in the app despite an error being reported.
Turns out iOS already includes the font I was trying to load! I assume the error was caused because there already existed a font of the same name.
Each of the following CoreText functions work. Something to note, is that CoreText supports more than just ttf format, it also supports ttc, which is also very useful.
import CoreText
//: Register with batch of font files
func registerFontResources(_ fonts: [String]) -> Bool {
let fontURLs: [URL] = fonts.flatMap {
Bundle.main.url(forResource: $0, withExtension: nil)
}
return CTFontManagerRegisterFontsForURLs(fontURLs as CFArray, .process, nil)
}
//: Register with single font file
func registerFontResource(_ font: String) -> Bool {
if let fontURL = Bundle.main.url(forResource: font, withExtension: nil) {
return CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, nil)
}
return false
}
Also the 3rd argument to those functions provide a way to receive errors. I did not demonstrate that here because if you only want success value, that's already the return.

Can I embed a custom font in a bundle and access it from an ios framework?

I'm creating an ios framework with its bundle for packaging ressources (nib, images, fonts) and I'm trying to embed a custom font in the bundle but I'm not able to load it from the framework, is it possible ?
1) I can localize the font file with this:
objc
NSString *fontPath = [[NSBundle frameworkBundle] pathForResource:#"MyCustomFont" ofType:#"ttf"];
2) But I can't get it in my fonts lists:
objc
NSArray * array = [UIFont familyNames];
I included my font name in the bundle's plist with a "Fonts provided by application", without success, tried also in the app info plist, include it in the framework ressource without success.
I can load the nib and images from the bundle (by prefixing with the bundle's name) but not for the font. Any thought ?
EDIT : I saw the following post : Can I embed a custom font in an iPhone application?, but the question is just "Can I embed a custom font in an iPhone application?" not "Can I embed a custom font in an external framework/bundle ?" It also makes references to a dynamic loading which is interesting but it is using private api, which is not usable solution for a framework.
Thanks
Swift 3:
Firstly, don't access framework bundle from main with appending path components... Instantiate it from its identifier. You can get font URLs like this:
static func fontsURLs() -> [URL] {
let bundle = Bundle(identifier: "com.company.project.framework")!
let fileNames = ["Roboto-Bold", "Roboto-Italic", "Roboto-Regular"]
return fileNames.map({ bundle.url(forResource: $0, withExtension: "ttf")! })
}
And I find it nice to have UIFont extension for registering fonts:
public extension UIFont {
public static func register(from url: URL) throws {
guard let fontDataProvider = CGDataProvider(url: url as CFURL) else {
throw SVError.internal("Could not create font data provider for \(url).")
}
let font = CGFont(fontDataProvider)
var error: Unmanaged<CFError>?
guard CTFontManagerRegisterGraphicsFont(font, &error) else {
throw error!.takeUnretainedValue()
}
}
}
Now enjoy the registration:
do {
try fontsURLs().forEach({ try UIFont.register(from: $0) })
} catch {
print(error)
}
This is a new method that lets you load fonts dynamically without putting them in your Info.plist: http://www.marco.org/2012/12/21/ios-dynamic-font-loading
Here is way I implemented it for my fmk based on the solution provided by "David M."
This solution doesn't require to add the reference to the font in the plist.
1) Class that load the font
- (void) loadMyCustomFont{
NSString *fontPath = [[NSBundle frameworkBundle] pathForResource:#"MyFontFileNameWithoutExtension" ofType:#"ttf"];
NSData *inData = [NSData dataWithContentsOfFile:fontPath];
CFErrorRef error;
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge 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);
}
2) Category on NSBundle to get access to my bundle
+ (NSBundle *)frameworkBundle {
static NSBundle* frameworkBundle = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
NSString* mainBundlePath = [[NSBundle mainBundle] resourcePath];
NSString* frameworkBundlePath = [mainBundlePath stringByAppendingPathComponent:#"MyBundleName.bundle"];
frameworkBundle = [NSBundle bundleWithPath:frameworkBundlePath];
});
return frameworkBundle;
}
Note: require to integrate CoreText in your project
In swift, I use the code below :
public class func loadMyCustomFont(name:String) -> Bool{
let fontPath = self.frameworkBundle().pathForResource(name, ofType: "ttf")!
let inData = NSData(contentsOfFile:fontPath)
var error: Unmanaged<CFError>?
let provider = CGDataProviderCreateWithCFData(inData)
if let font = CGFontCreateWithDataProvider(provider) {
CTFontManagerRegisterGraphicsFont(font, &error)
if error != nil {
print(error) //Or logged it
return false
}
}
return true
}
The frameworkBundle method :
class func frameworkBundle() -> NSBundle{
var bundle = NSBundle()
var predicate = dispatch_once_t()
dispatch_once(&predicate) {
let mainBundlePath = NSBundle.mainBundle().bundlePath
let frameworkBundlePath = mainBundlePath.stringByAppendingString("/myFramework.framework/")
bundle = NSBundle(path: frameworkBundlePath)!
}
return bundle
}
Exemple of call : (In my case, i added all fonts in the Fonts folder)
YouClassName.loadMyCustomFont("Fonts/Roboto-Regular")
Your corrections and remarks are welcome !
Updated for Swift 4/5 and changed to throw errors instead of returning a Bool.
enum FontLoadingError: Error {
case fileNotFound
case unreadableFontData
}
func loadCustomFont(name: String) throws {
guard let fontURL = frameworkBundle.url(forResource: name, withExtension: "ttf") else {
throw FontLoadingError.fileNotFound
}
guard
let provider = CGDataProvider(url: fontURL as CFURL),
let font = CGFont(provider)
else {
throw FontLoadingError.unreadableFontData
}
var cfError: Unmanaged<CFError>?
CTFontManagerRegisterGraphicsFont(font, &cfError)
if let error = cfError as? Error {
throw error
}
}
You can use this extension if you have the font in a file/bundle.
public extension UIFont {
static func register(from url: URL) throws {
if !FileManager.default.fileExists(atPath: url.path) {
throw VError.incorrectFont
}
var error: Unmanaged<CFError>?
guard CTFontManagerRegisterFontsForURL(url as CFURL, .process, &error) else {
throw error!.takeUnretainedValue()
}
}
}
Swift 3 version of #Ali-ABBAS's answer, also updated to up-wrap options instead of force unwrapping.
fileprivate func loadCustomFont(name:String) -> Bool{
guard let fontPath = frameworkBundle.path(forResource: name, ofType: "ttf") else {
return false
}
guard let inData = NSData(contentsOfFile:fontPath) else {
return false
}
guard let provider = CGDataProvider(data: inData) else {
return false
}
let font = CGFont(provider)
var error: Unmanaged<CFError>?
CTFontManagerRegisterGraphicsFont(font, &error)
guard error == nil else {
print(error) //Or logged it
return false
}
return true
}

Resources