317

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.

P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
Warrior
  • 39,156
  • 44
  • 139
  • 214

23 Answers23

517

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.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Pugalmuni
  • 9,350
  • 8
  • 56
  • 97
  • 1
    This method will generate bug if you try to swipe to delete. – Nat Mar 16 '12 at 14:04
  • 125
    This is hack! Do do that, use answer below - using willSelectRowAtIndexPath with returning nil! – Tankista Oct 22 '12 at 10:56
  • 5
    The userInteractionEnabled stuff is not correct, hook willSelectRowAtIndexPath instead as people note. – Jonny May 08 '13 at 11:01
  • 23
    On iOS 6.0 and later, `tableView:shouldHighlightRowAtIndexPath:` is a new `UITableViewDelegate` method that returns a `BOOL` based on whether or not the passed `indexPath` should be highlighted. If you're building for 6.0 and later, I strongly recommend this new API. – cbowns Jul 29 '13 at 18:17
  • 3
    if you're doing this in Swift and you're having trouble but ended up here, you'll need to set `cell!.selectionStyle = .None` which force unwraps the cell, allowing you to access its properties. – Marcos Jul 20 '14 at 06:51
  • One nice thing about this answer is that it brings up the case of "what do I do with the cell's UI?" If using a custom cell (as the code I am in is), I added this code in the next comment to set and RESET the tint color, alpha and cellForRowAtIndexPath. – Alex Zavatone Aug 19 '15 at 15:48
  • `if (someConditionIsMet) { // set icon to look as if it is disabled display_Img.image = [display_Img.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; display_Img.tintColor = [UIColor lightGrayColor]; display_Img.alpha = .5; // Make label look grayed out display_Lbl.alpha = .5; // Disables tap on cell, but not UI cell.userInteractionEnabled = NO; } else { set all those properties back to default }` – Alex Zavatone Aug 19 '15 at 15:49
  • `cell.selectionStyle = UITableViewCellSelectionStyleNone;` for Obj-C – Rafael Ruiz Muñoz Jun 11 '16 at 18:25
  • This is absolutely *NOT* the way to do this! Because cells are reused and the way this code was written, the properties are not reset to their defaults and cells may be unexpectedly (un-)selectable. Furthermore, this is just a visual trick but for the coder, this requires quite some workarounds to handle selection of 'unselectable' cells. Use `willSelectRowAtIndexPath` instead, it's way less code and guaranteed to always work. – Mark Jan 10 '20 at 09:02
395

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;
}
Rishil Patel
  • 1,977
  • 3
  • 14
  • 30
Brian Chapados
  • 4,916
  • 2
  • 17
  • 7
  • 2
    Small detail: you ment <= 2, not =< 2. Bad smiley! :) – Jonas Byström Jan 05 '11 at 21:44
  • 24
    I would also like to add that this is the correct answer, but you should also set the selectionStyle to UITableViewCellSelectionStyleNone in cellForRowAtIndexPath. Why? because without this selection style the cell cannot be selected but it will still display "as selected" when touched down (so it changes to the selected color, if there is one for the other cells) – TooManyEduardos Apr 22 '14 at 18:52
  • 4
    this should have more upvotes than the accepted answer – user1244109 Jul 14 '14 at 19:59
  • 2
    I think the main reason this isn't the accepted answer is because the accepted answer is easier. While it's not the 'best way' to do it because your table will still call setSelected: on your cells, it still works, visually. This answer here takes more work but is the proper way to do it (you must combine @TooManyEduardos suggestion as well) to avoid any unforeseen consequences of the table calling setSelected: on your cells. – Brian Sachetta Jan 08 '15 at 19:11
  • I like this answer, but if you don't want to duplicate your logic in cellForRowAtIndexPath and willSelectRowAtIndexPath, just check if cell.selectionStyle == none in willSelectRowAtIndexPath to return nil (see my answer below) – Eric D'Souza Apr 03 '15 at 20:48
  • @TooManyEduardos - This is by far the best answer. The selected answer actually doesn't fix whether the cell is *actually* selected (other than using the .userInteractionEnabled=NO hack), but rather determines whether or not the cell will *appear* selected by altering the selection color. When you actually want to intelligently determine whether or not a cell should literally be *selected*, this is the way to do it. – d2burke Nov 03 '15 at 04:23
  • This is the best answer but will need `cell.selectionStyle = UITableViewCellSelectionStyle.None` to avoid a grey flash on touch. – Morten Holmgaard Dec 30 '15 at 15:26
  • A note: this approach does not hide the table editing selection UI. (the indented one with a circular checkmark) Maybe that's a separate mechanism entirely, but it wasn't clear to me. – SimplGy Jan 09 '16 at 21:57
  • This method is great! And in order to remind the user this button have already been disabled, I have add an alter in the if statement, and the app looks pretty nice. – n4feng Mar 28 '17 at 20:44
  • This is the right answer, cell.selectionStyle does not do anything w.r.to cell selection. The didSelect method gets fired. Upvoted. – Satheesh Feb 20 '18 at 06:37
  • In swift 3, use func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { ... } – oOEric Mar 12 '18 at 07:25
69

Starting in iOS 6, you can use

-tableView:shouldHighlightRowAtIndexPath:

If you return NO, it disables both the selection highlighting and the storyboard triggered segues connected to that cell.

The method is called when a touch comes down on a row. Returning NO to that message halts the selection process and does not cause the currently selected row to lose its selected look while the touch is down.

UITableViewDelegate Protocol Reference

Benjohn
  • 13,228
  • 9
  • 65
  • 127
Keller
  • 17,051
  • 8
  • 55
  • 72
16

None from the answers above really addresses the issue correctly. The reason is that we want to disable selection of the cell but not necessarily of subviews inside the cell.

In my case I was presenting a UISwitch in the middle of the row and I wanted to disable selection for the rest of the row (which is empty) but not for the switch! The proper way of doing that is hence in the method

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

where a statement of the form

[cell setSelectionStyle:UITableViewCellSelectionStyleNone];

disables selection for the specific cell while at the same time allows the user to manipulate the switch and hence use the appropriate selector. This is not true if somebody disables user interaction through the

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

method which merely prepares the cell and does not allow interaction with the UISwitch.

Moreover, using the method

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

in order to deselect the cell with a statement of the form

[tableView deselectRowAtIndexPath:indexPath animated:NO];

still shows the row being selected while the user presses on the original contentView of the cell.

Just my two cents. I am pretty sure many will find this useful.

MightyMouse
  • 13,208
  • 8
  • 33
  • 43
  • Agree. And returning nil in `willSelectRowAtIndexPath` has the same issue, subviews are not selectable. – jeffjv Sep 14 '16 at 23:20
  • Also note that if you have a static table you can just set the Selection to None in the Interface Builder for the cells you don't want the selection to appear. – jeffjv Sep 14 '16 at 23:59
13

For Swift 4.0 for example:

cell.isUserInteractionEnabled = false
cell.contentView.alpha = 0.5
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • This is the approach I have historically used, but it no longer appears to work when cells are configured using content configuration vs. text and detail labels. – Greg Brown Apr 15 '22 at 12:53
  • Update - this approach does still work as long as the content view's alpha value is set *after* setting the cell's content configuration. – Greg Brown Apr 15 '22 at 13:42
12

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.

TechZen
  • 64,370
  • 15
  • 118
  • 145
  • please elaborate how to use these methods or give some sample program links – Warrior Feb 15 '10 at 18:39
  • 2
    @Warrior: If you have installed the SDK you can open up Xcode, go to `Help -> Developer documentation`, then copy those method names into the search field to see how it works. The Developer documentation also contains sample code. – kennytm Feb 15 '10 at 19:43
9

You need to do something like the following to disable cell selection within the cellForRowAtIndexPath method:

[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[cell setUserInteractionEnabled:NO];

To show the cell grayed out, put the following within the tableView:WillDisplayCell:forRowAtIndexPath method:

[cell setAlpha:0.5];

One method allows you to control the interactivity, the other allows you to control the UI appearance.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
azfar
  • 129
  • 1
  • 3
9

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
        }
    }
Nilesh
  • 438
  • 5
  • 15
  • 1
    Or for short: `return indexPath.row == clickableIndex ? indexPath : nil`, keeps your code a bit cleaner ;-) – Mark Jan 10 '20 at 09:00
8

For swift:

cell.selectionStyle = .None
cell.userInteractionEnabled = false
Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
  • This has been the best solution for me, except I leave isUserInteractiveEnabled. I created many reusable cells programmatically with different controls, i.e. a cell with a UISwitch, a UISlider, a UIPickerView etc. The cell itself cannot be touched and the accessory control remains user-interactive. No need to keep re-implementing this in the delegate. – MH175 Jul 29 '20 at 17:41
8

To stop just some cells being selected use:

cell.userInteractionEnabled = NO;

As well as preventing selection, this also stops tableView:didSelectRowAtIndexPath: being called for the cells that have it set.

JosephH
  • 37,173
  • 19
  • 130
  • 154
  • 1
    A weird thing happen all the time: when I set `userInteractionEnabled` to NO on one cell (more clearly, one indexPath), user interaction of some OTHER cells are disabled. I have no clue why this happen every time I use `userInteractionEnabled` on table view cells. Is it a known bug or I did something wrong? – Philip007 Jan 28 '13 at 20:01
  • 3
    @Philip007 Are the "NO" cells being reused? If so you may need to make sure you set it to "YES" when you dequeue a cell. – JosephH Jan 28 '13 at 23:03
  • 1
    Why set it to "YES"? I want it to to be NO (disallow user interaction).. A typical example is that I set user interaction of the first row (`if [indexPath row] == 0`) to `NO` in `tableView:cellForRowAtIndexPath:` after dequeueing the cell. It usually works OK at first. Then after several calls of `reloadData`, suddenly the fifth row is disallowed interaction, then the fourth row.. I can't figure out when it will happen. The whole thing looks random. – Philip007 Jan 29 '13 at 07:54
  • 8
    @Philip007 If you are reusing cells, you will need to reset it to "YES" for the cells you *DO* want to be interactive. Otherwise if a "NO" cell is reused in a row you do want to be interactive, it will still be disabled. ie: `if [indexPath row] == 0) { set to NO } else { set to YES }` – JosephH Jan 29 '13 at 08:30
5

You can use the tableView:willDisplayCell method to do all the kinds of customization to a tableViewCell.

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
     [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
     [cell setUserInteractionEnabled:NO];

     if (indexPath.section == 1 && indexPath.row == 0)
     {
         [cell setSelectionStyle:UITableViewCellSelectionStyleGray];
         [cell setUserInteractionEnabled:YES];
     }
} 

In this above code, the user can only select the first row in the second section of the tableView. The rest all rows can't be selected. Thanks!~

Abdul Rahman
  • 1,833
  • 1
  • 21
  • 21
5

My solution based on data model:

func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
    let rowDetails = viewModel.rowDetails(forIndexPath: indexPath)
    return rowDetails.enabled ? indexPath : nil
}

func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
    let rowDetails = viewModel.rowDetails(forIndexPath: indexPath)
    return rowDetails.enabled
}
SoftDesigner
  • 5,640
  • 3
  • 58
  • 47
1

I like Brian Chapados answer above. However, this means that you may have logic duplicated in both cellForRowAtIndexPath and then in willSelectRowAtIndexPath which can easily get out of sync. Instead of duplicating the logic, just check the selectionStyle:

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

    else
        return indexPath;
}
Eric D'Souza
  • 680
  • 5
  • 21
1

I found this handy as it works with both static and dynamic tables. I only set the disclosure indicator on those cells I want to allow selection.

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    if (cell.accessoryType != UITableViewCellAccessoryDisclosureIndicator) {
        return nil;
    }
    return indexPath;
}
Jeffery Jones
  • 75
  • 1
  • 5
1

on Xcode 7 without coding you can simply do the following:

In the outline view, select Table View Cell. (The cell is nested under Table View Controller Scene > Table View Controller > Table View. You may have to disclose those objects to see the table view cell)

In the Attributes inspector, find the field labeled Selection and select None. atribute inspector With this option, the cell won’t get a visual highlight when a user taps it.

Tung Fam
  • 7,899
  • 4
  • 56
  • 63
1

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
}
ySgPjx
  • 10,165
  • 7
  • 61
  • 78
1

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
    }
}
Peter
  • 29
  • 1
  • 5
Reimond Hill
  • 4,278
  • 40
  • 52
1

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) }
    }
}
rgkobashi
  • 2,551
  • 19
  • 25
1

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

cell.selectionStyle = UITableViewCell.SelectionStyle.none
Helton Malambane
  • 1,147
  • 11
  • 12
0

SIMPLE

Just use cell.userInteractionEnabled = YES; to the cell if it can navigate and cell.userInteractionEnabled = NO; otherwise

Rick Royd Aban
  • 904
  • 6
  • 33
  • 1
    This also prevents interaction with any accessory view within the cell, which may not be the desired behavior. – Greg Brown Aug 03 '15 at 17:33
0

Implement just the method tableView:willSelectRowAtIndexPath: in the data source for your table. If you want the row at the path to highlight, return the given indexPath. If you do not, return nil.

Example from my app:

- (NSIndexPath *)tableView:(UITableView *)tableView
    willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    MySelectableObj* obj = [self objectAtPath:indexPath];
    if(obj==nil) return nil;
    return indexPath;
}

The nice thing about this is that shouldPerformSegueWithIdentifier:sender: will not be called if the above method returns nil, although I repeat the test above just for completeness.

0

For Xcode 6.3:

 cell.selectionStyle = UITableViewCellSelectionStyle.None;
uplearned.com
  • 3,393
  • 5
  • 44
  • 59
-1

for swift 3.0 you can use below code to disable user interaction to UITableView cell

 cell.isUserInteractionEnabled = false