How to load iOS 13 default font using font name? - swift

I have a strange problem with iOS 13 system font. I am trying to load iOS default font San Francisco using the font name. But due to some iOS bug it's loading some other font like Times New Roman. I saw few problems like this here and here. But in my case I have to load the font with name because I am using a Utility class to globally set font name in app delegate. Is there any way to load the system font using font name in iOS 13?
// Set font here
MUIConstats.shared.defaultFontRegularName = UIFont.systemFont(ofSize: 17).fontName
// Get font from here
class func appFontRegularOf(size: CGFloat)-> UIFont{
if let fontName = MUIConstats.shared.defaultFontRegularName {
return UIFont(name:fontName, size: size) ?? UIFont.systemFont(ofSize:size)
}else{
return UIFont.systemFont(ofSize:size)
}
}

You can store the name of a nonsystem font, but if it's the system font the only thing you're allowed to store is the fact that it's the system font. This is a perfect opportunity for a union:
enum WhatFont {
case system
case name(String)
func fontOfSize(_ sz:CGFloat) -> UIFont? {
switch self {
case .system: return UIFont.systemFont(ofSize: sz)
case .name(let name): return UIFont(name: name, size: sz)
}
}
}
Test it:
let f1 = WhatFont.system
print(f1.fontOfSize(17))
let f2 = WhatFont.name("Georgia")
print(f2.fontOfSize(17))

Related

find device language with swift [duplicate]

I want get the language code of the device (en, es...) in my app written with Swift. How can get this?
I'm trying this:
var preferredLanguages : NSLocale!
let pre = preferredLanguages.displayNameForKey(NSLocaleIdentifier, value: preferredLanguages)
But this returns nil.
In Swift 3
let langStr = Locale.current.languageCode
It's important to make the difference between the App language and the device locale language (The code below is in Swift 3)
Will return the Device language:
let locale = NSLocale.current.languageCode
Will return the App language:
let pre = Locale.preferredLanguages[0]
Swift 4 & 5:
Locale.current.languageCode
Swift 3 & 4 & 4.2 & 5
Locale.current.languageCode does not compile regularly. Because you did not implemented localization for your project.
You have two possible solutions
1) String(Locale.preferredLanguages[0].prefix(2))
It returns phone lang properly.
If you want to get the type en-En, you can use Locale.preferredLanguages[0]
2)
Select Project(MyApp)->Project (not Target)-> press + button into Localizations, then add language which you want.
In Swift 3:
NSLocale.current.languageCode
TL;DR:
Use Bundle.main.preferredLocalizations[0] to get the language your app's UI is currently displayed in. Don't use Locale.current because it describes the region format (time, currency, distance, etc) and has nothing to do with language.
Detailed Answer:
The definite answer about how to get the language(!) code for the language your app's UI is displayed in comes from Apple engineer Quinn "The Eskimo", and I quote/paraphrase for Swift:
Locale.current returns the current locale, that is, the value set by Settings > General > Language & Region > Region Formats. It has nothing to do with the language that your app is running in. It's perfectly reasonable, and in fact quite common, for users in the field to have their locale and language set to 'conflicting' values. For example, a native English speaker living in France would have the language set to English but might choose to set the locale to French (so they get metric weights and measures, 24 time, and so on).
The language that your app runs in is determined by the language setting, that is, Settings > General > Language & Region > Preferred Language Order. When the system runs your app it takes this list of languages (the preferred list) and matches it against the list of languages that your app is localised into (the app list). The first language in the preferred list that exists in the app list is the language chosen for the app. This is what you'll find in the first entry of the main bundle's preferredLocalizations array.
Language Name from Code
To get the human-readable name of a language from its code, you can use this:
let langCode = Bundle.main.preferredLocalizations[0]
let usLocale = Locale(identifier: "en-US")
var langName = ""
if let languageName = usLocale.localizedString(forLanguageCode: langCode) {
langName = languageName
}
This will give you the English name of the current UI language.
To get current language used in your app (different than preferred languages)
NSLocale.currentLocale().objectForKey(NSLocaleLanguageCode)!
swift 3
let preferredLanguage = Locale.preferredLanguages[0] as String
print (preferredLanguage) //en-US
let arr = preferredLanguage.components(separatedBy: "-")
let deviceLanguage = arr.first
print (deviceLanguage) //en
Locale.current.languageCode returns me wrong code, so I use these extensions:
extension Locale {
static var preferredLanguageCode: String {
guard let preferredLanguage = preferredLanguages.first,
let code = Locale(identifier: preferredLanguage).languageCode else {
return "en"
}
return code
}
static var preferredLanguageCodes: [String] {
return Locale.preferredLanguages.compactMap({Locale(identifier: $0).languageCode})
}
}
Swift 5.4:
let languagePrefix = Locale.preferredLanguages[0]
print(languagePrefix)
you may use the below code it works fine with swift 3
var preferredLanguage : String = Bundle.main.preferredLocalizations.first!
I want to track the language chosen by the user in Settings app every time the user launches my app - that is not yet localized (my app is in English only). I adopted this logic:
create an enum to to make it easier to handle the languages in array
enum Language: String {
case none = ""
case en = "English"
case fr = "French"
case it = "Italian"
} // add as many languages you want
create a couple of extension to Locale
extension Locale {
static var enLocale: Locale {
return Locale(identifier: "en-EN")
} // to use in **currentLanguage** to get the localizedString in English
static var currentLanguage: Language? {
guard let code = preferredLanguages.first?.components(separatedBy: "-").last else {
print("could not detect language code")
return nil
}
guard let rawValue = enLocale.localizedString(forLanguageCode: code) else {
print("could not localize language code")
return nil
}
guard let language = Language(rawValue: rawValue) else {
print("could not init language from raw value")
return nil
}
print("language: \(code)-\(rawValue)")
return language
}
}
When you need, you can simply use the extension
if let currentLanguage = Locale.currentLanguage {
print(currentLanguage.rawValue)
// Your code here.
}
In Swift, You can get the locale using.
let locale = Locale.current.identifier
This is what I use in Swift 5 Xcode 11:
Inside the class variables:
let languagePrefix = Bundle.main.preferredLocalizations.first?.prefix(2)
This comes as a string. It returns 2 characters, i.e. "en", "es", "de"...
From this I can easily determine what language to display:
if languagePrefix == "es" { self.flipCard.setTitle("última carta", for: .normal) }
if languagePrefix == "en" { self.flipCard.setTitle("Last Card", for: .normal) }
If you want the full information of the language, then remove ?.prefex(2)
in most cases you want to get the language code of the current app UI, to send over an API to get localized response
extension Bundle {
var currentLocalizedUILanguageCode: String {
guard let code = Bundle.main.preferredLocalizations.first?.components(separatedBy: "-").first else {
return Locale.current.languageCode ?? "en"
}
return code
}
}
use like
headers["X-Language"] = Bundle.main.currentLocalizedUILanguageCode
use this function for get your system's current language code from iOS devices
func getSystemLanguageCode() -> String {
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
let pref_Language = NSLocale.preferredLanguages[0] as String //"fr-IN"
let language = pref_Language.components(separatedBy: "-") //["fr","IN"]
let lang_Code = language.first?.lowercased() ?? "" //"fr"
UserDefaults.standard.set([lang_Code], forKey: "AppleLanguages")
return lang_Code
}
Almost none of the answers are correct. This is working Swift 5.7 solution.
extension Locale {
// Gets the language of the device, had to remove the content of AppleLanguages since `preferredLanguages`
// is combining the result from multiple APIs. AppleLanguage is then being set to the old value
static var preferredLanguageCode: String {
let appleLanguages = UserDefaults.standard.stringArray(forKey: kLanguage)
UserDefaults.standard.removeObject(forKey: kLanguage)
guard let preferredLanguage = preferredLanguages.first,
let code = Locale(identifier: preferredLanguage).languageCode else {
UserDefaults.standard.set(appleLanguages, forKey: kLanguage)
return "en"
}
UserDefaults.standard.set(appleLanguages, forKey: kLanguage)
return code
}
}

CTFontGetGlyphsForCharacters Returns false when passing emoji

In a class conforming to NSLayoutManagerDelegate I implement this method:
func layoutManager(_ layoutManager: NSLayoutManager,
shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>,
properties props: UnsafePointer<NSLayoutManager.GlyphProperty>,
characterIndexes charIndexes: UnsafePointer<Int>,
font aFont: UIFont,
forGlyphRange glyphRange: NSRange) -> Int {
// First, make sure we'll be able to access the NSTextStorage.
guard let textStorage = layoutManager.textStorage
else { return 0 }
// Get the first and last characters indexes for this glyph range,
// and from that create the characters indexes range.
let firstCharIndex = charIndexes[0]
let lastCharIndex = charIndexes[glyphRange.length - 1]
let charactersRange = NSRange(location: firstCharIndex, length: lastCharIndex - firstCharIndex + 1)
var bulletPointRanges = [NSRange]()
var hiddenRanges = [NSRange]()
let finalGlyphs = UnsafeMutablePointer<CGGlyph>(mutating: glyphs)
// Generate the Middle Dot glyph using aFont.
let middleDot: [UniChar] = [0x00B7] // Middle Dot: U+0x00B7
var myGlyphs: [CGGlyph] = [0]
// Get glyphs for `middleDot` character
guard CTFontGetGlyphsForCharacters(aFont, middleDot, &myGlyphs, middleDot.count) == true
else { fatalError("Failed to get the glyphs for characters \(middleDot).") }
}
The problem is that CTFontGetGlyphsForCharacters returns false when I type an emoji into the textview. I think it might have something to do with UTF-8 vs. UTF-16 but I'm kind of out of my depth a little here. Little help?
The font you are using does not have a glyph for that particular character.
The system maintains a list of "font fallbacks" for times when the specific font you are trying to look at does not have a glyph but another font might.
The list of fallbacks is given by CTFontCopyDefaultCascadeListForLanguages, but since you're at the point where you are being asked for the glyph from a particular font, it seems that fallback generation should be handled higher up in the chain.
You should probably return 0 to indicate that the layout manager should use it's default behavior.

How to stop NSTextView from displaying characters that are not supported in a specified font

I am looking for a way to stop an NSTextView from falling back on a cascading font when certain characters in the display string are unavailable in the specified font.
I am writing an app in which it is important that the user know if certain characters (those in East Asian character sets, for example) are available in a given font. If these characters are not available, I want the field to display these as boxes, blank spaces, or something similar. However, the default is for the textview to fall back on a cascading font in which the characters are supported. Is there any way to change this behavior?
If this is not possible, might there be some way to detect which languages are supported by a given font?
Any help would be greatly appreciated.
I was able to make it work by creating extensions partially based on this answer.
The solution I ended up using was to iterate through each character in the string in question and check to see if it is contained in the font's character set. If it is not, the appearance is changed (in this case, changing the color and adding strikethrough) before it is added to an attributed string containing all of characters of the original string.
This is what it ended up looking like:
extension UnicodeScalar {
func isIn(font:NSFont)-> Bool {
let coreFont:CTFont = font
let charSet:CharacterSet = CTFontCopyCharacterSet(coreFont) as CharacterSet
if charSet.contains(self) {
return true
}
return false
}
}
extension NSFont {
func supportString(_ str:String)-> NSAttributedString {
let attString = NSMutableAttributedString(string: "")
for scalar in str.unicodeScalars {
if !scalar.isIn(font: self) {
let r:CGFloat = 200
let g:CGFloat = 0
let b:CGFloat = 0
let a:CGFloat = 0.2
let color = NSColor(red: r, green: g, blue: b, alpha: a)
let attcha = NSMutableAttributedString(string: String(scalar), attributes: [NSAttributedStringKey.foregroundColor:color, NSAttributedStringKey.strikethroughStyle:NSUnderlineStyle.styleSingle.rawValue])
attString.append(attcha)
} else {
let attcha = NSAttributedString(string: String(scalar), attributes: [NSAttributedStringKey.foregroundColor:NSColor.black])
attString.append(attcha)
}
}
return attString
}
}

Get all available characters from a font

I am developing an iOS app in Swift 3.
In this app I am listing all available fonts (system provided) but I would like to list all available characters for them too.
For example I am using Font Awesome to and I want the user to be able to select any of the characters/symbols from a list. How can I do this?
This is how I get an array of the fonts. How can I get an array of all characters for a selected font?
UIFont.familyNames.map({ UIFont.fontNames(forFamilyName: $0)}).reduce([]) { $0 + $1 }
For each UIFont, you have to get characterSet of that font. For example, I take first UIFont.
let firsttFont = UIFont.familyNames.first
let first = UIFont(name: firsttFont!, size: 14)
let fontDescriptor = first!.fontDescriptor
let characterSet : NSCharacterSet = fontDescriptor.object(forKey: UIFontDescriptorCharacterSetAttribute) as! NSCharacterSet
Then, use this extension to get all characters of that NSCharacterSet:
extension NSCharacterSet {
var characters:[String] {
var chars = [String]()
for plane:UInt8 in 0...16 {
if self.hasMemberInPlane(plane) {
let p0 = UInt32(plane) << 16
let p1 = (UInt32(plane) + 1) << 16
for c:UTF32Char in p0..<p1 {
if self.longCharacterIsMember(c) {
var c1 = c.littleEndian
let s = NSString(bytes: &c1, length: 4, encoding: String.Encoding.utf32LittleEndian.rawValue)!
chars.append(String(s))
}
}
}
}
return chars
}
}
(Ref: NSArray from NSCharacterset)
So, at last, just call characterSet.characters to get all characters (in String)
I don't think you'll be able to without a lot of coding. Here's a few links to Apple documentation:
In their main font page you'll have to scroll down a bit to get to a list of documentation, but in that list is their TrueType reference manual. The characters are stored as glyphs, meaning they are vector-based to allow for clean font sizes. (I believe the simple drop-down of font sizes in IB are merely "suggestions", and you can type in any size you care to.)
In that second link, scroll down to the lengthy list of font tables. One looks promising - the cmap table. But reading through this, it's possible to (a) have foreign characters like "umlaut A" or Chinese and (b) omit characters in each font. Also, this is just a lookup table - you'll then maybe need to use the mapping table to get the location of the glyph.
If you are targeting English only, you might be better off finding a way to check if the letters "Aa" exist for the font and display them.

How to check if a font is available in version of iOS?

I'm currently working on an app that uses the "ChalkboardSE-Regular" font and my deployment target is 4.0+. This font was not available in 4.1 but it is supported in 4.3. What would be the best way to go about checking if the font exists and if it does not, use another supported font on the <4.1 versions of iOS. [UIFont familyName] returns a list of these fonts "Chalkboard SE"
Thanks in advance!
T
[UIFont familyName] is supported back to iPhone OS 2.0 (before 2.0, third-party apps were not allowed on iPhone or iPod touch) , so I would use that to check to see if a font family exists on the current device, and if it doesn't exist, use a suitable fall-back font for that version of iOS. Here's John Gruber's list of iPhone fonts from the original iPhone in 2007 (contrasted with the fonts in Mac OS X of the day). I haven't checked them all, but the iOS fonts I did check are still in iOS 5:
http://daringfireball.net/misc/2007/07/iphone-osx-fonts
Here's an example of using [UIFont familyName]:
NSLog (#"Font families: %#", [UIFont familyNames]);
This will produce a list like this:
Font families: (
Thonburi,
"Snell Roundhand",
"Academy Engraved LET", ... et cetera.
Once you know the family name, you can use the UIFont class method fontNamesForFamilyName to get an NSArray of the names of all the fonts in the family. For example:
NSLog (#"Courier New family fonts: %#", [UIFont fontNamesForFamilyName:#"Courier New"]);
That will produce a list like this:
Courier New family fonts: (
CourierNewPSMT,
"CourierNewPS-BoldMT",
"CourierNewPS-BoldItalicMT",
"CourierNewPS-ItalicMT"
)
The following example code prints a list of each font in every family on the current device:
NSArray *fontFamilies = [UIFont familyNames];
for (int i = 0; i < [fontFamilies count]; i++)
{
NSString *fontFamily = [fontFamilies objectAtIndex:i];
NSArray *fontNames = [UIFont fontNamesForFamilyName:[fontFamilies objectAtIndex:i]];
NSLog (#"%#: %#", fontFamily, fontNames);
}
For more information, check out the iOS Developer Reference document for the UIFont class methods familyNames and fontNamesForFamilyName:.
If you use Swift you can print all fonts (see below). You can also check if the font exists.
for family in UIFont.familyNames {
print("\(family)")
for name in UIFont.fontNames(forFamilyName: family) {
print(" \(name)")
}
}
Swift 5.x
Forget those for-loops, this is the easiest way to see fonts supported by iOS.
Just run any of the following in a playground.
Family Names Only
Input
dump(UIFont.familyNames)
Output
▿ 75 elements
- "Copperplate"
- "Heiti SC"
- "Kohinoor Telugu"
...
Font Names Only
Input
dump(UIFont.familyNames.compactMap { UIFont.fontNames(forFamilyName: $0) })
Output
▿ 248 elements
- "Copperplate-Light"
- "Copperplate"
- "Copperplate-Bold"
...
Font Names for Specified Family Name
Input
dump(UIFont.fontNames(forFamilyName: "Helvetica Neue"))
Output
▿ 14 elements
- "HelveticaNeue-Italic"
- "HelveticaNeue-Bold"
- "HelveticaNeue-UltraLight"
- "HelveticaNeue-CondensedBlack"
...
This font was not available in 4.1 but it is supported in 4.3. What would be the best way to go about checking if the font exists
Simply ask for the font in the usual way using UIFont* f = [UIFont fontWithName:... size:...];. If the font isn't available, the result (f) will be nil.
(One way to guarantee the availability of a font is to include it in the app bundle...)
For iOS9 / Swift 2.0, none of above won't work as the syntax changed a little. I also personally prefer to use extension (I choose to create one for UIFont, as it fits the best and modified #API answer as this was the best one):
extension UIFont {
static func availableFonts() {
// Get all fonts families
for family in UIFont.familyNames() {
NSLog("\(family)")
// Show all fonts for any given family
for name in UIFont.fontNamesForFamilyName(family) {
NSLog(" \(name)")
}
}
}
}
Now you can just call:
UIFont.availableFonts()
And it will tell you all the fonts in the form of
: Bangla Sangam MN
: BanglaSangamMN-Bold
: BanglaSangamMN
: Zapfino
: Zapfino
Hope it helps!
This is what i did on objective c to find out if font is available or not
NSFont *font = [NSFont fontWithName:#"thefont" size:25.0];
if (font==nil) {
// thefont is available
} else {
// thefont is not available
}
Swift version:
UIFont.familyNames().sort( { $0 < $1 } ).forEach({ print("\($0)"); UIFont.fontNamesForFamilyName("\($0)").sort( { $0 < $1 } ).forEach({ print(" \($0)") }) })
Well, rather than writing a single line of code, you can just open http://iosfonts.com and check availability based on your iOS version support. You can also know how would it look like.
Here is a conversion of Steves answer to Swift code (for quick copy and paste purpose):
var fontFamilies = UIFont.familyNames()
for (var i:Int = 0; i < fontFamilies.count; i++) {
var fontFamily: NSString = fontFamilies[i] as NSString
var fontNames: NSArray = UIFont.fontNamesForFamilyName(fontFamilies[i] as String) as NSArray
NSLog ("%#: %#", fontFamily, fontNames)
}
Try to init with that font name, and if it's nil do something else. Swift code:
if let font = UIFont(name: name, size: size) { // ok } else { // not ok }
I figured that using swiftui preview, you can make a app that shows what each font looks like using the preview simulator.
struct TitleViewModifier_Previews:
PreviewProvider {
static var previews: some View {
List{
ForEach(UIFont.familyNames.sorted(),id: \.self){ family in
ForEach(UIFont.fontNames(forFamilyName: family), id: \.self){ eachFont in
Text(eachFont)
.font(.custom(eachFont, size: 22))
}
}
}
}
}
Here is what my preview looks like
Press the play button to make the list interactive and scrollable
For objective-c
for (NSString *family in UIFont.familyNames) {
NSLog(#"family %#",family);
for (NSString *name in [UIFont fontNamesForFamilyName:family]) {
NSLog(#" name = %#",name);
}
}
print("Font families: %#", UIFont.familyNames)
Swift 4.x
UIFont.familyNames().sort( { $0 < $1 } ).forEach({ print("\($0)"); UIFont.fontNamesForFamilyName("\($0)").sort( { $0 < $1 } ).forEach({ print(" \($0)") }) })
Swift 5.x
UIFont.familyNames.sorted( by: { $0 < $1 } ).forEach({ print("\($0)"); UIFont.fontNames(forFamilyName: "\($0)").sorted(by: { $0 < $1 } ).forEach({ print(" \($0)") }) })
To check if UIFont is registered:
let fontName = "Helvetica"
UIFont.familyNames.flatMap(UIFont.fontNames).contains(fontName)