Episode #205

TableView Customization

17 minutes
Published on January 22, 2016

This video is only available to subscribers. Get access to this video and 584 others.

In this episode I take a stock UITableView and UINavigationController and customize their appearance to match a design for a Starcraft II companion app. We'll leverage Swift enums to capture colors & fonts so we can reuse them in multiple places. We will also utilize the UIAppearance API to style all instances of a UINavigationBar, and get rid of UITableViewCell's default separator indentation.

Episode Links

Setting up Colors and Fonts for Easy Re-use

It's not easy sharing colors & font settings between Storyboards and Code, so we'll do the majority of our customization in code.

struct Theme {

    enum Colors {
        case TintColor
        case BackgroundColor
        case DarkBackgroundColor
        case SectionHeader
        case Foreground
        case LightTextColor

        var color: UIColor {
            switch self {
            case .TintColor: return UIColor(red:0.97, green:0.9, blue:0, alpha:1)
            case .BackgroundColor: return UIColor(hue:0.67, saturation:0.37, brightness:0.35, alpha:1)
            case .DarkBackgroundColor: return UIColor(red:0.11, green:0.1, blue:0.22, alpha:1)
            case .SectionHeader: return UIColor(hue:0.67, saturation:0.4, brightness:0.25, alpha:1)
            case .Foreground: return UIColor(red:0.26, green:0.25, blue:0.37, alpha:1)
            case .LightTextColor: return UIColor(red:0.64, green:0.65, blue:0.8, alpha:1)
            }
        }
    }

    enum Fonts {
        case TitleFont
        case BoldTitleFont

        var font: UIFont {
            switch self {
            case .BoldTitleFont: return UIFont(name: "Copperplate-Bold", size: 17)!
            case .TitleFont: return UIFont(name: "Copperplate", size: 16)!
            }
        }
    }
}

With this in place we can easily reference our colors and fonts from anywhere. Updating the styles for further tweaking is as simple as modifying Theme.swift.

Setting Global Styles

In AppDelegate.swift:

window?.tintColor = Theme.Colors.TintColor.color
let navBarAppearance = UINavigationBar.appearance()
navBarAppearance.titleTextAttributes = [
    NSFontAttributeName: Theme.Fonts.BoldTitleFont.font,
    NSForegroundColorAttributeName: Theme.Colors.TintColor.color
]
navBarAppearance.barStyle = UIBarStyle.Black
navBarAppearance.barTintColor = Theme.Colors.Foreground.color

Initial Table View Tweaks

In viewDidLoad we can set our colors, as well as removing the fake repeating cells that occur at the bottom of a .Plain-styled UITableView:

tableView.separatorColor = Theme.Colors.DarkBackgroundColor.color
tableView.backgroundColor = Theme.Colors.BackgroundColor.color
tableView.tableFooterView = UIView()

Custom Race Header View

Instead of the standard section headers, we'll implement our own view:

class RaceHeaderView : UIView {
    var imageView: UIImageView!
    var label: UILabel!

    convenience init() {
        self.init(frame: CGRectZero)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupSubviews()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupSubviews()
    }

    func setupSubviews() {
        backgroundColor = Theme.Colors.SectionHeader.color

        imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.contentMode = UIViewContentMode.Center
        addSubview(imageView)

        imageView.centerYAnchor.constraintEqualToAnchor(centerYAnchor).active = true
        imageView.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 20).active = true

        label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.font = Theme.Fonts.BoldTitleFont.font
        label.textColor = Theme.Colors.TintColor.color
        addSubview(label)
        label.leftAnchor.constraintEqualToAnchor(imageView.rightAnchor, constant: 20).active = true
        label.centerYAnchor.constraintEqualToAnchor(imageView.centerYAnchor).active = true
    }
}

Then we can vend this view in our controller when our table view asks for it:

    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = RaceHeaderView()
        let race = self.tableView(tableView, titleForHeaderInSection: section)!
        header.label.text = race
        header.imageView.image = UIImage(named: race.lowercaseString)!
        return header
    }

    override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 66
    }

Removing the Default Separator Margin

I never understood the rationale behind the cell separator indent. It looks okay for a stock look & feel, but often looks wrong when you start changing styles. We can remove this in our custom cell subclass:

class GuideCell : UITableViewCell {
    static let reuseIdentifier = "GuideCell"

    @IBOutlet weak var nameLabel: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        backgroundColor = Theme.Colors.Foreground.color
        nameLabel.font = Theme.Fonts.TitleFont.font
        nameLabel.textColor = Theme.Colors.LightTextColor.color

        separatorInset = UIEdgeInsetsZero
        layoutMargins = UIEdgeInsetsZero
    }
}

You can also choose to do this in the controller itself, if all cells need to have the same effect applied:

func tableView(_ tableView: UITableView,
        willDisplayCell cell: UITableViewCell,
      forRowAtIndexPath indexPath: NSIndexPath) {
    cell.separatorInset = UIEdgeInsetsZero
    cell.layoutMargins = UIEdgeInsetsZero
}

And with that, we've customized the theme quite a bit!

Credits

Icons were obtained from the Starcraft Wikia.