UITableView reloadRowsAtIndexPaths and cellForRowAtIndexPath - objective-c

I have a timer which calls the below code to reload a cell in the second section of my grouped UITableView. It works perfectly fine unless I scroll the screen up so that the first section/cell cannot be seen. Once it "springs back" to its correct position, I see that the text which should appear in section 0 has appeared in section 1 and vice-versa.
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:1]] withRowAnimation:UITableViewRowAnimationNone];
Does reloadRowsAtIndexPaths or cellForRowAtIndex path work from the top of the full (visible or invisible) cells, or does it only work starting on the cells that are visible on the screen? In other words, does section 0 actually change depending whether it is visible on-screen or not?
What else would cause cellForRowAtIndexPath to load the wrong data into the cells?

Ok, I played a bit with your project, here are my suggestions:
As you are adding the button to the cell, remember to remove it when you don't want use it (when you refresh the cell, basically you create a new cell, and store the second one to reuse, so if you scroll down and then up, you'll reuse that cell as the cell for the name).
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[[cell viewWithTag:2] removeFromSuperview];
for this to work, you must set the corresponding tag to the button:
myButton1.tag = 2;
[cell addSubview:myButton1];
And also, add the button directly to the cell, not to the contentView (this just worked for me, I'm not sure why).
EDIT: I uploaded a fixed version of your project, download it.

Related

UIImage as accessory view in UITableViewCell not showing

Within my app, I bring up a UITableView when prompting the user to select an item. Based on whether the item is in stock, out of stock, on order, etc., I want to display an image on the right-hand side of the UITableViewCell.
I have tried using the accessory view button, but it isn't showing up on the UITableViewCell. I have tried changing the UITableViewCellStyle to UITableViewCellStyleValue2 (the style with the detail closure button), and have also tried setting the accessory type of the cell directly.
static NSString *CellIdentifier = #"MyCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
//cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
}
[cell.textLabel setText:#"Testing"];
[cell.detailTextLabel setText:#""];
//cell.accessoryType = UITableViewCellAccessoryNone;
//cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
UIImageView *imageView = [[UIImageView alloc] initWithImage:[myImageFilePath stretchableImageWithLeftCapWidth:0 topCapHeight:1.0]];
[imageView setContentMode: UIViewContentModeScaleAspectFit];
[imageView setFrame:CGRectMake(0, 5, cell.frame.size.height - 10, cell.frame.size.height - 5)];
cell.accessoryView = imageView;
return cell;
When the app runs, the UITableViewCells appear with the "Testing" text, but there is no image on the right-hand side of the cells.
Is there another method that needs to be overridden in order to implement a custom UIImage for the detail diclosure button?
The "button" is more of a status indicator, because I don't want the tap of the button to do anything different than tapping anywhere else on the UITableViewCell does (just select the row).
Some additional info:
If I remove
[imageView setFrame:CGRectMake(0, 5, cell.frame.size.height - 10, cell.frame.size.height - 5)];
then the image shows up in the UITableViewCell, but it is too big and does not fit on a single row, so it overlaps with other rows' accessory views.
I was using the setFrame so that it would resize the image to fit with the UIViewContentModeScaleAspectFit so that it would fit on the row, but it seems to prevent the image from appearing at all.
Note that in this example cell.frame.size.height = 44.
If I change the frame to:
[imageView setFrame:CGRectMake(0.0, 0.0, 120, 44)];
then the image appears on the row.
However, since it is 120 pixels wide, it is cutting off the text with the trailing ... earlier that needed. The image could fit about 3 times before the trailing ..., with the width being 120 and height being 44.
How can I get the trailing ... to get closer to the image?
If I reduce the 120 to a lower value, the image moves to the right and eventually runs off the cell.
The width of the UITableView was 804. Updating it to 768 allowed me to use the original code that was posted.
Your question is very similar to this one here:
Using a custom image for a UITableViewCell's accessoryView and having it respond to UITableViewDelegate
You can ignore the "have it respond" bit unless you need to. But, as you can see there, the rect is square for the accessoryView property, so an image that is 120x44 is probably not going to fit correctly, ever.
I would rethink your approach to this problem, and create a custom UITableViewCell subclass that has the necessary items you require (sounds like a UILabel and a UIImageView). This way, you can easily control how large the label is versus how large the image is, how the label will wrap lines (if it wraps at all or maybe truncates) and so on.
Another way is just subclass the UITableviewCell and set frame's in "layoutSubviews" and also make sure to pass correct row height for each cell.
By subclassing you will get full flexibility.
hope this helps
I guess there's an issue with your CGRect X and Y, in this case here 0 and 5. Maybe trying to set them dinamically can fix this. Let me know

ProgressView wont update in tableview

I am trying to update a custom tableview cell's progressview but the progress view wont update.
I can set the labels on the tableview cell but accessing things like progress bars or activity indicators doesnt work.
I do the following on the custom cell
cell.progressView.hidden = NO;
cell.progressView.progress = 0.5;
[cell.progressView setNeedsDisplay];
This doesnt work even though in the debugger I see the object is of the correct cell type and the progressView is allocated.
I have tried setNeedsDisplay on the cell itself as well, but no luck. What am I missing?
Calling [self tableView:tableView cellForRowAtIndexPath:indexPath]; is most likely the problem because this likely results in another cell being dequeued from the table. So you end up setting up a new cell, not the one currently being display.
Instead, do this:
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
This directly asks the table for the cell. This should return the currently displayed cell if the row is visible.

UITableView: load all cells

Is it possible to load all cells of an UITableView when the view is loaded so that they are not loaded when I'm scrolling?
(I would show a loading screen while doing this)
Please, it's the only way at my project (sorry too complicate to explain why ^^)
EDIT:
Okay let me explain you, what I'm definite doing:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [NSString stringWithFormat:#"Identifier %i/%i", indexPath.row, indexPath.section];
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSDictionary *currentReading;
if (cell == nil)
{
cell = [[[CustomTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
UILabel *label;
UIView *separator;
if(indexPath.row == 0)
{
// Here I'm creating the title bar of my "table" for each section
}
else
{
int iPr = 1;
do
{
currentReading = [listData objectAtIndex:iPr-1];
iPr++;
} while (![[currentReading valueForKey:#"DeviceNo"] isEqualToString:[devicesArr objectAtIndex:indexPath.section]] ||
[readingresultsArr containsObject:[currentReading valueForKey:#"ReadingResultId"]]);
[readingresultsArr addObject:[currentReading valueForKey:#"ReadingResultId"]];
//
// ...
//
}
}
return cell;
}
My error happens in the do-while-loop:
"listData" is an array with multiple dictionaries in it.
My problem ist that when I’m scrolling my table slowly down, all is fine, but when I’m scrolling quickly to the end of the view and then I’m scrolling to the middle, I get the error that iPr is out of the array’s range. So the problem is, that the last row of the first section has already been added to the "readingresultsArr", but has not been loaded or wants to be loaded again.
That’s the reason why I want to load all cells at once.
You can cause all of the cells to be pre-allocated simply by calling:
[self tableView: self.tableView cellForRowAtIndexPath: indexPath];
for every row in your table. Put the above line in an appropriate for-loop and execute this code in viewDidAppear.
The problem however is that the tableView will not retain all of these cells. It will discard them when they are not needed.
You can get around that problem by adding an NSMutableArray to your UIViewController and then cache all the cells as they are created in cellForRowAtIndexPath. If there are dynamic updates (insertions/deletions) to your table over its lifetime, you will have to update the cache array as well.
put a uitableview on a uiscrollview
for example , you expect the height of the full list uitableview is 1000
then set the uiscrollview contentsize is 320X1000
and set the uitableview height is 1000
then all cell load their content even not visible in screen
In my case it was that I used automaticDimension for cells height and put estimatedRowHeight to small that is why tableview loaded all cells.
Some of the answers here and here suggest using automaticDimension for cells height and put mytable.estimatedRowHeight to a very low value (such as 1).
Starting with iOS 15 this approach seems not to work anymore. Hence, another way to achieve the table to "load" all cells could be by automatically scrolling to the last cell. Depending on the tables height and how many rows it can show some cells are discarded but each cell would be loaded and shown at least once.
mytable.scrollEnabled = YES;
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:cellCount - 1 inSection:0];
[mytable scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
mytable.scrollEnabled = NO;
If you want to scroll up again just scroll to the top as outlined here.
Following the comment that was made by juancazalla, I found that if you have a tableView that is using automaticDimension, loading all the cells at once can be best achieved by setting estimatedRowHeight to a low value (such as 1).

UITableView accessory added to additional cells when added to index 0

I'm having a hard time understanding some of the logic behind the UITableView. I am populating my table from a MPMediaItemCollection as a queue of songs. What I am trying to achieve is to have a now playing indicator as the accessory view of the cell at the index path that matches the currently playing song.
I originally tried this with the following:
if (indexPath.row == [mutArray indexOfObject:[mainViewController.musicPlayer.nowPlayingItem valueForProperty:MPMediaItemPropertyTitle]]) {
UIImageView *playButtonView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"PlayButton.png"]];
[cell setAccessoryView:playButtonView];
}
This works fine for the now playing item, which is always actually objectAtIndex:0. But what I don't understand is why my table seems to define this index once every height of combined visible cells.
Let me try to explain this more clearly. Lets say that my table is 600px tall, and its content height is 1800px. This causes the indicator to be added roughly every 600px down the content height.
Now my first thought was that this was something wrong with the code for judging the index based off the name of the song, so I tried changing it to:
if (indexPath.row == 0)
But this produces the same result!
This screenshot should help explain what I'm talking about.
So, is there anything I can do to make the table treat indexPath0 as only the first cell in reference to the entire table instead of in reference to the currently visible cells?
You have to state explicitly also when the accessory should not be there:
if (indexPath.row==0) {
UIImageView *playButtonView = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"PlayButton.png"]];
[cell setAccessoryView:playButtonView];
}
else {
[cell setAccessoryView:nil];
}
The reason is that when cell 0 gets dequeued (i.e. reused) on a different row it still has the accessory view in it.
Actually, this is a nice case study for understanding how dequeueing table view cells actually works ;-).
That's how tableview reuse cells: when cell scrolls out of screen it's added to reuse pool, so once you've added your accessory view to cell and that cell is reused - you'll see it in random places while scrolling.
You can check your cells index in -willDisplayCell:forIndexPath and add (if it's not added), hide (if it's there, but not your desired index) or show (if it's there and it's your index), or add accessory view to all cells and show/hide as needed.

how to toggle the selection color of a UITableViewCell?

I want to modify a cell in a table view; making it highlight blue only when clicked. when released, the blue color should disappear.
the code:
cell.selectionStyle = UITableViewCellSelectionStyleNone;
in the configuration of the cell, will disable the selection completely.
in all, this is mostly needed when i return from the push view controller. i then dont want to see any selected cells in the table.
Any ideas on how to do this?
thanks
When you touch a row in a tableview (if selection is allowed) the row is rendered in the selection style. If you want the "blue" selection colour to disappear, just deselect the row using:
NSIndexPath* rowPath = [NSIndexPath indexPathForRow:5 inSection:2];
[self.tableView deselectRowAtIndexPath:rowPath animated:NO];
Sounds like you're detecting the selection and doing something with the object backing the row and then returning to the same table, so just do the deselection in the same method as you do the action probably in:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
You can set a cell background color when the user clicks and set it back to white when the user releases.
That code should be useful to solve the 2nd problem
self.clearsSelectionOnViewWillAppear = NO;