UITableView divided into sections

Each section can have a header. The header for our last section starts with a "\n" (newline character) for extra vertical space. Each section can also have a footer.

As usual, it looked better in iOS 6:

Source code in Section.zip

  1. Class AppDelegate
  2. Class TableViewController

Data source and delegate

Our TableViewController acts as the data source for the table view. The TableViewController must therefore have the following methods. We have already seen the first three in States.

  1. numberOfSectionsInTableView(_:) returns 5 in this app
  2. tableView(_:numberOfRowsInSection:)
  3. tableView(_:cellForRowAtIndexPath:)
  4. tableView(_:titleForHeaderInSection:) and tableView(_:titleForFooterInSection:). These two methods are optional; see exercise 1. If you want a picture instead of a line of text, see tableView(_:viewForHeaderInSection:) and tableView(_:viewForFooterInSection:).

Our TableViewController also acts as the delegate for the table view. The TableViewController must therefore have the following method.

  1. tableView(_:didSelectRowAtIndexPath:). This method is optional. But if we didn’t have it, the tabe view would do nothing when its cells were touched.

Things to try

  1. Give each section a footer displaying the time zone’s offset from Greenwich: “UTC −5:00” for Eastern Standard Time, “UTC −6:00” for Central Standard Time. Give the data source a property named footers, just like headers, and a method named tableView(_:titleForFooterInSection:), just like tableView(_:titleForHeaderInSection:). Unfortunately, each footer looks like it’s part of the following header. Perhaps the moral is that we should not have both headers and footers.


  2. Make the headers and footers float by changing the table view’s style from UITableViewStyle.Grouped to UITableViewStyle.Plain in the application(_:didFinishLaunchingWithOptions:) method of the application delegate. Launch the app and scroll the table view up and down. It looks worse, doesn’t it?


  3. Add an index along the right edge of the window. Add the following two methods to the data source.
    	override func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {
    		return ["EST", "CST", "MST", "PST", "Misc."]; //Return an array of Strings.
    	}
    
    	override func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String,
    		atIndex index: Int) -> Int {
    		//one-to-one correspondence between sections and section index titles
    		return index;
    	}
    

  4. [Advanced]. I’m sorry we have miscellaneous strings scattered throughout the methods tableView(_:titleForHeaderInSection:), tableView(_:titleForFooterInSection:), and sectionIndexTitlesForTableView(_:). Let’s concentrate all the strings in one big array. This is called programming.

    Change zones (the array of five arrays) to an array of five tuples. Each tuple will contain four values. The first value will be the header for the section. The second value will be the footer for the section. The third value will be the index word along the right edge of the window. The fourth value will be the array of state names.

    	let zones: [(header: String, footer: String, index: String, states: [String])] = [
    		(
    			"EST: Eastern Standard Time", //header
    			"UTC -5:00",                  //footer
    			"EST",                        //index
    			[
    				"Alabama",
    				"Connecticut",
    				"Delaware",
    				"Florida",
    				"Georgia",
    				"Indiana",
    				"Kentucky",
    				"Maine",
    				"Maryland",
    				"Massachusetts",
    				"Michigan",
    				"New Hampshire",
    				"New Jersey",
    				"New York",
    				"North Carolina",
    				"Ohio",
    				"Pennsylvania",
    				"Rhode Island",
    				"South Carolina",
    				"Tennessee",
    				"Vermont",
    				"Virginia",
    				"West Virginia"
    			]
    		),
    
    		(
    			"CST: Central Standard Time", //header
    			"UTC -6:00",                  //footer
    			"CST",                        //index
    			[
    				"Arkansas",
    				"Illinois",
    				"Iowa",
    				"Kansas",
    				"Louisiana",
    				"Minnesota",
    				"Mississippi",
    				"Missouri",
    				"Nebraska",
    				"North Dakota",
    				"Oklahoma",
    				"South Dakota",
    				"Texas",
    				"Wisconsin"
    			]
    		),
    
    		(
    			"MST: Mountain Standard Time", //header
    			"UTC -7:00",                  //footer
    			"MST",                        //index
    			[
    				"Arizona",
    				"Colorado",
    				"Idaho",
    				"Montana",
    				"New Mexico",
    				"Utah",
    				"Wyoming"
    			]
    		),
    
    		(
    			"PST: Pacific Standard Time", //header
    			"UTC -8:00",                  //footer
    			"PST",                        //index
    			[
    				"California",
    				"Nevada",
    				"Oregon",
    				"Washington"
    			]
    		),
    
    		(
    			"Miscellaneous",              //header
    			"Miscellaneous",              //footer
    			"Misc.",                      //index
    			[
    				"Alaska",
    				"Hawaii"
    			]
    		),
    	];
    
    		//in tableView(_:numberOfRowsInSection:)
    		return zones[section].states.count;
    
    		//in tableView(_:cellForRowAtIndexPath:)
    
    		assert(0 <= indexPath.section && indexPath.section < zones.count
    			&& 0 <= indexPath.row && indexPath.row < zones[indexPath.section].states.count);
    
    		cell.textLabel!.text = zones[indexPath.section].states[indexPath.row];
    
    		//in sectionIndexTitlesForTableView
    
    		var indices: [String] = [String]();	//an empty array of Strings
    		for zone in zones {
    			indices.append(zone.index);
    		}
    		return indices;
    
    		//a shorter way to do the above
    		//in sectionIndexTitlesForTableView
    
    		return zones.map({$0.index});
    
    Even better, put the above array of dictionaries in a separate object. The class of the object should be named Model.