How to set User Interaction enabled in UITableView cell - ios

I am using UITableview(grouped). I have disabled the user interaction in xib. But, I want to enable the interaction for one cell. How can I enable this? I have used [cell setUserInteractionEnabled:yes] which is not working. -tableView didselectrowindexpath is not getting called. Can any one suggest me. Thanks in advance

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Configure the cell
if (indexPath.row == yourcell)
cell.userInteractionEnabled = YES;
}
Hope,this will help you

enable the user interaction in Xib.
add
if(indexPath.row != YOUR ROW NO)
cell.userInteractionEnabled = NO;
in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
hope it helps. happy coding :)

// To enable or disable user Interaction in particular Cell , you can try below code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//i have used guard statment for creating object of class you can also use if you wish
guard let createCell = tableView.dequeueReusableCell(withIdentifier: "Create") as? CreateProfileCell else {
return CreateProfileCell()
}
//now You can enable or disable user interaction in cell you want as i did below
switch indexPath.row {
case 0:
createCell.isUserInteractionEnabled = false . // false to disable interaction
case 1:
createCell.isUserInteractionEnabled = true // true to enable interaction
case 2:
createCell.isUserInteractionEnabled = false
default:
print("try again")
}
return createCell
}

Related

Reuse error tableView with cell design programmatically

I have a problem with my tableView who I managed specially, i need to delete and add row really often. My cell are designed programmatically. I update my array who depend my cells and called self.tableView.reloadData() but this don't remove the cells I need and update the tableView like my array.
Cause to the reuse and my design of cell (programmatically) I need to check if the cell is always design or not. And the problem come from here.
When I called tableView.reloadData() my data are not properly reload, so I need to delete All view in the cells: indicate that the cells are not design, to design the new cell ... Of course I can just update the visible cells (with tableView.visibleCells), so this work but how can I update my other not-visible cells ?
Maybe I have an architecture problem? If so, what is the best way to delete and insert a row in the TableView with a indexPath defined? Or, how programmatically design the cell only one time?
Code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return user.lobbySurvey.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:"Celll") as! CardCell
for survey in user.lobbySurvey{
let index = user.lobbySurvey.index(where: {
//get the current index is nedeed else the cells reuse lazy
$0 === survey
})
if indexPath.row == index{
var surveyState : UserSurvey.state
surveyState = survey.state
switch surveyState{
case .selectSurvey:
cell.drawCard(statutOfCard: .selectSurvey)
case .goSurvey:
cell.drawCard(statutOfCard: .goSurvey(picture: survey.picture))
case .surveyEnded:
print("survey Ended")
case .surveyWork:
print("survey in progress to vote")
case .surveyWaiting:
cell.drawCard(statutOfCard: .surveyWaiting(selfSurveyId: survey.id, timeLeft: survey.timeLeft, picture: survey.picture))
case .buyStack:
cell.drawCard(statutOfCard: .buyStack(supView : self.view))
}
}
}
cell.delegate = self
cell.delegateCard = self
cell.layer.backgroundColor = UIColor.clear.cgColor
cell.backgroundColor = .clear
tableView.backgroundColor = .clear
tableView.layer.backgroundColor = UIColor.clear.cgColor
return cell
}
You can have an array which is the model of your table:
NSMutableArray *model; (model can have identifier).
You can change this model whenever you want.
With this you can make your table dynamic just calling tableView.reloadData() and make whatever in cellForRow & heightForRow
- (CGFloat) tableView: (UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height;
YourModel *model = self.model[indexPath.row];
if ([model isKindOfClass:[SomeClassTableViewCellModel class]]) {
height = 50;
} else if([model.identifier isEqualToString:#"Whatever"]){
height = 0.0f;
else{
height = kHeightForNormalCells;
}
return height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YourModel *model = self.model[indexPath.row];
cell = [tableView dequeueReusableCellWithIdentifier:model.identifier forIndexPath:indexPath];
if ([cell isKindOfClass:[SomeClass class]]) {
NSLog(#"Configure Cell");
[cell setModel:model];
}
return cell;
}
Moreover, your cell should have a method setModel:
- (void)setModel:(SomeTableViewCellModel *)model {
_model = model;
self.label1.text = [NSString stringWithFormat:#"%#",model.value1];
self.label2.text = [NSString stringWithFormat:#"%#",model.value2];
}
Hope this helps you.

How to detect last visible cell in UITableView

I am new in iOS development and currently working on UITableView. I want to find last visible cells on the screen of device and cells that are at the bottom of the screen must be of blue color, which should fade to green as the cell is scrolled to the top of the screen.
I have gone through these links
Link1
Link2
But could not get success. Can anyone please provide idea how to detect last cells & cell fade animation?
Get last visible cell:
if let lastCell = tableView.visibleCells.last {
// do something with lastCell
}
In Swift 3.0, you can used this tableview method.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
let intTotalrow = tableView.numberOfRows(inSection:indexPath.section)//first get total rows in that section by current indexPath.
//get last last row of tablview
if indexPath.row == intTotalrow - 1{
// call for last display
}
}
#shallowThought solution will only work if cells are already presented.
But, if you want to know the last cell when cells aren't presented yet and are going to be presented, we can create an extension for UITableView as follow:
func isLastVisibleCell(at indexPath: IndexPath) -> Bool {
guard let lastIndexPath = indexPathsForVisibleRows?.last else {
return false
}
return lastIndexPath == indexPath
}
This way, you can check tableView.isLastVisibleCell(...) multiple times until you have reached actual visible cell.
Try this code, It will work
Initially Declare
int firstIndexRow;
int lastIndexRow;
Write below code inside of ViewDidLoad()
[myTable reloadData]; //Reload because get visible last cell index row
firstIndexRow = 0;
lastIndexRow = (int)[self.myTable.indexPathsForVisibleRows lastObject].row;
NSLog(#"first : %d",firstIndexRow);
NSLog(#"Bottom : %d",lastIndexRow);
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSIndexPath *firstVisibleIndexPath = [[self.myTable indexPathsForVisibleRows] objectAtIndex:0];
NSIndexPath *lastObject = [self.myTable.indexPathsForVisibleRows lastObject];
firstIndexRow = (int)firstVisibleIndexPath.row;
lastIndexRow = (int)lastObject.row;
NSLog(#"first : %d",firstIndexRow);
NSLog(#"Bottom : %d",lastIndexRow);
[myTable reloadData];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [myTable dequeueReusableCellWithIdentifier:#"cell"];
if (indexPath.row == firstIndexRow) {
cell.textLabel.textColor = [UIColor blueColor];
}else if (indexPath.row == lastIndexRow) {
cell.textLabel.textColor = [UIColor greenColor];
}else{
cell.textLabel.textColor = [UIColor grayColor];
}
cell.textLabel.text =[namesArray objectAtIndex:indexPath.row];
return cell;
}

UITableView, replace cell view on selection

Is it possible to replace a cell view for the selected cell? I have registered my controller as a data source and delegate. Requests for cell views, row numbers, selection status, etc, all come in nicely. In the cell selection callbacks, I'm trying to have the table view reload the cell with a new view (not the actual data!). Basically, I want the cell view to expand and show more of the underlying data, if it is selected. The problem is that I have no clue how to make the table view ask my controller for a new view. I can see that the view is requesting the cell height, but nothing more.
Calling reloadData on the view works, but it's inefficient and comes with a set of its own problems (animation possibilities, maintaining selection state, etc).
Here is the approach I would take to do this.
#property (nonatomic, strong) NSIndexPath *selectedIndexPath;
Set a property of type NSIndexPath to your controller to store which index path was selected.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedIndexPath = indexPath;
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation: UITableViewRowAnimationNone];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath compare:self.selectedIndexPath] == NSOrderedSame) {
// Create your custom cell here and return it.
}
else {
// Should create a normal cell and return it.
}
}
Exactly. Note too that you probably want to deselect. Here's the full code in Swift. Use selectedIndexPath in cellForRowAtIndexPath as appropriate.
// Selecting TableViewController
import UIKit
class SelectingTableViewController: UITableViewController
{
internal var selectedIndexPath:NSIndexPath? = nil
override func viewDidLoad()
{
super.viewDidLoad()
tableView.estimatedRowHeight = 68.0
tableView.rowHeight = UITableViewAutomaticDimension
self.clearsSelectionOnViewWillAppear = false;
}
override func tableView
(tableView:UITableView, didSelectRowAtIndexPath indexPath:NSIndexPath)
{
print("did select....")
// in fact, was this very row selected,
// and the user is clicking to deselect it...
// if you don't want "click a selected row to deselect"
// then on't include this clause.
if selectedIndexPath == indexPath
{
print("(user clicked on selected to deselect)")
selectedIndexPath = nil
tableView.reloadRowsAtIndexPaths(
[indexPath],
withRowAnimation:UITableViewRowAnimation.None)
tableView.deselectRowAtIndexPath(indexPath, animated:false)
return
}
// in fact, was some other row selected??
// user is changing to this row? if so, also deselect that row
if selectedIndexPath != nil
{
let pleaseRedrawMe = selectedIndexPath!
// (note that it will be drawn un-selected
// since we're chaging the 'selectedIndexPath' global)
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths(
[pleaseRedrawMe, indexPath],
withRowAnimation:UITableViewRowAnimation.None)
return;
}
// no previous selection.
// simply select that new one the user just touched.
// note that you can not use Apple's willDeselectRowAtIndexPath
// functions ... because they are freaky
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths(
[indexPath],
withRowAnimation:UITableViewRowAnimation.None)
}
}
Have you tried:
- (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
Then you can specify only your updated cell for reloading. You can also tell your tableview to remember or forget selection status upon reload with:
tableViewController.clearsSelectionOnViewWillAppear = NO;
Then you will also have to deal with the custom view inside
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath

UITableView selected cell doesn't stay selected when scrolled

I'm having problems with table view cells not keeping their "selected" state when scrolling the table. Here is the relevant code:
#property (nonatomic, strong) NSIndexPath *selectedIndexPath;
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
self.selectedIndexPath = indexPath;
//do other stuff
}
-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell_iPhone* cell = [tableView dequeueReusableCellWithIdentifier:#"MyCustomCell_iPhone"];
if (cell == nil)
cell = [[[NSBundle mainBundle] loadNibNamed:#"MyCustomCell_iPhone" owner:self options:nil] objectAtIndex:0];
if ([indexPath compare: self.selectedIndexPath] == NSOrderedSame) {
[cell setSelected:YES animated:NO];
}
return cell;
}
And for the cell:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
if (selected) {
self.selectedBg.hidden = NO;
}else{
self.selectedBg.hidden = YES;
}
}
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
[super setHighlighted:highlighted animated:animated];
if (highlighted) {
self.selectedBg.hidden = NO;
}else{
self.selectedBg.hidden = YES;
}
}
How can I get the selected cell to stay highlighted? If I scroll it off the screen, when it scrolls back on the screen it appears in its unselected state (with its selectedBg hidden).
EDIT:
Removing the setHighlighted method from the cell fixes the issue. However that means that I get no highlighted state when pressing the table cell. I'd like to know the solution to this.
Had the same problem, selected cell's accessoryView disappeared on scroll. My co-worker found pretty hack for this issue. The reason is that in iOS 7 on touchesBegan event UITableView deselects selected cell and selects touched down cell. In iOS 6 it doesnt happen and on scroll selected cell stays selected. To get same behaviour in iOS 7 try:
1) Enable multiple selection in your tableView.
2) Go to tableView delegate method didSelectRowAtIndexPath, and deselect cell touched down with code :
NSArray *selectedRows = [tableView indexPathsForSelectedRows];
for(NSIndexPath *i in selectedRows)
{
if(![i isEqual:indexPath])
{
[tableView deselectRowAtIndexPath:i animated:NO];
}
}
Fixed my problem! Hope it would be helpful, sorry for my poor English btw.
I know my method is not very orthodox but seems to work. Here is my solution:
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if cell.selected {
cell.selected = true
} else {
cell.selected = false
}
}
You must implement all the methods you mentioned on your post as well (#soleil)
I am using Xcode 9.0.1 and Swift 4.0. I found the following codes resolved my selection mark when cells off screen and back:
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if cell.isSelected {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
}
iOS 7/8 both deselect the cell when scrolling begins (as Alexander Larionov pointed out).
A simpler solution for me was to implement this UIScrollViewDelegate method in my ViewController:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
NSInteger theRow = [self currentRowIndex]; // my own method
NSIndexPath *theIndexPath = [NSIndexPath indexPathForRow:theRow inSection:0];
[self.myTableView selectRowAtIndexPath:theIndexPath
animated:NO
scrollPosition:UITableViewScrollPositionNone];
}
This works because my viewController is the UITableView's delegate, and UITableView inherits from UIScrollView.
If you want to achieve the same thing in Swift then here is the code. By the way I am using Xcode 7.2 with Swift 2.1.
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if cell.selected == true{
cell.selected = true
cell.backgroundColor = UIColor.blackColor()
}else{
cell.backgroundColor = tableViewCellColor //Don't panic its my own custom color created for the table cells.
cell.selected = false
}
}
Do other customization what ever you want..
Thanks..
Hope this helped.
Swift 3 solution, 2017.
I fixed the problem with this simple line of code:
cell.isSelected = tableView.indexPathsForSelectedRows?.contains(indexPath) ?? false
Inside the tableView(tableView:cellForRowAt indexPath:) method:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue a reusable cell
if let cell = tableView.dequeueReusableCell(withIdentifier: "YourCellID") {
cell.isSelected = tableView.indexPathsForSelectedRows?.contains(indexPath) ?? false
// Now you can safely use cell.isSelected to configure the cell
// ...your configurations here
return cell
}
return UITableViewCell()
}
Swift 5
Put the following code in your custom UITableViewCell subclass:
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
guard !isSelected else { return }
super.setHighlighted(highlighted, animated: animated)
if highlighted {
// Style cell for highlighted
} else {
// Style cell for unhighlighted
}
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
// Style cell for selected
} else {
// Style cell for unselected
}
}
Explanation: Try setting breakpoints on both setHighlighted and setSelected. You'll find that the dequeueReusableCell method calls setSelected then setHighlighted in that order to reset the new cell. So your highlighting code is blowing away the styling you did in your selection code. The non-hack fix is to avoid destroying your selected styling when setHighlighted(false, animated: false) gets called.
Have you tried comparing the rows of the index paths instead of the entire index path object?
if ((indexPath.row == self.selectedIndexPath.row) && (indexPath.section == self.selectedIndexPath.section)) {
[cell setSelected:YES animated:NO];
}
Here's the solution I came up with — and it doesn't even feel hacky.
1) Implement -scrollViewWillBeginDragging: and -scrollViewWillEndDragging:withVelocity:targetContentOffset: and manually highlight the cell for the selected row (if there is one) during scrolling.
Mine look like this:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollview {
self.scrollViewIsDragging = YES;
if( [self.tableView indexPathForSelectedRow] ) {
[[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]] setHighlighted:YES];
}
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
self.scrollViewIsDragging = NO;
if( [self.tableView indexPathForSelectedRow] ) {
[[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]] setHighlighted:NO];
}
}
The scrollViewIsDragging property is there so that in -tableView:cellForRowAtIndexPath: we can make sure any newly dequeued cells have the proper highlighting (e.g. if the cell for the selected row is scrolled onto screen after having been off screen). The pertinent part of that method looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// ... cell creation/configuration ...
if( self.scrollViewIsDragging && [[tableView indexPathForSelectedRow] isEqual:indexPath]) {
[cell setHighlighted:YES animated:NO];
}
}
…and there you have it. The cell for the selectedRow will stay highlighted during scrolling.
UITableViewCell has a BOOL property "selected". Whenever you load the cell, check the state of selected and make selection or deselection accordingly as follows in cellForRowAtIndexPath definition:
if (cell.selected) {
// Maintain selected state
}
else{
// Maintain deselected state
}
Posted a quick answer to that here:
https://stackoverflow.com/a/35605984/3754003
In it, I also explain why this happens.
Do not use built-in system properties isSelected.
You can create your own property, for example:
var isSelectedStyle = false
cell.isSelectedStyle = ....

How to remove the check mark on another click?

I want to make a table that user can select and deselect with a check mark:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath
{
...;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
- (void)tableView:(UITableView *) tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
...;
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
...;
}
I was trying to remove the check mark when clicking on the check-marked cell again, but it takes 2 clicks to do that instead of one.
If I set selection style to default, when I click on a selected row, it removes the blue highlight; clicking again, it removes the check mark.
I also tried some conditional statements in didSelectRowAtIndexPath, but they only respond to second click as well.
What causes the problem and how do I fix it?
You can try this one:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger index = [[tableView indexPathsForVisibleRows] indexOfObject:indexPath];
if (index != NSNotFound) {
UITableViewCell *cell = [[tableView visibleCells] objectAtIndex:index];
if ([cell accessoryType] == UITableViewCellAccessoryNone) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
}
}
This should toggle the cell's checkmark on every touch.
If you additionally want only one cell to appear selected at a time, also add the following:
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger index = [[tableView indexPathsForVisibleRows] indexOfObject:indexPath];
if (index != NSNotFound) {
UITableViewCell *cell = [[tableView visibleCells] objectAtIndex:index];
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
}
If you don't want the blue hilight background, simply set the cell's selection style to UITableViewCellSelectionStyleNone once you create the cell.
Based on Starter's link to Apple docs (my code in Swift 3):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: selectedIndexPath) {
cell.accessoryType = .none
}
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
selectedIndexPath = indexPath
}
The main point is to keep track of currently selected cell and change cell.accessoryType accordingly. Also don't forget to properly set cell.accessoryType in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) based on selectedIndexPath.
Check this Apple official doc
best solution ever
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (index != NSNotFound) {
UITableViewCell *cell = [[tableView visibleCells] objectAtIndex:index];
if (cell.accessoryType == UITableViewCellAccessoryNone)
cell.accessoryType = UITableViewCellAccessoryCheckmark;
else
cell.accessoryType = UITableViewCellAccessoryNone;
[self.tableName reloadData];
}
}
This helps in toggling the checkmarks (remove the check mark when clicking on the check-marked cell again)
You can use this:
You can use this:
if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark { tableView.cellForRow(at: indexPath)?.accessoryType = .none } else { tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark } tableView.deselectRow(at: indexPath, animated: true)

Resources