Swift - Creating Custom Controls - Expanding TableViews

If everyone was content with what was available, we would have a very monochromatic world. Everything would look and feel the same, all would drive the same cars in the same colors. There word customization would not exists, because everyone would be happy with just that. Something like out of the Eiffel 65's Music Video - Blue or the Miranda Ad with the Orange men. Most importantly, there would be hardly any need for developers. Luckily, people want customization and we have the need for custom controls to match those little customizations on top of the standard controls

Customization

There are two ways to go about this, One being creating an entirely new revolutionary (at least in our minds that's what it looks like on the design board) UI/custom control. The second being adding customization functionality to an existing control. In this article we shall look at the latter and look at customizing the standard UITableView

The spec

Normal UITableViews display data like in a book, one item per line. This is fine if we need to group, we can use the grouping functionality to break them visually into their little groups. However you might have the need to create something that can (like HTML UI's) expand and collapse (using the little arrow) to display or hide the elements.

The Planning

This might sound like a very complex and involving task. Over the years I have been creating custom controls (for use in my own applications and some publicly available components) using various languages and technologies. The one thing that I learned is you can use a 3rd party control but it could turn out to be the weak link in your application. That is the only piece of code that you would have no control over. If it breaks or fails, so does your app. So, the most important things first is to map what you are after and how much effort would it take. This article stemmed from a discussion I had with a developer last week felt that an Expandable TableView was too much of an effort and was difficult.

The Execution

Always love a challenge, and this control was created in a matter of 15 minutes using Swift walking him through the entire process with all the little bells and whistles to allow for customization.

[Image of Expected TableView]

What we want to build would look like the image above. When a user taps on the little icon (the plus or the minus button), the row would expand or contract accordingly. The look of the Group Headers can be customized, there are no footers (for the moment in this solution).

The idea

If you love a challenge, and you want to try this out first yourself to see how you go creating this (everyone has their own way to create solutions, this proposed one is mine). If you want to simply read on how to achieve this, read on.

We shall create a plain tableView, i.e. the sections are not grouped (visually) via the IB setting. If the rows were rendered, they would all render as a single list. However each section has rows from 0..numberOfItems in that section. This is what we shall take advantage of, we shall use the first row as our header.

The next thing that we need to do is the little icon on the side that displays the + or the - (indicating the state of the group, expanded or collapsed). These can be the AccessoryTypes on the cell, so rather than display the > arrow or the DetailDisclosureButton, we can replace this with an ImageView which can have the image.

To hold the data for the group, we need a structure (holding the minimal data as)
struct RowData {
  var title: String?
}

struct SectionData {
  var title: String?         // The title for the group
  var expanded: Bool = true  // Indicates if the group is expanded or not
  var rows:[RowData] = []    // We are using a RowData Structure so that we can add more fields later

  func addRow(aRow:RowData) {
    rows.append(aRow)
  }
}

With this, we can very simply create a UITableViewDataSource that contains these.
 class MyDataSource: NSObject, UITableViewDataSource, UITableViewDelegate {

  var theData:[SectionData] = []    // holds all of the data for the tableView

  func numberOfSections() -> Int {
    return theData.count
  }

  func tableView() -> Int {
    return sections[section].rows.count
  }

  func tableView() -> UITableViewCell {
    return UItableViewCell()
  }

  func addSection(aSection:SectionData) {
    theData.append(aSection)
  }

  func createSection(title:String, expanded:Bool = true) -> SectionData {
    let section = SectionData(title:title, expanded:expanded, rows:[])
    return section
  }
}

and we can now create our data source simply via

let mySource = MyDataSource()

var section = mySource.createSection("One")
 section.addRow(RowData("Eins"))
 section.addRow(RowData("Uno"))
 section.addRow(RowData("Une"))
 section.addRow(RowData("Ek"))
 section.addRow(RowData("Wahad"))
mySource.addSection(section)

section = mySource.createSection("Two")
 section.addRow(RowData("Zwei"))
 section.addRow(RowData("Do"))
mySource.addSection(section)

section = mySource.createSection("Three")
 section.addRow(RowData("Drei"))
 section.addRow("Trois")
 section.addRow(RowData("Teen"))
mySource.addSection(section)

This data is internal to the dataSource and we can simply assign this to the tableView (if you have connected the TableView to the UIViewController in the IB as an IBOutlet called tableView)

 tableView.dataSource = mySource
 tableView.delegate = mySource 


Update so far

What we have done so far is only create the structure and a self contained UITableViewSource (Model + Controller) that will drive the tableView (View).

You would see nothing on the screen as the function that gets the cell for the row is doing nothing

Part II

In the next part, we shall look at displaying the data, customizing the header cell and expanding/contracting the group.

Comments

Popular Posts