Search code examples
iosuitableviewswiftios8checkmark

UITableView-Swift: Checkmark in UITableViewCell


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.


Solution

  • 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
        }
    }