Organize your fonts in iOS Projects
Everyone who develops iOS Apps have to face with font problem. For example we want to create a new label programmatically and change its font to “AvenirNext-Medium”
let label = UILabel(frame: .zero)label.font = UIFont(name: "AvenirNext-Medium", size: 14)
Lets look at this AvenirNext-Medium
part of code. We can divide it to two part due to dash (“-”) character:
- Font Family: Before dash character part which is
AvenirNext
in our example. - Font Style: After dash part character which is
Medium
in our example.
Also you can create a label and change its font from xib/storyboard. These two methods are doing exactly same things.
Actually problems are already started! Think that you are working on huge project and usually projects use few fonts. If you cant find a way to handle all of theese line of codes, you will have to write same lines to change fonts and when you search AvenirNext-Medium
in your project there will be 9235123 same line .
The underlying problem of always writing create font with hardcoded string is that UIFont(name: String, size: CGFloat)
initializer is failable initializer (You can get more information about Failable Initializers from Swift Blog). That means if you make a type while writing font name for example AvenirNEXTMedium
, UIFont initializer will return a nil and if you write a code like
self.label = UIFont(name: "AvenirNextMedium", size: 16)
your label’s font will be System font with 16 size automatically because there is no font with this name.
Another problem to create UIFont with hardcoded string is you cant control your application’s fonts after a while. For example you decided to change your application fonts. You have to search and replace all of your hardcoded font names in project.
So we have to find a smart solution for your project. Okey here we go!
- First of all create a struct. We will call this struct when we want to create new font everytime.
public struct FontBook {}
- After that create a new protocol. Classes which conforms this protocol will be available via Fontbook.
public protocol isFont {var family: String { get }var style: String { get }}
- Lets create our first Font as enum which conform
RawRepresantable
so we will use String enums and it will also conformisFont
protocol.
public enum AvenirNext: String, isFont {
public var family: String {
return "AvenirNext"
}
public var style: String {
return self.rawValue.capitalizingFirstLetter()
}
case bold
case demiBold
case heavy
case medium
case regular
}
family
and style
properties required for conforming isFont
protocol. As we know font names are like AvenirNext-Medium
. Write every style of this font family as case of enum.
If you dont write any string equivalent of cases in string enum , it will be string of your case(like “bold”). So style
computed property will return capitalized first letter version of raw value. (capitalizingFirstLetter() method is String extension)
- Put your font into Fontbook struct and write a method into font enum that returns a UIFont
public struct FontBook {
public enum AvenirNext: String, isFont {
public var family: String {
return "AvenirNext"
}
public var style: String {
return self.rawValue.capitalizingFirstLetter()
}
case bold
case demiBold
case heavy
case medium
case regularpublic func size(_ size: CGFloat) -> UIFont {
let fontName = "\(family)-\(style)"
return UIFont(name: fontName, size: size) ?? UIFont.systemFont(ofSize: size)
}}
}
This method takes a size as a input parameter, creates UIFont with isFont protocol properties and returns UIFont.
- Now we can use our new struct like:
FontBook.AvenirNext.medium.size(14)
What if we want to add another font? You should do these steps:
- Create new string enum which conform
isFont
protocol - Set
family
andstyle
properties - Define every style of font as a enum case
- Write a method that returns a UIFont with desired size
If you notice that you will write same lines in step 4. You can write extension to isFont
protocol to get rid of step 4.
extension isFont {
public func size(_ size: CGFloat) -> UIFont {
let fontName = "\(family)-\(style)"
return UIFont(name: fontName, size: size) ?? UIFont.systemFont(ofSize: size)
}
}
As we say before, if we want to change our font style of app what will we do?
Write a extension to your FontBook to define fonts aliases according to usage. For example:
public extension FontBook {
public static let subTitle = AvenirNext.mediumpublic static let title = AvenirNext.heavy.size(18)
}
Now you can use your FontBook all of your app and if you want to change titles size or just general fonts of subtitles, just change from FontBook.
Here is the final status of FontBook
public protocol isFont {
var family: String { get }
var style: String { get }
}extension isFont {
public func size(_ size: CGFloat) -> UIFont {
let fontName = "\(family)-\(style)"
return UIFont(name: fontName, size: size) ?? UIFont.systemFont(ofSize: size)
}
}public struct FontBook {
public enum AvenirNext: String, isFont {
public var family: String {
return "AvenirNext"
}
public var style: String {
return self.rawValue.capitalizingFirstLetter()
}
case bold
case demiBold
case heavy
case medium
case regular
}
public enum MyriadWebPro: String, isFont {
public var family: String {
return "MyriadWebPro"
}
public var style: String {
return self.rawValue.capitalizingFirstLetter()
}
case regular
}}