
This video is only available to subscribers. Start a subscription today to get access to this and 477 other videos.
More TableView Customization
Episode Links
Setting up the Table View
This table view will be a mixture of static & dynamic content, so we'll set it all up in code. To make things a little easier, we'll use a Swift enum to represent our different sections.
enum Sections : Int {
case Details = 0
case BuildOrder
case Actions
case Count
var title: String? {
switch self {
case .Details: return "Guide Details"
case .BuildOrder: return "Build Order"
default: return nil
}
}
}
Here you can see that we have a simple Int
enum that shows our sections in the right order. The Count
case is there as a neat trick to sum the number of items above it. We also use a title
property that will return a title for a given section.
With this in place our table view data source methods are a little cleaner:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return Sections.Count.rawValue
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let s = Sections(rawValue: section) else { return 0 }
switch s {
case .Details: return 2
case .BuildOrder: return guide.buildOrder.count
case .Actions: return 1
default: return 0
}
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return Sections(rawValue: section)?.title
}
TextField Cell
To capture the name of the guide, we'll use a TextFieldCell
, which is a simple UITableViewCell
subclass that adds a textField to the right side. We won't keep a reference to the text field, instead we'll just read the value off of the model and update the model when the text field changes.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch (indexPath.section, indexPath.row) {
case (Sections.Details.rawValue, 0):
let cell = TextFieldCell()
cell.backgroundColor = Theme.Colors.Foreground.color
cell.label.textColor = Theme.Colors.LightTextColor.color
cell.label.text = "Name"
cell.textField.delegate = self
cell.textField.textColor = Theme.Colors.LightTextColor.color
cell.textField.text = guide.name
return cell
// code omitted
}
}
// MARK: UITextFieldDelegate
func textFieldDidEndEditing(textField: UITextField) {
guide.name = textField.text
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
Selecting a Race
This one will use a standard cell with the .Value1
style. We'll use a disclosure indicator to indicate that we'll select a value from another view controller. When the user taps on it, we'll present the view controller and handle the selection with a simple callback block.
// cellForRowAtIndexPath:
case (Sections.Details.rawValue, 1):
let cell = UITableViewCell(style: .Value1, reuseIdentifier: nil)
cell.backgroundColor = Theme.Colors.Foreground.color
cell.textLabel?.text = "Race"
cell.textLabel?.textColor = Theme.Colors.LightTextColor.color
cell.textLabel?.font = UIFont.boldSystemFontOfSize(16)
cell.detailTextLabel?.text = guide.race?.rawValue
cell.detailTextLabel?.textColor = Theme.Colors.LightTextColor.color
cell.accessoryType = .DisclosureIndicator
return cell
// snip
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
switch (indexPath.section, indexPath.row) {
case (Sections.Details.rawValue, 1):
let raceVC = storyboard!.instantiateViewControllerWithIdentifier("SelectRaceViewController") as! SelectRaceViewController
raceVC.raceSelectionBlock = { race in
self.guide.race = race
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
self.navigationController?.popViewControllerAnimated(true)
}
self.navigationController?.pushViewController(raceVC, animated: true)
default: break
}
Selecting a Unit
For the build order section we'll append units as the user selects them.
case (Sections.BuildOrder.rawValue, let i):
let unit = guide.buildOrder[i]
let cell = tableView.dequeueReusableCellWithIdentifier(UnitCell.reuseIdentifier, forIndexPath: indexPath) as! UnitCell
cell.selectionStyle = .None
if let imgName = unit.imageName, image = UIImage(named: imgName) {
cell.imageView?.image = image
} else {
cell.imageView?.image = nil
}
cell.textLabel?.text = unit.name
return cell
Here we are using reusable cells since there will be many of these. When you tap on "Add a Unit" we'll call selectUnit()
:
func selectUnit() {
if let race = guide.race {
let unitVC = storyboard!.instantiateViewControllerWithIdentifier("SelectUnitViewController") as! SelectUnitViewController
unitVC.units = units[race]!
unitVC.unitSelectionBlock = { unit in
self.guide.buildOrder.append(unit)
let rowIndex = self.guide.buildOrder.count - 1
let indexPath = NSIndexPath(forRow: rowIndex, inSection: 1)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
self.navigationController?.popViewControllerAnimated(true)
}
self.navigationController?.pushViewController(unitVC, animated: true)
} else {
let alert = UIAlertController(title: "Race not selected", message: "Please select a race first", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
presentViewController(alert, animated: true, completion: nil)
}
}
Supporting Reorderable Cells just for the Build Order Section
First we need to tell the table view that it is in edit mode. We also need to allow selection during editing so the other table view cells still function:
// viewDidLoad
tableView.editing = true
tableView.allowsSelectionDuringEditing = true
Then we need to tell the table view that we only want editing / reordering for the build order section:
override func tableView(tableView: UITableView,
canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return indexPath.section == Sections.BuildOrder.rawValue
}
override func tableView(tableView: UITableView,
canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return indexPath.section == Sections.BuildOrder.rawValue
}
override func tableView(tableView: UITableView,
moveRowAtIndexPath sourceIndexPath: NSIndexPath,
toIndexPath destinationIndexPath: NSIndexPath) {
let unit = guide.buildOrder.removeAtIndex(sourceIndexPath.row)
guide.buildOrder.insert(unit, atIndex: destinationIndexPath.row)
}
This works, but it allows us to reorder a unit cell outside of its section! To fix this we need to implement 1 more delegate method:
override func tableView(tableView: UITableView,
targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath,
toProposedIndexPath proposedDestinationIndexPath: NSIndexPath) -> NSIndexPath {
if proposedDestinationIndexPath.section != sourceIndexPath.section {
if proposedDestinationIndexPath.section < sourceIndexPath.section {
return NSIndexPath(forRow: 0, inSection: sourceIndexPath.section)
} else {
let lastRow = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1
return NSIndexPath(forRow: lastRow, inSection: sourceIndexPath.section)
}
}
return proposedDestinationIndexPath
}
With this in place we're always using an indexPath in the build order section, so it works as you would expect.
Credits
Icons and unit data were obtained from the Starcraft Wikia.