UITableview: How to Disable Selection for Some Rows but Not Others

Viewed 239137

I am displaying in a group tableview contents parsed from XML. I want to disable the click event on it (I should not be able to click it at all) The table contains two groups. I want to disable selection for the first group only but not the second group. Clicking the first row of second group navigates to my tube player view.

How can I make just specific groups or rows selectable?

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
{
    if(indexPath.section!=0)
    if(indexPath.row==0)    

    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:tubeUrl]];   
}

Thanks.

23 Answers

You just have to put this code into cellForRowAtIndexPath

To disable the cell's selection property: (while tapping the cell)

cell.selectionStyle = UITableViewCellSelectionStyleNone;

To enable being able to select (tap) the cell: (tapping the cell)

// Default style
cell.selectionStyle = UITableViewCellSelectionStyleBlue;

// Gray style
cell.selectionStyle = UITableViewCellSelectionStyleGray;

Note that a cell with selectionStyle = UITableViewCellSelectionStyleNone; will still cause the UI to call didSelectRowAtIndexPath when touched by the user. To avoid this, do as suggested below and set.

cell.userInteractionEnabled = NO;

instead. Also note you may want to set cell.textLabel.enabled = NO; to gray out the item.

If you want to make a row (or subset of rows) non-selectable, implement the UITableViewDelegate method -tableView:willSelectRowAtIndexPath: (also mentioned by TechZen). If the indexPath should be not be selectable, return nil, otherwise return the indexPath. To get the default selection behavior, you just return the indexPath passed to your delegate method, but you can also alter the row selection by returning a different indexPath.

example:

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // rows in section 0 should not be selectable
    if ( indexPath.section == 0 ) return nil;

    // first 3 rows in any section should not be selectable
    if ( indexPath.row <= 2 ) return nil;

    // By default, allow row to be selected
    return indexPath;
}

You trap selections with these data source methods.

– tableView:willSelectRowAtIndexPath: 
– tableView:didSelectRowAtIndexPath: 
– tableView:willDeselectRowAtIndexPath: 
– tableView:didDeselectRowAtIndexPath:

In these methods, you check if the selected row is one you want to be selectable. If it is, take an action, if not, do nothing.

Unfortunately, you can't turn off selection for just one section. It's the whole table or nothing.

You can however set the table cells selectionStyle property to UITableViewCellSelectionStyleNone. I believe that will make the selection invisible. Combined with the above methods that should make the cells look completely inert from the user's perspective.

Edit01:

If you have a table in which only some of the rows are selectable it is important that the cells of the selectable rows be visually distinct from the non-selectable rows. The chevron accessory button is the default way to do this.

However you do it, you don't want your users trying to select rows and thinking the app has malfed because the row doesn't do anything.

For swift 4.0 This will do the trick. It will disable the Cell in didSelectRowAtIndexPath method but keep the subviews clickable.

func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
         if (indexPath.row == clickableIndex ) { 
            return indexPath
         }else{
            return nil
        }
    }

Use this to make the cell look like it is disabled and non-selectable:

cell.selectionStyle = UITableViewCellSelectionStyleNone;

Important: note that this is only a styling property, and does not actually disable the cell. In order to do that, you have to check for selectionStylein your didSelectRowAtIndexPath: delegate implementation:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    if(cell.selectionStyle == UITableViewCellSelectionStyleNone) {
        return;
    }

    // do your cell selection handling here
}

I agree with Bryan's answer.

If I do cell.isUserInteractionEnabled = false then the subviews within the cell won't be user interacted.

On the other hand, setting cell.selectionStyle = .none will trigger the didSelect method despite not updating the selection color.

Using willSelectRowAt is the way I solved my problem. Example:

func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
    switch (indexPath.section, indexPath.row) {
    case (0, 0), (1, 0): return nil
    default: return indexPath
    }
}

appart from tableView.allowsMultipleSelection = true you will need to deselect the rest of the cells of the section when selecting:

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if indexPath.section does not support multiple selection {
        // deselect the rest of cells
        tableView
            .indexPathsForSelectedRows?.compactMap { $0 }
            .filter { $0.section == indexPath.section && $0.row != indexPath.row }
            .forEach { tableView.deselectRow(at: $0, animated: true) }
    }
}

Swift 5: Place the following line inside your cellForRowAt function:

cell.selectionStyle = UITableViewCell.SelectionStyle.none
Related