I have problem with updating rows in UITableView. In my tableView only one row in section can be selected (there can be a couple of section). So, when user select row, I programmatically deselect other rows in this section. Everything works when deselected rows are visible. If any deselected row isn't visible, then it is still checked. But didDeselectRowAtIndexPath is called for this row. Why? How to fix this?
- (NSIndexPath*)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
// It is predefined group - only one row in section cab be selected
// Deselect other rows in section
for (NSIndexPath * selectedIndexPath in tagTableView.indexPathsForSelectedRows) {
if (selectedIndexPath.section == indexPath.section) {
[self tableView:tableView didDeselectRowAtIndexPath:selectedIndexPath];
[tableView deselectRowAtIndexPath:selectedIndexPath animated:YES];
}
}
return indexPath;
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tagTableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
}
Selected state of a cell should not saved in the UITableViewCell but in the object (model) that fills the cell.
Since tablecells are reused there state are representing the the last state of the model that filled them. You should either keep the selected state in the object filling the cell of keep an array of select indexPaths.
The in the -tableView:cellForRowAtIndexPath: set the correct state of that cell. If you model object hold the selected state you can just set the state according to that property or if you are keeping an array with select indexpaths check if the path is in the array and set the state accordingly.
Related
I have a table view with (ex : 20 rows). and I have used a custom table view cell with this table view.
inside this table view cell, there are few labels, one button and a hidden view (UIView).
I have written button action for hide/show the hidden view inside the custom table view cell class.it works fine.but it affect to other rows in the table view. that means, when I tap the button in first row, then the hidden view show, and it can see in some other rows in the table view when scroll down.
At the same time (when hide/show), I want to increase and decrease the row height (only clicked row/cell) . what is going wrong. below is my codes and some screen shots to get an idea.
note : cell expand/increase it self when click on expand button in each cell.
this is how I hide and show the hidden view, inside the custom table view cell class.
- (IBAction)hideshow:(id)sender {
BOOL ishidden = self.insideCollectionView.hidden;
if(ishidden == true)
{
self.insideCollectionView.hidden = false;
}
else
{
self.insideCollectionView.hidden = true;
}
}
what is going wrong, hope your help with this.
Advance : it is great if there is a way to do both hide/show and expand(increase the row height) of the cell when click on expand button for each cell.
Found the suited solution for this by my self. thanx everyone who supported me.
Do the followings.
Create a NSMutableArray to hold Which button in Which row
clicked.
Then when user click on the Button in Custom table view cell, check it that index path is already in the mutable array, if it is already inside it, then romove it ,otherwise add it.
Then in the cellforrowatindexpath method, check that nsmutable array and , check whether the indexpath.row is exist or not.
Finally, if it is exists, do not hide, else hide it, this works perfectly.
here is the implementation for the table view. .m file
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 25;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FirstTableViewCell *cells = [tableView dequeueReusableCellWithIdentifier:#"tvcell" forIndexPath:indexPath];
NSString *theIndexpath = [NSString stringWithFormat:#"%ld", (long)indexPath.row];
//here check whether it is exists or not.
if ([chekcme containsObject:theIndexpath])
{
cells.insideCollectionView.hidden = false;
}
else
{
cells.insideCollectionView.hidden = true;
}
[cells setColletionData:bbarray];
[cells.expandMe addTarget:self action:#selector(runs:) forControlEvents:UIControlEventTouchUpInside];
//in here set the tag of the button to indexpath.row
cells.expandMe.tag = indexPath.row;
return cells;
}
//this is the action for the button inside the custom tableview cell.
- (IBAction)runs:(UIButton *)sender{
NSString *myob = [NSString stringWithFormat:#"%li", (long)sender.tag];
NSLog(#"%#",myob);
if ([chekcme containsObject:myob]) {
[chekcme removeObject:myob];
}
else
{
[chekcme addObject:myob];
}
NSLog(#"%#", chekcme);
//keep in mind to reload the table view here.
[self.maintableView reloadData];
}
note : checkme is NSMutableArray to holds the objects clicked by the user.
I will need to see the code in your tableview data source methods, but i think following can solve your issues:
Issue #1: I am assuming your are using deque for your cell, this is causing the cell to be reused when you scroll. I will suggest you to maintain the state of your each cell (eg: isExpanded) and configure cell accordingly in cellForRowAtIndex:
Issue #2: Use the same 'IsExpanded' in heightForRowAtIndex: and call reloadRowsAtIndexPaths: on the table for your cell when state is changed for 'IsExpanded'
That the button affects several rows is because you store the BOOL in the collectionView in the cell.
When the cell is reused for another row, that cell will be hidden/shown based on the status of the row it was previously used for. As has already been suggested you need to store this state for each and every one of your rows and set it accordingly when the cell is prepared by the dataSource.
You should set the status about the view initially in cellForRowAtIndexPath. Hope it will help you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// here you have to maintain the status about the current cell. whether it is hidden or not.
cell.insideCollectionView.hidden = status;
}
I,am doing the same thing like:
In didSelect
switch selectedIndexPath {
case nil:
selectedIndexPath = indexPath
default:
if selectedIndexPath! == indexPath {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
}
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
And at row height
if selectedIndexPath != nil {
if selectedIndexPath == indexPath {
return estimatedHeight
}
}
return 44
The selectedIndexPath is a property of NSIndexPath.
Hope this may help you.
you have need to reload all table instead of single cell
you can use below code
tbl_name .beginUpdates(
tbl_name .endUpdates()
Update
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == userselectedrow // save selectecd cell index if you want to only one cell expand otherwise save selected row in array and compare to here
{
return incrementheight // increment row height here
}
else
{
return decrementheight // decrement row height
}
}
- (IBAction)hideshow:(UIButton*)sender {
CGPoint point11=[sender convertPoint:CGPointZero toView:tbl_calender];
NSIndexPath index_pathGlobal =[tbl_calender indexPathForRowAtPoint:point11]; // save index_pathGlobal
}
hope this will be help you
Step 1:- Assume that you are showing data in rows like label value, button and hidden view.Add one parameter in your array ex. isViewNeedToShow, By default fill that value FALSE.
Step 2:- After that in your button action your are passing indexPath.row as tag value in cell for row at index path, So on button action change the array vale parameter isViewNeedToShow == TRUE, and reload the section of table view. For ex:-
Item *tempItem = yourArray[sender.tag];
tempItem. isViewNeedToShow = tempItem. isViewNeedToShow ? FALSE : TRUE;
**For particular row update** :-
[yourTableView beginUpdates];
[yourTableView reloadRowsAtIndexPaths:#[sender.tag, 0] withRowAnimation:UITableViewRowAnimationNone];
[yourTableView endUpdates];
Step 3:- If you want to expand table cell you have to calculate height of row that contains the items like view, label etc.
Or if you are using auto layouts in your project use UI Table View Delegate Methods
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
I hopes your problem would be resolved.
So basically what I am doing now is expanding my cells by selecting on them and using heightForRowAtIndexPath to change their height and if I select on it again or select a different cell those cells will expand or go back to their normal size.
However, upon the cell expanding I have some extra data to show in the expanded section, change the background color of the cell and set some other properties that I have defined in my tableviewcell subclass. So as an example when the cell is in its normal height the background will be light green. When its expanded it needs to be white. I set my tableviewcell subclass property (a BOOL) so that when its time to loop through the cells again (cellforRowatindexpath) i can update these properties as needed. Unfortunately, I haven't been able to find a way to get the current cell thats been selected in cellForRowAtIndexPath.
Here is the relevant code below. Keep in mind that I want to keep track of the current cell selected and the previous cell (if different from the current one) so that i can update both cells properties. Only one cell can be expanded at a time. When the current cell is selected and expanded the previous one(if applicable) will contract back to normal height. My configureCell method is just there to assign the properties based its BOOL property.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:myIdentifier forIndexPath:indexPath];
[cell configureCell:self.item[indexPath.row] isCollapsed:cell.isCollapsed];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MyCustomCell *currentCell;
MyCustomCell *previousCell;
self.currentSelectedIndex = indexPath;
//assign previous and current if previous is nil
if(!self.previousSelectedIndex){
self.previousSelectedIndex = self.currentSelectedIndex;
currentCell = [tableView cellForRowAtIndexPath:self.currentSelectedIndex];
currentCell.isCollapsed = NO;
}
//we have tapped the same cell as before
else if(self.previousSelectedIndex == self.currentSelectedIndex){
previousCell = [tableView cellForRowAtIndexPath:self.previousSelectedIndex];
previousCell.isCollapsed = YES;
self.previousSelectedIndex = self.currentSelectedIndex = nil;
}
//if they aren't equal, then collapse the previous selected cell
//and expand the current selected cell
else{
previousCell = [tableView cellForRowAtIndexPath:self.previousSelectedIndex];
previousCell.isCollapsed = YES;
currentCell = [tableView cellForRowAtIndexPath:self.currentSelectedIndex];
currentCell.isCollapsed = NO;
self.previousSelectedIndex = self.currentSelectedIndex;
}
[tableView beginUpdates];
if(self.currentSelectedIndex){
[tableView reloadRowsAtIndexPaths:#[self.currentSelectedIndex, self.previousSelectedIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
}
[tableView endUpdates];
}
So, obviously my current and previous cell will be trashed once we leave this method since they are local but I am struggling with how to:
a. reload the cells which would cause cellForRowAtIndexPath to execute again(this works when trying to use reloadRows - but maybe I'm doing that wrong)
b.once cellForRowAtIndex starts going through the cells how to capture the currentCell and the previousCell so that i can update its content as I described above. [dequeueReusableCellWithIdentifier:myIdentifier] just gets a new cell which I do not want obviously.
The cells expand and contract fine so thats not an issue.
My main problem is keeping the selected value of a UISegmentedControl when the cell that it is inside is reused.
When I scroll, the reused cell still has the same value for the segmented control.
- (UITableViewCell *)tableView:(UITableView *)thisTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"yesNoCell";
testCell = [thisTableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
testCell.mainText.text = [ questionArray objectAtIndex:indexPath.row];
[testCell.mainControl setFrame:CGRectMake(690, 22, 334, 40)];
return testCell;
}
Edit for clarification:
I want to keep the selection for each row. I may have, for example, 10 visible rows and 30 rows in all. When I select a segment in row 3, the row that shows up when "3" disappears has the same selected segment. I would like to make sure that the only rows with selected segments are those that the user actually changes.
Your model for each row should have a property that keeps the selected segment, lets call it selectedSegment. When the user clicks on a segment you affect the value of the selectedSegment property for the instance of the object representing the affected row.
Then in your cellForRowAtIndexPath: method, you update the UISegmentedControl's selected index with the value of the selectedSegment property.
Just keep selected indexes of segmented control in mutable array as NSNumber objects.
In - (UITableViewCell *)tableView:(UITableView *)thisTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method set tag property of segmented control to indexPath.row and get selected value from array by indexPath.row
When segmented control changed selected index you need to replace selected index in array to new value(get the selected index value by tag value from array)
P.S
Initially you need to create indexes array with default values
I have a UITableView with Dynamic Prototypes.
I implemented the code below so that when I select a row, it will be marked and the previously selected row will be unmarked.
However, for example, the row I selected is displayed in the middle of the screen, then when I scroll up or down, another cell (which is in the same middle position of the screen) is marked. In short, every view, there is a selected cell at the middle.
Please advise.
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *oldIndexPath = [self.tableView indexPathForSelectedRow];
[self.tableView cellForRowAtIndexPath:oldIndexPath].accessoryType = UITableViewCellAccessoryNone;
[self.tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
return indexPath;
}
Probably you are overwriting accessoryType in your cellForRowAtIndexPath method - this is called each time table is about to draw new rows which were invisible before (as you described when you scroll up / down).
You need to handle it also in that function and update accessoryType there - otherwise it will randomly reuse a cells with different accessoryTypes.
You are modifying just the visuals of cell, you're not updating the data model. Store the selected index path in a #property somewhere, and adjust accessoryType inside cellForRowAtIndexPath.
How could I easily dim the complementary set of rows, when one gets selected.
Right now I have the code to select a cell so I can call a method on it, but I would like to set opacity of all the other rows.
-(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SummaryCell * selectedCell = [tableView cellForRowAtIndexPath:indexPath];
[selectedCell manageContent];
}
Edit: I dont want to iterate through all the other cells (because there will be a lot of them) - wouldn't it be easier to add an UIView above all other cell (this would also prevent user interaction) and place the selected cell above that view (something like increasing z-index in HTML).
It depends what you mean by "dim" the other rows. The process to iterate over all visible rows and to set a property on all the rows other than the one selected is as follows:-
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
for (UITableViewCell *otherCell in self.tableView.visibleCells) {
NSIndexPath *otherIndexPath = [self.tableView indexPathForCell:otherCell];
if (![indexPath isEqual:otherIndexPath]) { // exclude the selected cell
// Do whatever you want with otherCell here
}
}
}
Where my comment is, you can set whatever properties you like on otherCell. For example, otherCell.alpha which is the alpha (transparency) of that cell.
You can iterate all the visible cells, configure them to be dimmed, then on the datasource cellForIndex method check if there's a selected cell, if there is, check if it is at the asked index, if so configure the cell as selected, if not as dimmed.