iosuitableviewcustom-controlsreuseidentifier

How do use different cell styles in a UITableView


I'm creating a view for accessing settings in an app which will be presented in a UITableView. Some cells will have a UITableViewCellAccessoryDisclosureIndicator, I need one with a UISwitch, and another with a UISegmentedControl sort of like this:

enter image description here

In other words, I think I need custom cells. Through a lot of research, I've gotten pretty close but I can't wire up everything I've learned to work together. Here's what I've done:

Created three UITableViewCell classes:

class DisclosureCell: UITableViewCell {
  override func awakeFromNib() {
    super.awakeFromNib()
  }

  override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
  }

  class func defaultIdentifier() -> String {
    return NSStringFromClass(self)
  }

}

class SegmentedControlCell: UITableViewCell {

  override func awakeFromNib() {
    super.awakeFromNib()
  }

  override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
  }

 class func defaultIdentifier() -> String {
    return NSStringFromClass(self)
  }

}

class SwitchCell: UITableViewCell {

  override func awakeFromNib() {
    super.awakeFromNib()
  }

  override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)

    // Configure the view for the selected state
  }

  class func defaultIdentifier() -> String {
    return NSStringFromClass(self)
  }

}

I have my Sections class and Section struct that will provide the table view sections and each choice available:

class SettingsData {

  func getSectionsFromData() -> [Section] {

  var settings = [Section]()

    let preferences = Section(title: "OPTIONS", objects: ["Formulas", "Lifts", "Units", "Allow 15 reps"])
    let patronFeatures = Section(title: "PATRON FEATURES", objects: ["Support OneRepMax"])
    let feedback = Section(title: "FEEDBACK", objects: ["Send Feedback", "Please Rate OneRepMax"])

    settings.append(preferences)
    settings.append(patronFeatures)
    settings.append(feedback)

    return settings
  }
}

struct Section {
  var sectionHeading: String
  var items: [String]

  init(title: String, objects: [String]) {
    sectionHeading = title
    items = objects

  }

}

Finally, my UITableViewController:

class SettingsViewController: UITableViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    self.navigationController?.navigationBar.topItem?.title = "Settings"

    navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: .Plain, target: self, action: #selector(self.dismissSettings(_:)))

  }

  func dismissSettings(sender: AnyObject?) {
    self.dismissViewControllerAnimated(true, completion: nil)

  }

  // MARK: - Table view data source

  var sections: [Section] = SettingsData().getSectionsFromData()

  override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return sections.count

  }

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return sections[section].items.count

  }

  override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return sections[section].sectionHeading
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("DisclosureCell", forIndexPath: indexPath)
    cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]

    return cell

  }

I think what I need help with is:

I've found a lot of threads about this subject but either they're in Objective-C and/or they don't speak to the particular parts I seem to be stuck on.

Thanks!

UPDATE:

I've updated my override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell as follows:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cellIdentifier = tableView.cellForRowAtIndexPath(indexPath)?.reuseIdentifier
    if cellIdentifier == "DisclosureCell" {
      let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "DisclosureCell")
      cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]

      return cell

    } else if cellIdentifier == "SegmentedControlCell"  {
      let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "SegmentedControlCell")
      cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]

      return cell
    }
      else {
          let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "SwitchCell")
          cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]

      return cell
      }
  }

I hope this is a step in the right direction but I'm still missing something because I get this error:

2016-07-28 07:15:35.894 One Rep Max[982:88043] Terminating app due to uncaught exception 'NSRangeException', reason: ' -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 0]’

And I need to figure out a way for each cell to know which of the three types it SHOULD be, then based on which type it IS, I can set it up in the code above.

UPDATE 2 I've now got it kind of working by using the indexPath.row:

enter image description here

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell", forIndexPath: indexPath) as! CustomTableViewCell
    if indexPath.row == 0 {
      cell.selectionStyle = .None
      cell.textLabel!.text = sections[indexPath.section].items[indexPath.row]
      cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
      return cell

    } else if indexPath.row == 1 {
      cell.selectionStyle = .None
      cell.textLabel!.text = sections[indexPath.section].items[indexPath.row]
      //cell.accessoryView = useAuthenticationSwitch
      let switchView: UISwitch = UISwitch()
      cell.accessoryView = switchView
      switchView.setOn(false, animated: false)
      return cell

    } else {
      cell.selectionStyle = .None
      cell.textLabel!.text = sections[indexPath.section].items[indexPath.row]
      let segmentedControl: UISegmentedControl = UISegmentedControl(items: ["lbs", "kg"])
      segmentedControl.selectedSegmentIndex = 0
      segmentedControl.addTarget(self, action: nil, forControlEvents: .ValueChanged)
      cell.accessoryView = segmentedControl
      return cell

    }
  }

The problem is that by using the indexPath.row, not all of the rows have the correct accessoryView. In the example above, I want the first two rows to have disclosureIndicators, the third one to have the UISegmentedControl, and the fourth to have a UISwitch. Now, I completely understand why my code produces this - it's because the accessoryView is being determined by the cell's position. Instead, I want to be able to tell each cell as it's being created, "You're the cell for this setting, so you get a [fill in the blank] accessoryView". I have custom cell classes created for each type and tried to do what I'm trying to do with those but couldn't figure it out. You may notice that in the code above I went to one custom cell class and reuse identifier.

I suppose I could do things like if indexPath.row == 0 | 1 { blah, blah } but that approach won't hold up through each section. If I try to deal with each section like this, then the code will get messier than it already is. There must be a smarter way.

Is there?


Solution

  • I'd suggest you to use static cells (not prototype) for such screens like settings. You can change type of cells in storyboard. After you can assign IBOutlets to all controls and make them interact with your model.