3

I'm using Xcode 6.0.1, iOS8 and Swift. I have to reproduce in my UITableView the same behavior of the Auto-Lock option in Settings->General. In Objective-C, in the past, I wrote

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CheckMarkCellIdentifier = @"CheckMarkCellIdentifier";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CheckMarkCellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CheckMarkCellIdentifier] autorelease];
    }
    cell.textLabel.text = ...
    cell.detailTextLabel.text = ...;
    cell.accessoryType = [indexPath isEqual: self.lastSelectedIndexPath] ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;  
    return cell;

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    int newRow = indexPath.row;
    int oldRow  = self.lastSelectedIndexPath.row;
    if (newRow != oldRow) {
        UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
        newCell.accessoryType = UITableViewCellAccessoryCheckmark;
        UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:self.lastSelectedIndexPath];
        oldCell.accessoryType = UITableViewCellAccessoryNone;
        self.lastSelectedIndexPath = indexPath;
    }
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

This works great! Now, in Swift, I wrote:

override func viewDidLoad() {
    super.viewDidLoad()
    ...
    self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "categoryCell")
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("categoryCell", forIndexPath: indexPath) as UITableViewCell
    cell.textLabel?.text = categories[indexPath.row]
    let row = indexPath.row;
    if let lastIndexPath = self.lastSelectedIndexPath {
        cell.accessoryType = (lastIndexPath.row == row) ? UITableViewCellAccessoryType.Checkmark : UITableViewCellAccessoryType.None;
    }
    return cell
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    self.tableView.deselectRowAtIndexPath(indexPath, animated: true)

    let newRow = indexPath.row;
    var oldRow: Int?
    if let lastIndexPath = self.lastSelectedIndexPath {
        oldRow = lastIndexPath.row;
        let oldCell = self.tableView(tableView, cellForRowAtIndexPath: lastIndexPath)
        oldCell.accessoryType = UITableViewCellAccessoryType.None;
    }
    if (newRow != oldRow) {
        let newCell = self.tableView(tableView, cellForRowAtIndexPath: indexPath)
        newCell.accessoryType = UITableViewCellAccessoryType.Checkmark;
        self.lastSelectedIndexPath = indexPath;
    }
}

And this doesn't work. The checkmark appears only sometimes and it disappears when I scroll. I spent two hours to figure out why but without success.

Giorgio
  • 1,973
  • 4
  • 36
  • 51

1 Answers1

11

The main issue is that you call tableView(_:cellForRowAtIndexPath:) instead of tableView.cellForRowAtIndexPath(_:) and thus create a new cell instead of getting the one currently displayed.

Here's some cleanup:

override func viewDidLoad() {
    super.viewDidLoad()

    // …

    tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "categoryCell")
}


func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("categoryCell", forIndexPath: indexPath) as UITableViewCell
    cell.accessoryType = (lastSelectedIndexPath?.row == indexPath.row) ? .Checkmark : .None
    cell.textLabel?.text = categories[indexPath.row]

    return cell
}


func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    tableView.deselectRowAtIndexPath(indexPath, animated: true)

    if indexPath.row != lastSelectedIndexPath?.row {
        if let lastSelectedIndexPath = lastSelectedIndexPath {
            let oldCell = tableView.cellForRowAtIndexPath(lastSelectedIndexPath)
            oldCell?.accessoryType = .None
        }

        let newCell = tableView.cellForRowAtIndexPath(indexPath)
        newCell?.accessoryType = .Checkmark

        lastSelectedIndexPath = indexPath
    }
}
fluidsonic
  • 4,655
  • 2
  • 24
  • 34
  • I used optional binding 'cause self.lastSelectedIndexPath can be nil – Giorgio Oct 07 '14 at 14:50
  • Yep, that's why there's a `?` in `lastSelectedIndexPath?.row == row` which should take care of that :) – fluidsonic Oct 07 '14 at 14:52
  • So the two code snippets are equivalent? I tried to use your code fragment but the result is the same. – Giorgio Oct 07 '14 at 14:57
  • No, in your case there is a situation where `cell.accessoryType` is never set in `tableView(_:cellForRowAtIndexPath:)` which can cause weird behavior due to cell re-use. -- What is the desired behavior? Tapping on a cell which is already checked unchecks it again? Or does it keep the checkmark? – fluidsonic Oct 07 '14 at 14:59
  • The same as in Auto-Lock option in Settings->General. When I tap on a cell which is already checked, it keep the checkmark. I think the problem is in some change in iOS8. I tap on a cell, `newCell.accessoryType = UITableViewCellAccessoryType.Checkmark` is called, but checkmark doesn't appear. – Giorgio Oct 07 '14 at 15:07
  • 1
    And where is lastSelectedIndexPath supposed to be declared? – Josh Jun 16 '15 at 14:43
  • In the view controller – fluidsonic Jun 16 '15 at 15:27