Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Static Logo

Version Status Swift Version Carthage compatible

Simple static table views for iOS in Swift. Static's goal is to separate model data from presentation. Rows and Sections are your “view models” for your cells. You simply specify a cell class to use and that handles all of the presentation. See the usage section below for details.

Version Compatibility

Swift Version Static Version
5.0+ 4.0.2
4.2+ 3.0.1
3.2+ 2.1
3.0.1 2.0.1
3.0 2.0
2.3 1.2
2.2 1.1
2.0 - 2.1 1.0



Carthage is the recommended way to install Static. Add the following to your Cartfile:

github "venmo/Static"


CocoaPods is a dependency manager for Cocoa projects. To install Static with CocoaPods:

Make sure CocoaPods is installed (Static requires version 0.37 or greater).

Update your Podfile to include the following:

pod 'Static', git: ''

Run pod install.


For manual installation, it's recommended to add the project as a subproject to your project or workspace and adding the appropriate framework as a target dependency.


An example app is included demonstrating Static's functionality.

Getting Started

To use Static, you need to define Rows and Sections to describe your data. Here's a simple example:

import Static

Section(rows: [
    Row(text: "Hello")

You can configure Sections and Rows for anything you want. Here's another example:

Section(header: "Money", rows: [
    Row(text: "Balance", detailText: "$12.00", accessory: .disclosureIndicator, selection: {
        // Show statement
    Row(text: "Transfer to Bank…", cellClass: ButtonCell.self, selection: {
        [unowned self] in
        let viewController = ViewController()
        self.presentViewController(viewController, animated: true, completion: nil)
], footer: "Transfers usually arrive within 1-3 business days.")

Since this is Swift, we can provide instance methods instead of inline blocks for selections. This makes things really nice. You don't have to switch on index paths in a tableView:didSelectRowAtIndexPath: any more!

Customizing Appearance

The Row never has access to the cell. This is by design. The Row shouldn't care about its appearance other than specifying what will handle it. In practice, this has been really nice. Our cells have one responsibility.

There are several custom cells provided:

  • Value1Cell — This is the default cell. It's a plain UITableViewCell with the .Value1 style.
  • Value2Cell — Plain UITableViewCell with the .Value2 style.
  • SubtitleCell — Plain UITableViewCell with the .Subtitle style.
  • ButtonCell — Plain UITableViewCell with the .Default style. The textLabel's textColor is set to the cell's tintColor.

All of these conform to Cell. The gist of the protocol is one method:

func configure(row row: Row)

This gets called by DataSource (which we'll look at more in a minute) to set the row on the cell. There is a default implementation provided by the protocol that simply sets the Row's text on the cell's textLabel, etc. If you need to do custom things, this is a great place to hook in.

Row also has a context property. You can put whatever you want in here that the cell needs to know. You should try to use this as sparingly as possible.

Custom Row Accessories

Row has an accessory property that is an Accessory enum. This has cases for all of UITableViewCellAccessoryType. Here's a row with a checkmark:

Row(text: "Buy milk", accessory: .checkmark)

Easy enough. Some of the system accessory types are selectable (like that little i button with a circle around it). You can make those and handle the selection like this:

Row(text: "Sam Soffes", accessory: .detailButton({
  // Show info about this contact

Again, you could use whatever function here. Instance methods are great for this.

There is an additional case called .view that takes a custom view. Here's a Row with a custom accessory view:

Row(text: "My Profile", accessory: .view(someEditButton))

Custom Section Header & Footer Views

Section has properties for header and footer. These take a Section.Extremity. This is an enum with Title and View cases. Extremity is StringLiteralConvertible you can simply specify strings if you want titles like we did the Getting Started section.

For a custom view, you can simply specify the View case:

Section(header: .view(yourView))

The height returned to the table view will be the view's bounds.height so be sure it's already sized properly.

Working with the Data Source

To hook up your Sections and Rows to a table view, simply initialize a DataSource:

let dataSource = DataSource()
dataSource.sections = [
    Section(rows: [
        Row(text: "Hello")

Now assign your table view:

dataSource.tableView = tableView

Easy as that! If you modify your data source later, it will automatically update the table view for you. It is important that you don't change the table view's dataSource or delegate. The DataSource needs to be those so it can handle events correctly. The purpose of Static is to abstract all of that away from you.

Wrapping Up

There is a provided TableViewController that sets up a DataSource for you. Here's a short example:

class SomeViewController: TableViewController {
    override func viewDidLoad() {
        dataSource.sections = [
            Section(rows: [
                Row(text: "Hi")
            // ...