Swifty UITableViewController with generics and structs

Disclaimer - This post along with code supporting is inspired by Chris Eidhof's talk at trySwift. I took the liberty to slightly update the code as per my own logic. There is no difference from the original source as long as user facing functionality is concerned.

This code is open sourced at my Github Repository

First off, we will create a generic UITableViewController which will take list of generic items.


final class ViewController: UITableViewController {
let config: (UITableViewCell, Item) -> ()
    
    init(items: [Item], config: (UITableViewCell, Item) -> (), style: UITableViewStyle, editable: Bool) {
        self.items = items
        self.config = config
        undoOp = JKUndoOperation(items)
        super.init(style: style)
        
        if editable {
            self.navigationItem.leftBarButtonItem = items.count > 0 ? UIBarButtonItem(barButtonSystemItem: .Edit, target: self, action: #selector(self.deleteItem)) : nil
        }
    }
}

Note the generic annotation <Item>. It mean item can be of any generic type. Since client will be managing initialized block, viewcontroller is completely isolated from that responsibility.

For example, let's create an object of type Person and initialize our UITableViewController with items of type Person

struct Person {
    let firstName: String
    let lastName: String
}

let items: [Person] = [Person(firstName: "Jayesh", lastName: "kawli"), Person(firstName: "Chitali", lastName: "Mitre"), Person(firstName: "Kitali", lastName: "Ladendorf"), Person(firstName: "Tom", lastName: "Bushtosh")]
let vc: UIViewController = ViewController(items: items, config: { (cell, item) in
                cell.textLabel?.text = item.firstName
                cell.detailTextLabel?.text = item.lastName
                }, style: .Plain, editable: true)

If you don't like custom objects, you can even initialize our UITableViewController with the collection of String

let items: [String] = ["Jayesh kawli", "Chitali Mitre", "Kitali Ladendorf", "Tom Bushtosh"]
let vc: UIViewController = ViewController(items: items, config: { (cell, item) in
                cell.textLabel?.text = item
                }, style: .Plain, editable: true)

**Deleting items from the list**

Please note that we are also passing an editable parameters which indicates whether user can edit tableView content. We create a barButtonItem based on the number of items for tableView. UIBarButtonItem changes every time items are updated


var items: [Item] {
        didSet {
            tableView.reloadData()
            self.navigationItem.leftBarButtonItem = items.count > 0 ? UIBarButtonItem(barButtonSystemItem: .Edit, target: self, action: #selector(self.deleteItem)) : nil
         }
    }

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        guard editingStyle == .Delete else { return }
        items.removeAtIndex(indexPath.row)
    }
    
    func deleteItem() {
        UIView.animateWithDuration(0.25) {
            self.tableView.editing = !self.tableView.editing
        }
    }

Undo Operation


struct JKUndoOperation {
    let initialList: [Item]
    var updatedListOfUndoItems: [[Item]]
    var currentItem: [Item]
    
    init (_ items: [Item]) {
        initialList = items
        updatedListOfUndoItems = []
        currentItem = initialList
    }
    
    var undoOperationAllowed: Bool {
        return !updatedListOfUndoItems.isEmpty
    }
    
    mutating func addToListOfUndoItems(item: [Item]) {
        updatedListOfUndoItems.append(item)
        currentItem = item
    }
    
    mutating func removeFromListOfUndoItems() {
        if let item = updatedListOfUndoItems.popLast() {
            currentItem = item
        } else {
            currentItem = initialList
        }
    }
 }

Also declare a property undoOp to keep track of ongoing undo functionality.

var undoOp: JKUndoOperation

In addition to delete functionality, we also provide a undo functionality. First off, we add undo button as a UIBarButtonItem as a rightBarButtonItem. Every time list if updated. We check the property undoOperationAllowed on JKUndoOperation object and display/hide undo button.

var items: [Item] {
        didSet {
            tableView.reloadData()
            self.navigationItem.rightBarButtonItem = undoOp.undoOperationAllowed ? UIBarButtonItem(barButtonSystemItem: .Undo, target: self, action: #selector(self.undoItem)) : nil
        }
    }

func undoItem() {
        undoOp.removeFromListOfUndoItems()
        items = undoOp.currentItem
    }

Every time user deletes any item, we store the state before deletion in our undo object.

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        guard editingStyle == .Delete else { return }
        undoOp.addToListOfUndoItems(items)
        items.removeAtIndex(indexPath.row)
    }

Let's summarize what we learned in this post,

  • How to use struct to create objects
  • Usage of generics
  • Usage of closure while initializing object
  • Delete operation on the tableView list
  • Undo operation

I hope this post makes some of the things clear for you. Do let me know your feedback if you find any problems with post/bugs with the code. You can either add your opinion in the comment section or say Hi to me on Twitter. (@jayeshkawli)

Again thanks to Chris Eidhof's talk at trySwift. Please refer to his original talk for more detailed explanation. This post is fully written in the spirit of this original talk.