Swipe to delete cell causes tableViewHeader to move with cell - ios

I have encountered a strange bug with my tableViewHeader on my UITableView in iOS 8. When swiping on a cell to reveal the delete button (standard iOS swipe-to-delete), it moves the tableViewHeader along with the cell that is being swiped. As I swipe the cell, the header moves in the same way that the cell being swiped does. No other cells in the table view are moved, only the header and whatever cell is being swiped. I have tested this on iOS 7 haven't encountered the problem. To me, this seems like a bug with tableViewHeader in iOS 8, being that it only occurs in this version and seems like something that should never occur. I see no reason for the header to ever be included in swipe-to-delete.
Below is just a mockup. Swipe-to-delete within the app is default iOS, nothing custom.

Building on ferris's answer, I found the easiest way when using a UITableViewCell as a section header is to return the contentView of the cell in viewForHeaderInSection. The code is as follows:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell : cellSectionHeader = tableView.dequeueReusableCellWithIdentifier("SectionHeader") as cellSectionHeader
return cell.contentView
//cellSectionHeader is my subclassed UITableViewCell
}

This was caused because I was using a UITableViewCell as the header for the table. To solve the swiping issue, instead of using tableView.tableHeaderView = cell, I use the following:
UIView *cellView = [[UIView alloc] init];
[cellView addSubview:cell];
tableView.tableHeaderView = cellView
I don't know why this solves the problem, especially being that it worked on iOS 7, but it seems to solve the problem.
Make sure to add all view to the cells view, as supposed to the cells contentView, otherwise the buttons will not be responsive.
Works:
[cell addSubview:view]; or [self addSubview:view];
Doesn't work:
[cell.contentView addSubview:view] or [self.contentView addSubview:view]

The way to avoid the headers moving with the cells is to return the contentView of the cell in viewForHeaderInSection. If you have a subclassed UITableViewCell named SectionHeaderTableViewCell, this is the correct code:
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
SectionHeaderTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SectionHeader"];
//Do stuff to configure your cell
return cell.contentView;
}

SWIFT 3.0 Tested solution. As mentioned in the first example for Objective-C; the key point is returning cell.contentView instead of cell So new format syntax is as below.
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// HeaderCell is the name of custom row designed in Storyboard->tableview->cell prototype
let cell = tableView.dequeueReusableCell(withIdentifier: "HeaderCell")
cell.songLabel.text = "Your Section Name"
return cell.contentView
}

Try implementing this method and give proper conditions for checking the swipe.If this method get called for header view.
1.tableView:editingStyleForRowAtIndexPath: 2.tableView:titleForDeleteConfirmationButtonForRowAtIndexPath: 3.tableView:shouldIndentWhileEditingRowAtIndexPath:

One problem not mentioned with the .contentView method is that if your table view cell makes use of layoutSubviews, you may not get the behavior you want-- because layoutSubviews will not get called. I ended up making an entirely separate UIView subclass that supports both normal cell operation and header operation, and creating a bare minimum UITableView cell class that uses that.

Victor's issue of losing background color, is solved by below addition to Brad's answer:
cell.backgroundColor = UIColor.cyanColor()
To:
cell.contentView.backgroundColor = UIColor.cyanColor()

Related

why UITableViewAutomaticDimension not working?

Hi there is plenty of question answering the dynamic height for UITableViewCell of UITableView. However I find it weird when I did it.
here's are some of the answer :
here and here
usually this would answer the dynamic height for cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
but in my case I wonder this line wont do anything.
my UITableView is being viewed after clicking tabbar inside the splitview. Is this helpful?
Maybe I'm Missing something. Could anyone help me I spent 2 hours doing silly.
These are my constraint for title the title could be long but the label is not.
and this is my cell
In order to make UITableViewAutomaticDimension work you have to set all left, right, bottom, and top constraints relative to cell container view. In your case you will need to add the missing bottom space to superview constraint for label under the title
I had added constraints programmatically, and accidentally added them to the cell directly, i.e. not on the contentView property. Adding the constraints to contentView resolved it for me!
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement tableview dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
Objective C:
// in ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property IBOutlet UITableView * table;
#end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
Swift:
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.
Note: If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.
Also make sure the Lines for your UILabel in the cell is set to 0. If it is set to 1, it will not grow vertically.
I have a specific case, and 99% of the solutions I read didn't work. I thought I'd share my experience. Hope it can helps, as I'd struggled for a couple of hours before fixing this issue.
My scenario:
I'm using two TableViews in one ViewController
I am loading Custom Cells (xib) in those tableviews
Dynamic content in cells consists of two labels
Page uses a ScrollView
What I needed to achieve:
Dynamic TableView height
Dynamic TableViewCells height
What you'll need to check to make it work smoothly:
Like every tutorial and answer will tell you, set all the constraints for your label/content (top, bottom, leading & trailing), and set it to the ContentView (Superview). This video tutorial will be helpful.
In the viewWillAppear method of my ViewController, I am giving one of my tableViews (tableView2) an estimated row height and then use the UITableViewAutomaticDimension as such (and as seen in all tutorials/answers):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tableView2.estimatedRowHeight = 80
tableView2.rowHeight = UITableViewAutomaticDimension
}
For me, these previous steps were not enough. My TableView would simply take the estimatedRowHeight and multiply it by the number of cells. Meeeh.
As I am using two different tableViews, I created an outlet for each of them, tableView1 and tableView2. Allowing me to resize them in the viewDidLayoutSubviews method. Feel free to ask in comment how I did this.
One additional step fixed it for me, by adding this to the viewDidAppear method:
override func viewDidAppear(_ animated: Bool) {
tableView1.reloadData() // reloading data for the first tableView serves another purpose, not exactly related to this question.
tableView2.setNeedsLayout()
tableView2.layoutIfNeeded()
tableView2.reloadData()
}
Hope that helps someone!
As of iOS 11 and Xcode 9.3, most of the above information is wrong. Apple's Guide suggests setting
tableView.estimatedRowHeight = 85.0
tableView.rowHeight = UITableViewAutomaticDimension
WWDC 2017 session 245 (Building Apps with Dynamic Type, about 16:33 into the video) suggests something similar. None of this made a difference for me.
For a Subtitle style cell, all that's necessary to get self-sizing table view cells is to set the number of lines (in the Label section of the attributes inspector) to 0 for both the Title and Subtitle. If one of these labels is too long and you don't set the number of lines to 0, the table view cell won't adjust its size.
Forgot to remove tableView(heightForRowAt:indexPath:)
It could be the case that, like me, you accidentally forgot to remove the boilerplate code for the heightForRowAt method.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.0
}
For anyone that maybe faced the same problem with me: I struggled for an hour to make it work. A simple UIlabel with top, bottom left and right constraints. I used the willDisplayCell to pass the data to the cell and this was causing problems in cells height. As long as I put that code in cellForRow everything worked just fine. Didn't understand why this happened, maybe the cell height was calculated before willDisplayCell was called so there was no content to make the right calculation. I was just wanted to mention and probably help someone with the same problem.
it was working for me fine in iOS 11 and above, while breaking in iOS 10
the solution was to add
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = 200 // a number and not UITableView.automaticDimension
also make sure you add "heightForRowAt" even if it works in iOS 11 and above, its needed if you support iOS 10
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
In my case i had two UITableViews, i had to modify this method as
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGFloat height = 0.0;
if (tableView == self.tableviewComments) {
return UITableViewAutomaticDimension;
}else if (tableView == self.tableViewFriends) {
height = 44.0;
}
return height;
}
Try this, Simple solution it's work for me,
In viewDidLoad write this code,
-(void)viewDidLoad
{
[super viewDidLoad];
self.tableView.estimatedRowHeight = 100.0; // for example. Set your average height
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
In cellForRowAtIndexPath write this code,
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] ;
}
cell.textLabel.numberOfLines = 0; // Set label number of line to 0
cell.textLabel.text=[[self.arForTable objectAtIndex:indexPath.row] valueForKey:#"menu"];
[cell.textLabel sizeToFit]; //set size to fit
return cell;
}
For my setup, I first double checked the constraints in the Storyboard for the UITableViewCell were unambiguous top to bottom, and then had to modify code in 2 places.
UITableViewDelegate's tableView(_:heightForRowAt:)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
// return UITableViewAutomaticDimension for older than Swift 4.2
}
UITableViewDelegate's tableView(_:estimatedHeightForRowAt:)
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
// return UITableViewAutomaticDimension for older than Swift 4.2
}
Step 1 and 2, lets you to apply autosizing just for particular rows. For applying to the whole UITableView, use:
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension
// return UITableViewAutomaticDimension for older than Swift 4.2
An uncommon solution, but one that may save others time: in cellForRowAtIndexPath make sure there is no delay in setting your label text. For some unknown reason, I had it wrapped in a DispatchQueue.main.async block, and this meant that the cell was being returned before the label text was actually set, so the cell and label never expanded to the size of the text.
In order to make UITableViewAutomaticDimension to work, you need to make sure all 4 corners' constraints is added to the superview. In most case, it works well with UILabel, but sometimes i find UITextView does the trick when UILabel not working well at the time. So try to switch to UITextView and make sure Scrolling Enabled is unchecked in storyboard, or you can also set it programmatically.
I had three horizontal items and the most left had a constraint to left, middle one had top, bottom, to the left item and to the right item. The right one had a right, and left to the middle item. The solution was to add additional left and right constraints to the middle item (my label) that were >= some number to the cell.
Make sure all views in the cell are constrained to the Content View and not to the cell.
As apple says:
lay out the table view cell’s content within the cell’s content view. To define the cell’s height, you need an unbroken chain of constraints and views (with defined heights) to fill the area between the content view’s top edge and its bottom edge.
In my case I did everything but it did not solve the problem for me, at last, I checked the lines of the label which was fixed to 3. Changing the line value to 0 solves the problem for me. so to tell you all it must be 0 and constraints Top, Bottom, Left, Right must be attached to work fine.
For recent version of ios and swift apple has changed the previously called UITableView.automaticDimension to UITableView().estimatedRowHeight, so to set the row height automatically use it like this:
tableView.estimatedRowHeight = 150 //random estimate manually to help the compiler before setting the estimate value.
tableView.rowHeight = UITableView().estimatedRowHeight
I've got a new solution..that's worked for me.
basically UITableViewAutomaticDimension renamed as UITableView.automaticDimension... I hope..it wo

UITableView duplicate section header after updating row heights

In my iOS app, I have a UITextView inside a tableview cell.
The UITextView and hence the cell height expands when the frame required for the text entered by user exceeds the current height of the cell.
In order to achieve the above, I am calling [tableView beginUpdates] followed by [tableView endUpdates] to reload the height for the cells.
The above is resulting duplicate section headers overlapping the expanded cell.
Is there a way to fix this without calling [tableView reloadData]?
Appended below is some relevant code:
When there is a text change, I verify if the text will fit in current text view, if not the cell is expanded to the new height:
- (void)textViewDidChange:(UITextView *)textView {
CGFloat oldTextViewHeight = [(NSNumber *)[self.cachedTextViewHeightsDictionary objectForKey:indexPath] floatValue];
CGFloat newTextViewHeight = [textView sizeThatFits:CGSizeMake(textView.frame.size.width, CGFLOAT_MAX)].height + CELL_HEIGHT_PADDING;
if (newTextViewHeight > oldTextViewHeight ||
(newTextViewHeight != oldTextViewHeight && oldTextViewHeight != TEXTVIEW_CELL_TEXTVIEW_HEIGHT)) {
[self reloadRowHeights];
}
}
- (void)reloadRowHeights {
// This will cause an animated update of the height of the UITableViewCell
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
It's also important to note that I am using a custom section header, which makes my problem similar to one mentioned here:
UITableView Custom Section Header, duplicate issue
I cannot however use the solution to above problem because I cannot reloadData for the tableView in middle of user entering text.
Try implementing
tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int)
delegate method if you didn't
Little late to the party, but I couldn't find a working solution on SO, and then I figured one out, so I thought I'd share.
I use UITableViewAutomaticDimension both for cell heights and for section header heights. My header view class is just a UIView subclass with some subviews as needed. Inside my tableView(:viewForHeaderInSection:) class, I just initialized a new header view as needed, and I was experiencing this duplicate headers issue. Not even reloadData helped.
What seems to have fixed it for me was to implement basic "cell re-use" for the headers. Something like this:
Store the header views in a dictionary somewhere in your view controller.
class ViewController: UIViewController {
var sectionHeaders: [Int: UIView] = [:]
// etc...
}
Then, upon request, return your existing section header view if available, or else create and store a new one.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let sectionHeader = self.sectionHeaders[section] {
return sectionHeader
} else {
let sectionHeader = YourSectionHeader()
// Setup as needed...
self.sectionHeaders[section] = sectionHeader
return sectionHeader
}
}

Best way to create custom UITableview section header in storyboard

Current I am creating a prototype cell in storyboard and using this cell as a section header.
Inside tableView:viewForHeaderInSection: method, I am dequeuing the cell and returning it.
My section header cell has a UITextField and a UIButton in it.
When I tap on text field keyboard appears but as soon as focus is moved away from text field whole section header disappears.
This happens when I return the cell directly as section header view, but if I return a newly allocated UIView as section header view onto which cell is added as subview then everything works fine besides autoresizing masks.
Why header is disappearing?
I am not sure what could be the best thing todo here.
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *CellIdentifier = #"SectionHeader";
SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//return sectionHeaderCell; // returning cell directly, section header disappears when focus is moved away from text field.
UIView * headerView = [[UIView alloc] initWithFrame:sectionHeaderCell.frame];
[headerView addSubView:sectionHeaderCell];
return sectionHeaderCell;//header view never disappears, but auto resizing masks do not work. Need to know how to set autoresizing masks to headerView so that it resizes correctly.
}
Prototype cell table views only allow you to design cells in the storyboard editor, not section headers and footers. Your attempt to use a UITableViewCell as the section header is a clever hack, but it's just not supported by the classes involved—UITableViewCell is not designed to be used for anything other than a table view cell. It could do a lot worse than the view disappearing or not being laid out correctly; UIKit would be well within its rights to fail an assertion, delete all the app's data, revoke your developer certificate, or set your house on fire.
If you want your code to function properly, your choices are to either create your section headers in code or to put them in a separate XIB file. I know that's not what you want to do, but those are the options you have.
I had the same issue and the fix was to return the cell's contentView like:
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
static NSString *CellIdentifier = #"SectionHeader";
SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
sectionHeaderCell.myPrettyLabel.text = #"Greetings";
sectionHeaderCell.contentView.backgroundColor = [UIColor whiteColor]; // don't leave this transparent
return sectionHeaderCell.contentView;
}
And you get the same autolayouted results as before, but without the disappearing.
I am sure you can use UITableViewCell as a section header, because UITableViewCell is subclass of UIView, so according to LSP
“objects in a program should be replaceable with instances of their
subtypes without altering the correctness of that program.”
In iOS 8, it's simple really. Just design your header the same way you design your cell. Everything is the same, you can put custom class and don't forget to add reuse identifier.
When it comes to use it in the code, just return that cell in tableView:viewForHeaderInSection method.
Don't forget to implement tableView:heightForHeaderInSection if you want to use fix height or tableView:estimatedHeightForHeaderInSection if the height depends on the cell intrinsic size.

iOS 8 UITableView first row has wrong height

I'm working on an app where I face a strange issue. I've created a UITableViewController in the storyboard and added a prototype cell. In this cell, I've added an UILabel element and this UILabel takes up the whole cell. I've set it up with Auto Layout and added left, right, top and bottom constraints. The UILabel contains some text.
Now in my code, I initialize the the rowHeight and estimatedRowHeight of the table view:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 50
}
And I create the cell as follows:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell : UITableViewCell? = tableView.dequeueReusableCellWithIdentifier("HelpCell") as? UITableViewCell
if(cell == nil) {
cell = UITableViewCell(style: .Default, reuseIdentifier: "HelpCell")
}
return cell!
}
I return two rows in my table view. Here comes my problem: the height of the first row is way to large. It appear that the second, third row etc all have a correct height. I really don't understand why this is the case. Can someone help me with this?
I had a problem where the cells' height were not correct on the first load, but after scrolling up-and-down the cells' height were fixed.
I tried all of the different 'fixes' for this problem and then eventually found that calling these functions after initially calling self.tableView.reloadData.
self.tableView.reloadData()
// Bug in 8.0+ where need to call the following three methods in order to get the tableView to correctly size the tableViewCells on the initial load.
self.tableView.setNeedsLayout()
self.tableView.layoutIfNeeded()
self.tableView.reloadData()
Only do these extra layout calls after the initial load.
I found this very helpful information here: https://github.com/smileyborg/TableViewCellWithAutoLayoutiOS8/issues/10
Update:
Sometimes you might have to also completely configure your cell in heightForRowAtIndexPath and then return the calculated cell height. Check out this link for a good example of that, http://www.raywenderlich.com/73602/dynamic-table-view-cell-height-auto-layout , specifically the part on heightForRowAtIndexPath.
Update 2: I've also found it VERY beneficial to override estimatedHeightForRowAtIndexPath and supply somewhat accurate row height estimates. This is very helpful if you have a UITableView with cells that can be all kinds of different heights.
Here's a contrived sample implementation of estimatedHeightForRowAtIndexPath:
public override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCell
switch cell.type {
case .Small:
return kSmallHeight
case .Medium:
return kMediumHeight
case .Large:
return kLargeHeight
default:
break
}
return UITableViewAutomaticDimension
}
Update 3: UITableViewAutomaticDimension has been fixed for iOS 9 (woo-hoo!). So you're cells should automatically size themselves without having to calculate the cells height manually.
As the Apple says in the description of setNeedsLayout:
This method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
Because of this you should add needed lines of code (which should be executed with right layout) in dispatch_after block(which will put your method in queue of RunLoop). And your code will be executed after needs layout applies.
Example:
- (void)someMethod {
[self.tableView reloadData];
[self.tableView setNeedsLayout];
[self.tableView layoutIfNeeded];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//code which should be executed with the right size of table
});
In iOS 8, assigning the estimatedRowHeight a value turns on the new iOS 8 automatic row height calculation feature. This means that the cell's height is derived by using its internal constraints from the inside out. If there's something wrong with those constraints you'll get odd results. So there's something wrong with your cell constraints. They are probably ambiguous; that is the usual reason for inconsistency. However that's all I can tell you, since you have not actually shown / described the constraints.
I suggest removing the bottom constraint on the UILabel. It will resize according to the text and the cell should resize as well.
If that didn't resolve the issue, try adding the following in viewDidLoad() :
self.tableView.reloadData()
This worked for me
- (void)layoutSubviews
{
[super layoutSubviews];
// Your implementation
}
I got this from here >> http://askstop.com/questions/2572338/uitableview-displays-separator-at-wrong-position-in-ios8-for-some-cells

How can I disable the UITableView selection?

When you tap a row in a UITableView, the row is highlighted and selected. Is it possible to disable this so tapping a row does nothing?
All you have to do is set the selection style on the UITableViewCell instance using either:
Objective-C:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
or
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
Swift 2:
cell.selectionStyle = UITableViewCellSelectionStyle.None
Swift 3 and 4.x:
cell.selectionStyle = .none
Further, make sure you either don't implement -tableView:didSelectRowAtIndexPath: in your table view delegate or explicitly exclude the cells you want to have no action if you do implement it.
More info here and here
For me, the following worked fine:
tableView.allowsSelection = false
This means didSelectRowAt# simply won't work. That is to say, touching a row of the table, as such, will do absolutely nothing. (And hence, obviously, there will never be a selected-animation.)
(Note that if, on the cells, you have UIButton or any other controls, of course those controls will still work. Any controls you happen to have on the table cell, are totally unrelated to UITableView's ability to allow you to "select a row" using didSelectRowAt#.)
Another point to note is that: This doesn't work when the UITableView is in editing mode. To restrict cell selection in editing mode use the code as below:
tableView.allowsSelectionDuringEditing = false
Because I've read this post recently and it has helped me, I wanted to post another answer to consolidate all of the answers (for posterity).
So, there are actually 5 different answers depending on your desired logic and/or result:
1.To disable the blue highlighting without changing any other interaction of the cell:
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
I use this when I have a UIButton - or some other control(s) - hosted in a UITableViewCell and I want the user to be able to interact with the controls but not the cell itself.
NOTE: As Tony Million noted above, this does NOT prevent tableView:didSelectRowAtIndexPath:. I get around this by simple "if" statements, most often testing for the section and avoiding action for a particular section.
Another way I thought of to test for the tapping of a cell like this is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// A case was selected, so push into the CaseDetailViewController
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.selectionStyle != UITableViewCellSelectionStyleNone) {
// Handle tap code here
}
}
2.To do this for an entire table, you can apply the above solution to each cell in the table, but you can also do this:
[tableView setAllowsSelection:NO];
In my testing, this still allows controls inside the UITableViewCell to be interactive.
3.To make a cell "read-only", you can simply do this:
[cell setUserInteractionEnabled:NO];
4.To make an entire table "read-only"
[tableView setUserInteractionEnabled:NO];
5.To determine on-the-fly whether to highlight a cell (which according to this answer implicitly includes selection), you can implement the following UITableViewDelegate protocol method:
- (BOOL)tableView:(UITableView *)tableView
shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
To sum up what I believe are the correct answers based on my own experience in implementing this:
If you want to disable selection for just some of the cells, use:
cell.userInteractionEnabled = NO;
As well as preventing selection, this also stops tableView:didSelectRowAtIndexPath: being called for the cells that have it set. (Credit goes to Tony Million for this answer, thanks!)
If you have buttons in your cells that need to be clicked, you need to instead:
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
and you also need to ignore any clicks on the cell in - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath.
If you want to disable selection for the whole table, use:
tableView.allowsSelection = NO;
(Credit to Paulo De Barros, thanks!)
As of iOS 6.0, UITableViewDelegate has tableView:shouldHighlightRowAtIndexPath:. (Read about it in the iOS Documentation.)
This method lets you mark specific rows as unhighlightable (and implicitly, unselectable) without having to change a cell's selection style, messing with the cell's event handling with userInteractionEnabled = NO, or any other techniques documented here.
You can also disable selection of row from interface builder itself by choosing NoSelection from the selection option(of UITableView Properties) in inspector pane as shown in the below image
FIXED SOLUTION FOR SWIFT 3
cell.selectionStyle = .none
In your UITableViewCell's XIB in Attribute Inspector set value of Selection to None.
EDIT: for newer Swift it is changed to:
cell.selectionStyle = .none
See this for more info:
https://developer.apple.com/documentation/uikit/uitableviewcell/selectionstyle
In case anyone needs answer for Swift:
cell.selectionStyle = .None
If you want selection to only flash, not remain in the selected state, you can call, in
didSelectRowAtIndexPath
the following
[tableView deselectRowAtIndexPath:indexPath animated:YES];
so it will flash the selected state and revert.
From the UITableViewDelegate Protocol you can use the method willSelectRowAtIndexPath
and return nil if you don't want the row selected.
In the same way the you can use the willDeselectRowAtIndexPath method and return nil if you don't want the row to deselect.
This is what I use ,in cellForRowAtIndexPath write this code.:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
1- All you have to do is set the selection style on the UITableViewCell instance using either:
Objective-C:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
or
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
Swift 2:
cell.selectionStyle = UITableViewCellSelectionStyle.None
Swift 3:
cell.selectionStyle = .none
2 - Don't implement -tableView:didSelectRowAtIndexPath: in your table view delegate or explicitly exclude the cells you want to have no action if you do implement it.
3 - Further,You can also do it from the storyboard. Click the table view cell and in the attributes inspector under Table View Cell, change the drop down next to Selection to None.
4 - You can disable table cell highlight using below code in (iOS) Xcode 9 , Swift 4.0
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OpenTbCell") as! OpenTbCell
cell.selectionStyle = .none
return cell
}
Objective-C:
Below snippet disable highlighting but it also disable the call to didSelectRowAtIndexPath. So if you are not implementing didSelectRowAtIndexPath then use below method. This should be added when you are creating the table. This will work on buttons and UITextField inside the cell though.
self.tableView.allowsSelection = NO;
Below snippet disable highlighting and it doesn't disable the call to didSelectRowAtIndexPath. Set the selection style of cell to None in cellForRowAtIndexPath
cell.selectionStyle = UITableViewCellSelectionStyleNone;
Below snippet disable everything on the cell. This will disable the interaction to buttons, textfields:
self.tableView.userInteractionEnabled = false;
Swift:
Below are the Swift equivalent of above Objective-C solutions:
Replacement of First Solution
self.tableView.allowsSelection = false
Replacement of Second Solution
cell?.selectionStyle = UITableViewCellSelectionStyle.None
Replacement of Third Solution
self.tableView.userInteractionEnabled = false
Try to type:
cell.selected = NO;
It will deselect your row when needed.
In Swift3 ...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let r = indexPath.row
print("clicked .. \(r)")
tableView.cellForRow(at: indexPath)?.setSelected(false, animated: true)
}
Swift 3,4 and 5
Better practice, write code in UITableViewCell
For example, you have UITableViewCell with the name MyCell,
In awakeFromNib just write self.selectionStyle = .none
Full example:
class MyCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
self.selectionStyle = .none
}
}
I've been battling with this quite profusely too, having a control in my UITableViewCell prohibited the use of userInteractionEnabled property. I have a 3 cell static table for settings, 2 with dates, 1 with an on/off switch. After playing about in Storyboard/IB i've managed to make the bottom one non-selectable, but when you tap it the selection from one of the top rows disappears. Here is a WIP image of my settings UITableView:
If you tap the 3rd row nothing at all happens, the selection will stay on the second row. The functionality is practically a copy of Apple's Calendar app's add event time selection screen.
The code is surprisingly compatible, all the way down to IOS2 =/:
- (NSIndexPath *)tableView: (UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 2) {
return nil;
}
return indexPath;
}
This works in conjunction with setting the selection style to none, so the cell doesn't flicker on touch down events
We can write code like
cell.selectionStyle = UITableViewCellSelectionStyleNone;
but when we have custom cell xib above line give warning at that time for
custom cell xib
we need to set selection style None from the interface builder
You just have to put this code into cellForRowAtIndexPath
To disable the cell's selection property:(While tapping the cell).
cell.selectionStyle = UITableViewCellSelectionStyle.None
From UITableViewDataSource Protocol, inside method cellForRowAt add:
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_CELL_IDENTIFIER", for: indexPath)
cell.selectionStyle = .none
return cell
OR
You can goto Storyboard > Select Cell > Identity Inspector > Selection and select none from dropdown.
I am using this, which works for me.
cell?.selectionStyle = UITableViewCellSelectionStyle.None
try this
cell.selectionStyle = UITableViewCellSelectionStyleNone;
and
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
and you can also set selection style using interfacebuilder.
Directly disable highlighting of TableViewCell into storyboard
While this is the best and easiest solution to prevent a row from showing the highlight during selection
cell.selectionStyle = UITableViewCellSelectionStyleNone;
I'd like to also suggest that it's occasionally useful to briefly show that the row has been selected and then turning it off. This alerts the users with a confirmation of what they intended to select:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
...
}
To disable the highlighting of the UItableviewcell
cell.selectionStyle = UITableViewCellSelectionStyleNone;
And should not allow the user to interact with the cell.
cell.userInteractionEnabled = NO;
You Can also set the background color to Clear to achieve the same effect as UITableViewCellSelectionStyleNone, in case you don't want to/ can't use UITableViewCellSelectionStyleNone.
You would use code like the following:
UIView *backgroundColorView = [[UIView alloc] init];
backgroundColorView.backgroundColor = [UIColor clearColor];
backgroundColorView.layer.masksToBounds = YES;
[cell setSelectedBackgroundView: backgroundColorView];
This may degrade your performance as your adding an extra colored view to each cell.
You can also do it from the storyboard. Click the table view cell and in the attributes inspector under Table View Cell, change the drop down next to Selection to None.
You can use :
cell.selectionStyle = UITableViewCellSelectionStyleNone;
in the cell for row at index path method of your UITableView.
Also you can use :
[tableView deselectRowAtIndexPath:indexPath animated:NO];
in the tableview didselectrowatindexpath method.
You can use this
cell.selectionStyle = UITableViewCellSelectionStyleNone;
You can use ....
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];

Resources