Skip to content

Using Table Schemer

James Richard edited this page Dec 19, 2015 · 14 revisions

The primary class in TableSchemer is the TableScheme class. This class implements the UITableViewDataSource protocol. It contains an array of SchemeSet objects, which map to a table view section. Each SchemeSet has an array of Scheme objects. Each Scheme object represents one or more cells in your table.

Creating a TableScheme

The recommended approach to creating a TableScheme is using the closure syntax provided by the init(buildHandler) convenience initializer. This allows you to group the table scheme creation in one block, and visually see how the layout of the table will look. Here's an example of creating a single BasicScheme cell:

let tableScheme = TableScheme(tableView: tableView) { builder in
    builder.buildSchemeSet { builder in
        builder.headerText = "Section Header"
                
        builder.buildScheme { (scheme: BasicSchemeBuilder) in                    
            scheme.configurationHandler = { cell in
                cell.textLabel.text = "My Cell"
            }
        }
    }
}

Each SchemeBuilder object has a createScheme method that creates the Scheme if it has all the required properties configured on it. This property is checked by the builder object before instantiating the SchemeSet. If it doesn't have the correct properties, an error will be thrown that is force-tried. This allows you to be sure your scheme is configured correctly at development time.

Scheme Builders use lots of optionals

Scheme Builders make heavy use of optionals to allow easy configuration. However, every scheme has some required information. The Scheme Builder will throw an error if it doesn't have one of the required properties.

Schemes are Generic

All built-in Scheme objects require at least one generic type to represent the UITableViewCell that it vends. Some Schemes can have multiple cell types, but due to how typing works in Swift, you'll have to do a conditional cast to get the proper cell type when configuring.

If your Scheme is just a regular UITableViewCell, you don't need to provide the generic, as shown above. If it does, you'll just pass the type like so:

builder.buildScheme { (scheme: BasicSchemeBuilder<InputFieldCell>) in 
}

When creating a custom Scheme, you aren't forced to use Generics like the built-in Schemes, but it is recommended. More details on that in Creating Custom Schemes.

Handling Selection

TableScheme allows you to set selection handlers on your Scheme classes. TableScheme conforms to UITableViewDelegate and sets itself as the delegate on instantiation. If you need to set another delegate for the table view you'll have to forward selection from your delegate to the TableScheme class.

Selections are handled by the TableScheme like so:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableScheme.tableView(tableView, didSelectRowAtIndexPath: indexPath)
}

Handling Height

If you aren't handling height through Auto Layout you can have height handled by your scheme objects. Each Scheme object handles height in its own way. See the Built-in Schemes section for more details on that.

TableScheme conforms to UITableViewDelegate and sets itself as the delegate on instantiation. If you need to set another delegate for the table view you'll have to forward height from your delegate to the TableScheme class.

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return tableScheme.tableView(tableView, heightForRowAtIndexPath: indexPath)
}

Interactions Inside Cells

If you're interacting with views inside the cell through Target/Action or some other mechanism other than cell selection you may need to know which scheme is triggering it. Here's a basic of a method used to handle switches from multiple schemes:

func switcherUpdated(switcher: UISwitch) {
    if let scheme = tableScheme.schemeContainingView(switcher) {
        if scheme === self.firstSwitchScheme {
            self.wifiEnabled = switcher.on
        } else if scheme === self.secondSwitchScheme {
            self.bluetoothEnabled = switcher.on
        }
    }
}

Sometimes though, you'll be working with an ArrayScheme or RadioScheme. In those instances you may need to know which item within those schemes was selected. Here's how you'd find that:

func buttonPressed(button: UIButton) {
    if let tuple = tableScheme.schemeWithIndexContainingView(button) {
        if tuple.scheme === self.buttonsScheme {
            let object = self.buttonsScheme.objects![tuple.index]
        }
    }
}

You can see the full source code for those examples in the example project in AdvancedTableSchemeViewController.swift.

Managing Scheme Visibility

Schemes and SchemeSets both have the concept of visibility. Schemes and SchemeSets that are set as hidden do not show up in the table view, and are not counted towards the number of rows and sections in the table view.

You should define the initial state of the these classes when constructing the TableScheme object using the swift var hidden: Bool property during configuration.

TableScheme provides all of the key methods to update the visibility. Here's an example demonstrating all of them:

tableScheme.hideScheme(scheme, inTableView: tableView, withRowAnimation: .Fade)
tableScheme.showScheme(scheme, inTableView: tableView, withRowAnimation: .Fade)
tableScheme.hideSchemeSet(schemeSet, inTableView: tableView, withRowAnimation: .Fade)
tableScheme.showSchemeSet(schemeSet, inTableView: tableView, withRowAnimation: .Fade)

If you need to reload your scheme or scheme set you can do that as well:

tableScheme.reloadScheme(scheme, inTableView: tableView, withRowAnimation: .Fade)
tableScheme.reloadSchemeSet(schemeSet, inTableView: tableView, withRowAnimation: .Fade)

Note: You can omit the withRowAnimation parameter to use the .Automatic row animation.

All of these calls will update the table immediately. If you'd like to batch the operations you would perform operations using a proxy object passed in by the TableScheme object in the batch call provided by the table scheme, like so:

tableScheme.batchSchemeVisibilityChangesInTableView(tableView) { animator in 
	// Changing visibility
	animator.hideScheme(self.scheme, inTableView: self.tableView, withRowAnimation: .Fade)
	animator.showScheme(self.scheme, inTableView: self.tableView, withRowAnimation: .Fade)
	animator.hideSchemeSet(self.schemeSet, inTableView: self.tableView, withRowAnimation: .Fade)
	animator.showSchemeSet(self.schemeSet, inTableView: self.tableView, withRowAnimation: .Fade)

	// Reloading
	animator.reloadScheme(self.scheme, inTableView: self.tableView, withRowAnimation: .Fade)
	animator.reloadSchemeSet(self.schemeSet, inTableView: self.tableView, withRowAnimation: .Fade)
}

Please note it is an error to call the tableScheme animation methods inside of the batch block. You should use the animator methods, as they defer the operations until after the block is executed.

Changing a Scheme after building

In some cases you may want to alter a scheme after it has been built. This is particularly true for the builtin ArrayScheme, where the data tends to be more dynamic. To faciliate changing a scheme, more particularly the rows the scheme represents, there are two methods on the TableScheme class.

The first is the lower-level, explicitly-defined animation method animateChangesToScheme(scheme: Scheme, inTableView tableView: UITableView, withChangeHandler changeHandler: (animator: SchemeRowAnimator) -> Void) . This method provides you a SchemeRowAnimator class, which is where you send animation instructions. Here's an example of using this method (you can see it in use in the sample project):

// self.toggledArrayScheme.objects was configured to be [1,2,3,4,5,6,7,8,9] at build time

if !self.toggledArrayToggled {
    self.tableScheme.animateChangesToScheme(self.toggledArrayScheme, inTableView: self.tableView) { animator in
        animator.deleteObjectAtIndex(1, withRowAnimation: .Left)
        animator.deleteObjectAtIndex(3, withRowAnimation: .Right)
        animator.deleteObjectAtIndex(5, withRowAnimation: .Left)
        animator.deleteObjectAtIndex(7, withRowAnimation: .Right)
        animator.deleteObjectAtIndex(8, withRowAnimation: .Top)
        animator.moveObjectAtIndex(4, toIndex: 3)
        animator.moveObjectAtIndex(6, toIndex: 2)
        animator.insertObjectAtIndex(4, withRowAnimation: .Top)
        self.toggledArrayScheme.objects = [1,3,7,5,11]
    }
} else {
    self.tableScheme.animateChangesToScheme(self.toggledArrayScheme, inTableView: self.tableView) { animator in
        animator.insertObjectAtIndex(1, withRowAnimation: .Left)
        animator.insertObjectAtIndex(3, withRowAnimation: .Right)
        animator.insertObjectAtIndex(5, withRowAnimation: .Left)
        animator.insertObjectAtIndex(7, withRowAnimation: .Right)
        animator.insertObjectAtIndex(8, withRowAnimation: .Top)
        animator.moveObjectAtIndex(2, toIndex: 6)
        animator.moveObjectAtIndex(3, toIndex: 4)
        animator.deleteObjectAtIndex(4, withRowAnimation: .Top)
        self.toggledArrayScheme.objects = [1,2,3,4,5,6,7,8,9]
    }
}

self.toggledArrayToggled = !self.toggledArrayToggled

When you create your animations you only care about how you are moving items around inside your scheme. You don't need to worry about the state of schemes above the one you are altering. This leads to really simple row animation logic. Don't forget to also update your data in this method or the inserted rows won't pick up on their correct data!

However, if you don't care about giving explicit animations to each row, you can use another method on TableScheme that will take a snapshot of how the rows look before a block, run a block where changes occur, and then infer the changes and create the animations for you: animateChangesToScheme<T: Scheme where T: InferrableRowAnimatableScheme>(scheme: T, inTableView tableView: UITableView, withAnimation animation: UITableViewRowAnimation = .Automatic, withChangeHandler changeHandler: () -> Void).

With this method you pass in a row animation you want, and make your changes to the scheme in the changeHandler. The one requirement of it is that your scheme conform to InferrableRowAnimatableScheme, which allows the method to determine an array of identifiers for your scheme. That method should return one identifier per existing row. Here's an example of using this method (you can see something similar in the sample project):

self.tableScheme.animateChangesToScheme(self.arrayScheme, inTableView: self.tableView, withAnimation: .Fade) {
    self.arrayScheme.objects = ["New", "Array", "Of", "Objects"]
}

Check out the Built-in Schemes page for details about each Scheme provided with TableSchemer.