1

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:

  • As I create each cell, how do I determine which of my three types each cell is so I can display them based on type as well as dequeue each type properly
  • I created the func defaultIdentifier() -> String in each custom class AND specified the reuse identifiers in the Storyboard for each cell type. Do I need to do both?
  • do I need to register these cells or is that unnecessary because I'm using a storyboard?

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?

Jim
  • 1,260
  • 15
  • 37
  • Have a look at my answer to similar question here : http://stackoverflow.com/questions/38565089/multiple-cutsom-cells-in-uitableview/38565553#38565553. The answer is in objective c but that will give you an idea about how to achieve what you are looking for. You can easily implement it in Swift. – Vishal Sonawane Jul 28 '16 at 05:18
  • single cell is enough – Anbu.Karthik Jul 28 '16 at 05:19
  • I don't much about TableView But I think it will help you. select the tableViewController from storyboard and go to the attribute inspector and then increase number of prototype cells as much as you want and then customize the tableView cell according to your requirement. – Abdul Rehman Jul 28 '16 at 05:21
  • @VishalSonawane, I've studied your answer to the similar question which helped somewhat. I've done steps 1-3, although instead of a Xib, I created 3 cells (which I've now changed to static) using the storyboard in a single `UITableView`. I believe your step 4 is about using the cell identifier value to get an index value. Is the index value (indexForCell) the position of the cell in the view (e.g. the fourth cell down would have an indexForCell of 3), is that correct? Also, please take a look at the update I made above. Thanks. – Jim Jul 28 '16 at 20:55
  • cellForRowAtIndexPath returns tableViewCell and then it is shown in tableView. You are trying to access the cell which is not in tableView. The cell will be available only after execution of cellForRowAtIndexPath. So I think your this approach is wrong. And for index value , it is used to identify the cell among various cells in the same Xib. – Vishal Sonawane Jul 29 '16 at 05:26
  • Thanks for the tip, @VishalSonawane. I took a different approach and it kind of works, but doesn't really help me accomplish what I'm trying to accomplish. See Update 2 above for details. – Jim Jul 30 '16 at 05:26

1 Answers1

0

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.

Ilya V.
  • 106
  • 6