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.