The collection view of the feed view controller now displays episode images. We are almost ready to build the user interface of the collection view and style the EpisodeCollectionViewCell class. Before we start with that task, I want to create a framework for styling the application. That is the focus of this episode.

Creating a Consistent User Interface

You should know by now that I don't like random string literals floating around in a project. A related problem is the lack of a framework for styling the application. This usually results in an inconsistent user interface and user experience. I don't imply that you should use a third party framework for styling the application. The idea is to create a lightweight framework that makes it easy to style user interface elements and, as a result, promotes a consistent user interface and user experience. Let me show you what I have in mind.

Defining Colors

For most projects, I define a color scheme and consistently apply it in the project. The Cocoacasts website already defines a color scheme and the plan is to port it to the Xcode project.

A few years ago, Apple introduced color assets and we use that feature in this episode. Color assets offer the ability to define and name colors. Open Assets.xcassets and add a folder with name Colors by clicking the + button at the bottom and choosing New Folder from the menu. The Cocoacasts website defines red, blue, brown, and two tints of gray, a light gray and a dark gray. With the Colors folder selected, click the + button and choose New Color Set. Name the color set red.

Select the color in the editor and open the Attributes Inspector on the right. Set Input Method to 8-bit Hexadecimal and enter the hexadecimal value in the Hex field. We keep Content set to sRGB. We repeat these steps for the other colors of the Cocoacasts color scheme.

Named Colors

You can access a named color by invoking the init?(named:) initializer of the UIColor class. We take it one step further by creating a static, constant property for each named color.

Create a group with name Design in the Miscellaneous group and add a Swift file to the Design group. Name the Swift file UIColor+Design.swift. We replace the import statement for Foundation with an import statement for UIKit and define an extension for the UIColor class. To avoid name collisions, we create a namespace for the named colors by defining an enum with name Cocoacasts. The Cocoacasts enum defines a static, constant property for each of the named colors in the asset catalog. We invoke the init?(named:) initializer to create the UIColor instance and forced unwrap the result of the initialization. Initializing a named color should never fail.

import UIKit

extension UIColor {

    enum Cocoacasts {

        static let red = UIColor(named: "red")!
        static let blue = UIColor(named: "blue")!
        static let brown = UIColor(named: "brown")!
        static let darkGray = UIColor(named: "darkGray")!
        static let lightGray = UIColor(named: "lightGray")!

    }

}

As an alternative for the namespace, you can add a prefix to the static properties. In this example, I could name the static properties ccRed, ccBlue, ccBrown, and so on. Choose the option you prefer.

Defining Fonts

The Cocoacasts website uses a number of custom fonts and I would like to use these fonts in the iOS and tvOS clients. This isn't complicated. We create a group, Fonts, in the Resources group and add the custom fonts to the Fonts group.

Adding Custom Fonts

Adding the custom fonts to the application bundle doesn't suffice. We need to add the fonts to the application's Info.plist. Open the application's Info.plist and add a row with key Fonts provided by application. The value is an array of strings, the names of the font files we added to the Fonts group.

Adding Custom Fonts

To use the fonts, we need to know the name of the font family and the names of the fonts. We obtain that information by asking the UIFont class for a list of the font families and the available fonts for each font family. This is easy to find out. Open AppDelegate.swift.

We can access the list of font family names through the familyNames class property of the UIFont class. We sort the list of font family names and invoke the forEach(_:) method to access each font family name.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...

    UIFont.familyNames.sorted().forEach {

    }

    return true
}

In the closure we pass to the forEach(_:) method, we ask the UIFont class for the font names of the font family by invoking the fontNames(forFamilyName:) method, passing in the font family name. We sort the list of font names and invoke the forEach(_:) method to access each font name. Let's print the names of each font family and its fonts.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...

    UIFont.familyNames.sorted().forEach {
        print($0)
        UIFont.fontNames(forFamilyName: $0).sorted().forEach { (fontName) in
            print("   ", fontName)
        }
    }

    return true
}

Build and run the application in the simulator and inspect the output in the console. The output shows us the names of the fonts for Bree Serif and Lato, the custom fonts we added to the Fonts group and defined in the application's Info.plist.

Bree Serif
    BreeSerif-Regular
Lato
    Lato-Bold
    Lato-Light
    Lato-Regular

Before we move on, we remove the code we added to the application(_:didFinishLaunchingWithOptions:) method in AppDelegate.swift.

Creating an Extension for UIFont

To use the custom fonts, we define a few helper methods in an extension for UIFont. Add a Swift file to the Design group and name it UIFont+Design.swift. We replace the import statement for Foundation with an import statement for UIKit and define an extension for UIFont.

import UIKit

extension UIFont {

}

Remember that we plan to create a framework that makes it easier to style the application. The idea is to create a number of predefined styles. We don't want to expose the font names to the rest of the project. We start by defining a private, static, constant property, breeSerif. We assign a string literal to the property, the name of the Bree Serif font. We repeat this step for Lato Bold, Lato Light, and Lato Regular.

import UIKit

extension UIFont {

    // MARK: - Fonts

    private static let breeSerif = "BreeSerif-Regular"

    private static let latoBold = "Lato-Bold"
    private static let latoLight = "Lato-Light"
    private static let latoRegular = "Lato-Regular"

}

To create a UIFont instance, we define a convenience method for each font name. The convenience method makes it easy to create a UIFont instance for a particular font. We define a static method, breeSerif(of:), that accepts one argument of type CGFloat, the size of the font. The method returns a UIFont instance.

The implementation of the method is straightforward. It uses the breeSerif constant property and the value of the size parameter to create a UIFont instance. Notice that the result of the initialization is forced unwrapped. Creating a UIFont instance should not fail. We repeat these steps for Lato Bold, Lato Light, and Lato Regular.

import UIKit

extension UIFont {

    // MARK: - Fonts

    private static let breeSerif = "BreeSerif-Regular"

    private static let latoBold = "Lato-Bold"
    private static let latoLight = "Lato-Light"
    private static let latoRegular = "Lato-Regular"

    // MARK: - Convenience Methods

    static func breeSerif(of size: CGFloat) -> UIFont {
        return UIFont(name: breeSerif, size: size)!
    }

    static func latoBold(of size: CGFloat) -> UIFont {
        return UIFont(name: latoBold, size: size)!
    }

    static func latoLight(of size: CGFloat) -> UIFont {
        return UIFont(name: latoLight, size: size)!
    }

    static func latoRegular(of size: CGFloat) -> UIFont {
        return UIFont(name: latoRegular, size: size)!
    }

}

Defining Styles

The idea I have in mind for the framework is similar to how a web page is styled using cascading style sheets or CSS. We define a number of styles and use these styles throughout the project. It summarizes which styles are available, it promotes consistency, and it improves maintainability. Add a Swift file to the Design group and name it Typography.swift.

We replace the import statement for Foundation with an import statement for UIKit and create an extension for String. The easiest solution to style a button or a label is by using attributed strings. For each style, we define a method that returns an attributed string. Let's start with a simple example. We name the method toTitle() and it returns an NSAttributedString instance.

import UIKit

extension String {

    func toTitle() -> NSAttributedString {

    }

}

We first define the attributes of the attributed string by defining a constant, attributes, of type [NSAttributedString.Key: Any]. We set the font to Bree Serif and the font size to 24.0 points. We use the dictionary of attributes to create the attributed string and return it from the method.

import UIKit

extension String {

    func toTitle() -> NSAttributedString {
        // Define Attributes
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.breeSerif(of: 24.0)
        ]

        return NSAttributedString(string: self, attributes: attributes)
    }

}

This is as simple as it gets. We can make the toTitle() method more flexible by defining several optional parameters. The first optional parameter we define is color of type UIColor. Its default value is black. We add the color to the dictionary of attributes.

import UIKit

extension String {

    func toTitle(color: UIColor = .black) -> NSAttributedString {
        // Define Attributes
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.breeSerif(of: 24.0),
            .foregroundColor: color
        ]

        return NSAttributedString(string: self, attributes: attributes)
    }

}

The second optional parameter we define is textAlignment of type NSTextAlignment. Its default value is center. Setting the text alignment of the attributed string is slightly more complex. We define an instance of the NSMutableParagraphStyle class, set its alignment property, and update the dictionary of attributes.

import UIKit

extension String {

    func toTitle(color: UIColor = .black, textAlignment: NSTextAlignment = .center) -> NSAttributedString {
        // Create Paragraph Style
        let paragraphStyle = NSMutableParagraphStyle()

        // Configure Paragraph Style
        paragraphStyle.alignment = textAlignment

        // Define Attributes
        let attributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.breeSerif(of: 24.0),
            .paragraphStyle: paragraphStyle,
            .foregroundColor: color
        ]

        return NSAttributedString(string: self, attributes: attributes)
    }

}

This example shows how easy it is to define a style that can be used throughout the project.

Using the Builder Pattern

Let's take it one step further and use the builder pattern to create a beautiful API. In Typography.swift, we define a final class with name StringBuilder. The class defines a constant property, string, of type String. The value of string is the string we plan to style. We define an initializer that accepts one argument, string, of type String. We set the string property in the body of the initializer.

final class StringBuilder {

    // MARK: - Properties

    private let string: String

    // MARK: - Initialization

    init(string: String) {
        // Set Properties
        self.string = string
    }

}

You should be familiar with the builder pattern if you watched the previous episode. We define a nested enum with name FontName. It defines a case for each of the custom fonts, breeSerif, latoBold, latoLight, and latoRegular. We also implement a convenience method, font(of:) that returns a UIFont instance. The FontName object uses the convenience methods we defined earlier in the extension for the UIFont class to create a UIFont object. We limit access to the font(of:) method with the fileprivate keyword.

final class StringBuilder {

    // MARK: - Types

    enum FontName {

        // MARK: - Cases

        case breeSerif
        case latoBold
        case latoLight
        case latoRegular

        // MARK: - Methods

        fileprivate func font(of size: CGFloat) -> UIFont {
            switch self {
            case .breeSerif: return UIFont.breeSerif(of: size)
            case .latoBold: return UIFont.latoBold(of: size)
            case .latoLight: return UIFont.latoLight(of: size)
            case .latoRegular: return UIFont.latoRegular(of: size)
            }
        }

    }

    // MARK: - Properties

    private let string: String

    // MARK: - Initialization

    init(string: String) {
        // Set Properties
        self.string = string
    }

}

The next step is defining private, variable properties for the attributes we want to customize using the builder. We start with a private, variable property, fontName, of type FontName. The default value of the fontName property is latoRegular.

private var fontName: FontName = .latoRegular

We also define a private, variable property for the size of the UIFont instance. The size property is of type CGFloat and has a default value of 17.0 points.

private var size: CGFloat = 17.0

The next property we define is of type UIColor and defines the color of the attributed string. The default value of the color property is black.

private var color: UIColor = .black

The last property we define is textAlignment of type NSTextAlignment with a default value of center.

private var textAlignment: NSTextAlignment = .center

With the private properties in place, it's time to define the public API. We create a method for each of the properties, fontName(_:), size(_:), color(_:), and textAlignment(_:). The corresponding property is set in the body of the method and, to enable method chaining, each method returns the StringBuilder instance.

// MARK: - Public API

func fontName(_ fontName: FontName) -> StringBuilder {
    self.fontName = fontName
    return self
}

func size(_ size: CGFloat) -> StringBuilder {
    self.size = size
    return self
}

func color(_ color: UIColor) -> StringBuilder {
    self.color = color
    return self
}

func textAlignment(_ textAlignment: NSTextAlignment) -> StringBuilder {
    self.textAlignment = textAlignment
    return self
}

The build() method returns an NSAttributedString instance and its implementation is similar to that of the toTitle(color:textAlignment:) method we created earlier. We define a mutable paragraph style and set its alignment property. We create the dictionary of attributes for the attributed string, using the values of the fontName and color properties. We ask the FontName object for a UIFont instance by invoking the font(of:) method. The dictionary of attributes is used to create an NSAttributedString instance, which we return from the build() method.

func build() -> NSAttributedString {
    // Create Paragraph Style
    let paragraphStyle = NSMutableParagraphStyle()

    // Configure Paragraph Style
    paragraphStyle.alignment = textAlignment

    // Define Attributes
    let attributes: [NSAttributedString.Key: Any] = [
        .foregroundColor: color,
        .font: fontName.font(of: size),
        .paragraphStyle: paragraphStyle
    ]

    return NSAttributedString(string: string, attributes: attributes)
}

Let's put the cherry on the cake by using the StringBuilder class in the toTitle(color:textAlignment:) method. We remove the current implementation and create an instance of the StringBuilder class, passing in self as the argument. We set the font size to 24.0 points by invoking the size(_:) method. The value of the color parameter of the toTitle(color:textAlignment:) method is passed to the color(_:) method of the StringBuilder instance. We pass breeSerif to the fontName(_:) method and the value of the textAlignment parameter of the toTitle(color:textAlignment:) method to the textAlignment(_:) method. We invoke the build() method to create the attributed string. I'm sure you agree that the API is flexible, readable, and elegant.

func toTitle(color: UIColor = .black, textAlignment: NSTextAlignment = .center) -> NSAttributedString {
    return StringBuilder(string: self)
        .size(24.0)
        .color(color)
        .fontName(.breeSerif)
        .textAlignment(textAlignment)
        .build()
}

Let's use the toTitle(color:textAlignment:) method to style the EpisodeCollectionViewCell class. We only need to update the configure(with:) method of the EpisodeCollectionViewCell class. We no longer set the text property of the title label. We set the attributedText property instead. We invoke the toTitle(color:textAlignment:) method on the value of the title property of the EpisodePresentable object. We pass UIColor.Cocoacasts.red as the only argument of the toTitle(color:textAlignment:) method.

func configure(with presentable: EpisodePresentable) {
    // Configure Title Label
    titleLabel.attributedText = presentable.title.toTitle(color: UIColor.Cocoacasts.red)

    // Configure Image View
    imageView.kf.setImage(with: presentable.thumbnailUrl)
}

If we build and run the application in the simulator, the title of each episode collection view cell should be styled.

Running the Application in the Simulator

Before we end this episode, I would like to do some cleanup. With the addition of the StringBuilder class, we no longer need the extension for the UIFont class. This means we can remove UIFont+Design.swift. Open Typography.swift and set the raw value of the FontName enum to String. The raw value of each case of the FontName enum should be the name of the corresponding font.

final class StringBuilder {

    // MARK: - Types

    enum FontName: String {

        // MARK: - Cases

        case breeSerif = "BreeSerif-Regular"
        case latoBold = "Lato-Bold"
        case latoLight = "Lato-Light"
        case latoRegular = "Lato-Regular"

        ...
    }

    ...
}

This means that we can simplify the implementation of the font(of:) method of the FontName enum. To instantiate a UIFont instance, we invoke the init?(name:size:) initializer, passing in the raw value and the size. We forced unwrap the result of the initialization.

final class StringBuilder {

    // MARK: - Types

    enum FontName: String {

        // MARK: - Cases

        case breeSerif = "BreeSerif-Regular"
        case latoBold = "Lato-Bold"
        case latoLight = "Lato-Light"
        case latoRegular = "Lato-Regular"

        // MARK: - Methods

        fileprivate func font(of size: CGFloat) -> UIFont {
            return UIFont(name: rawValue, size: size)!
        }

    }

    ...
}

What's Next?

A framework for styling an application shouldn't be complex or span hundreds of lines. You can start simple. The solution I showed you in this episode is one of many solutions. I encourage you to experiment and come up with a solution that you enjoy using and that fits your project's needs.