I want to change a specific UIImageView inside a UITableViewCell with this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView beginUpdates];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UYLTextCell *textCell = (UYLTextCell *)cell;
textCell.selected = YES;
//get and show the image at selected cell
textCell.testImage.image = [UIImage imageNamed:#"image280280.jpg"];
//[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
}
But what happened is some cell had their image View even though it has not been clicked.
Table views re-use cells. When a cell is scrolled offscreen it is added to a queue, and will be re-used for the next cell to be scrolled onscreen. That means your cell configuration code in tableView:cellForRowAtIndexPath: should be enough to properly configure any cell, including cells that were previously selected at a different indexPath. In tableView:didSelectRowAtIndexPath: store the selected index path in a property and use that property to configure your cell in tableView:cellForRowAtIndexPath:. The image will need to be set in tableView:cellForRowAtIndexPath: whether the cell is selected or not (unless you implement prepareForReuse in your custom cell).
Related
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.
Here is the cellForItemAtIndexPath code I have:
static NSString *cellIdentifier = #"Cell";
MyCustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
//do stuff, set label text
if (indexPath.section == 0) cell.label.text = [words1 objectAtIndex:indexPath.row];
else if (indexPath.section == 1) cell.label.text = [words2 objectAtIndex:indexPath.row];
else if (indexPath.section == 2) cell.label.text = [words3 objectAtIndex:indexPath.row];
return cell;
I register the cell type in viewDidLoad:
[_collectionView registerClass:[MyCustomCell class] forCellWithReuseIdentifier:#"Cell"];
I have my collection view setup in my storyboard with delegates and datasource linked up as normal.
Now my issue... I have two approaches, both with problems. I have 3 sections, each drawing from a unique array of data.
After I have fetched the required data, I use [_collectionView reloadData]. This loads the correct number of cells, as per the array count, but the cells offscreen are not populated. So when I scroll them onscreen, they have not has their label set.
Instead of the above, I try reloading each section as the data is fetched (a preferred route) using the following code (once per section):
[self.collectionView performBatchUpdates:^{
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
} completion:nil];
However, the problem using this method, is that after it has reloaded, it fills my collection view with the correct number of cells, but the cells themselves are blank, no text set on the labels.
In both scenarios, if I scroll the non-loading cells onscreen, offscreen and back on again, they will load. But the first time you see them, they don't.
Resolved. Placing your UI code, such as creating UILabel (not nil) inside the layoutSubviews method is bad.
Moving this code to the init method on my custom UICollectionViewCell subclass, fixed my problem if cell UI not consistently.
I'm creating an app which contains a screen that shows a table view with custom cells. Each cell contains two labels and a subview, which further contains other subviews. I'm handling the click event on the cell to hide/show the subviews within the subview in the cell. How can I make it so that when I click on a single cell, the subview of all the cells will change?
It is like the Stock application in iPhone (using iOS 7), here is a screenshot:
As in the image above, when you click on any of the green box, all the boxes change to reflect the same type of value.
Please let me know if this approach is fine, or how this can be implemented.
There are a couple ways of doing this. The first that comes to mind would be to handle the different states within the UITableViewCell subclass, and just reload the visible cells:
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationAutomatic];
If you're looking for more control over the process though, this process could also be achieved by changing the state future cells should load into, and then calling a method on every visible cell. This would provide you with an easy way to have complete control over how the contents of the cell update.
// Change flag for cell state then...
for (NSIndexPath *indexPath in [self.tableView indexPathsForVisibleRows]) {
if (condition) {
MyCellSubclass *cell = (MyCellSubclass *)[self.tableView cellForRowAtIndexPath:indexPath];
[cell someMethodWithArg:(id)state];
}
}
To do something as in Stock app you should handle two method cellForRowAtIndexPath: and click action method.
In cellForRowAtIndexPath: you should do the check which cell/button was pressed and display value base on it:
//Pseudo code
//cellForRowAtIndexPath
if (cellNo3Pressed)
{
//set up text with the right value.
}
else if (otherCell)
{
//set up text with the right value.
}
This will handle the cell which are not visible on the screen.
The next action method should handle nice animation on all of the visible cell:
NSArray *paths = [tableView indexPathsForVisibleRows];
for (NSIndexPath *path in paths)
{
UITableViewCell *cell = (UITableViewCell *)[self.tableView cellForRowAtIndexPath:path];
//Animate changes for cell
}
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.
I'm using a custom UITableViewCell subclass, called MessageCell.
I initialise the cell like so:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MessageCell *cell = [tableView dequeueReusableCellWithIdentifier:#"messageCell"];
// more code
return cell;
}
In each custom cell I have a checkbox used to mark this cell. When a cell is marked it is being added to an array, and vice versa.
While this works in terms of data, the UI is not reflecting it as it should. What happens is that in addition to the marked cell, other cells are being marked.
I assume this behaviour stems from cell reusability, i.e when I mark a cell as selected it has a common pointer with all future cells in this screen position.
For this reason, I want to use "normal" non-reusable cells.
So I tried:
MessageCell *cell = [[MessageCell alloc] init];
OR:
MessageCell *cell = [[MessageCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
But both of them resulted in displaying empty cells.
You still want to reuse cells! Just check your data and set the checkbox to checked or unchecked in your cellForRowAtIndexPath:
MessageCell *cell = [tableView dequeueReusableCellWithIdentifier: #"messageCell"];
if (condition cell should be checked) //set the cells checkbox to checked
else //set the cells checkbox to unchecked
Or some approach like that.
PS on re-reading your question:
do you really need a reference to a cell in your array? Can't you for example store the indexPath and use cellForRowAtIndexPath: if you need to make a cell out of it?