How can I store the UIActivityIndicator state in UITableView? - objective-c

I have a UITableView which shows file names. When the user taps on the cell, I download the tapped file. For this, I am showing an activity indicator at the left side of a selected cell. After the download ends, the activity indicator will hide. (Remember, the other content in cell wont change).
There is no rule here to click only one cell at a time. The user may tap any number of cells to initiate the download process. I just start the download process and will add it in the operation queue.
Problem: My problem is, consider the scenario where the user taps 3 cells. So three cells will show activity indicator to represent their download processes. If the user scrolls the table view and comes back to the same cells, the activity indicators was hidden. This is because, the tableview's cell creation method will called only for visible cells. So, how can I store the state of each cell's activity indicator?

You can store which file is downloading. And for each row create activity indicator. Something like this:
NSArray* filesArr;
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* identifier = #"identifier";
YoursTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell){
cell = [[YoursTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
//.....
if(filesArr[indexPath.row].downloading)
[cell.activity startAnimating];
else
[cell.activity stopAnimating];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(filesArr[indexPath.row].downloading)
return;
[self startDownload:filesArr[indexPath.row]];
filesArr[indexPath.row].downloading = YES;
[tableView reloadData];
}

As you allude in your question, the issue is in part an artifact of cell reuse. When a cell is dequeued, you must update its activity state on the cell, either by creating a property on the file object for downloading status as Sk0prion suggested, or by some parallel structure.
I would just mention an alternative. If you have only a few cells, you could conceivably bypass cell reuse and store the cells in a dictionary. By avoiding cell reuse, the status in essence, is borne by the cell rather than the object it references. Memory pressure is obviously at issue. I've rarely found a case where this is the preferred solution.

Related

How to track a tableViewCell that had slid out of the screen?

As we know, UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:1]];method is invalid after the cell is out of our sight. (it's been put to the pool of reusable cells by system) So how do we track the information inside this cell?
I created another cell variable to point to it.In method -(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPathI make _trackMarkCell = cell; trackMarkCell is a property of current controller. This way I can always get the information of the cell. But I feel this way is not that smart, is there any better solution?
Thanks.
So, basically , UITableviewCell is just a view, that is used to display the content in UITableView so from the method -(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath you can crate a new cell or if the cell is already in the pool you can reuse it
come to your question, with the information i mentioned above, the cell holds the information that is used to display on tableview so u just no need to worry about the cell, but the data what you are displayed in the cell. u just keep track of the data and while updating the cell, clear all the information of the cell (which is crated newly or reused from the pool) before using it. for example
lets say suppose u are displaying the image in the cell, but after some time, image in the cell which is changed, in this case u just change the image which is present the datasource not in the cell, if u are using the images array to display the images in the cell,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kMenuTableCellReuseId];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kMenuTableCellReuseId];
}
//hear clear all the info present in the cell
//no need of this also u can directly update the cell, but for clarity i mentioned this
cell.myImageView.image = nil;
//just load the cell with new data
cell.myImageView.image = [dataSource objectAtIndex:indexPath.row];
//.... other code
return cell;
}
one more thing using the another cell to keep track of the cell which is present in the pool which might give u different information of other cell. or that cell might be deallocate. so just keep track of the data not the table view cell.
Not sure how you're populating your cells, but if you're feeding the cells via an array of model objects, you'd have access to all your data within that array. You could reference the object with the information you're seeking based on the index within the array (eg. - array[3], where '3' is the index of the object desired)
So how do we track the information inside this cell?
You just don't. Cells don't store information. Cells display information from a data source. You store the data in arrays or Core Data or on a server or anywhere else. Then you just display it in the cells.
What you can do is, you can get data from the cell and put in your collection in UITableViewCell's prepareForReuse method: this will be called before the cell is reused.

Why dequeue reusable cell twice in making custom table view cell

I am following a tutorial of making custom table view cell with storyboard. I drag a UILabel as subview of the cell and set its tag to 1. I have two questions regarding the data source code.
What's the purpose of the second dequeue statement? I know it's an init method while not using storyboard to make the custom cell.
What's the difference between tableview and self.tableview?
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
NSDictionary *dToAccess = (self.tableView==tableView)?[self.arForTable objectAtIndex:indexPath.row] : [self.arForSearch objectAtIndex:indexPath.row];
[(UILabel*)[cell viewWithTag:1] setText:[dToAccess valueForKey:#"name"]];
[(UILabel*)[cell viewWithTag:2] setText:[dToAccess valueForKey:#"value"]];
return cell;
}
For your first question, the second dequeueReusableCellWithIdentifier: looks like a mistake.
Here is how a UITableView works:
You might have 50 rows in your table, but if only 10 rows are visible at a time, you only need to make 10 cells, and then when the user scrolls, you can reuse cells that have gone offscreen instead of always releasing them and init'ing new cells that come onscreen. A UITableView keeps a list of cells that have gone offscreen and when you call dequeueReusableCellWithIdentifier:, it removes it from the list of offscreen cells and returns it to you. From here you can customize the cell for re-use (change its text, color, etc) and return it. Again, this is not an "init" method, this is returning a pre-existing cell.
So, let's look at what happens when this UITableView is first displayed -- in this example there are 10 visible cells, so the tableView will call tableView:cellForRowAtIndexPath: 10 times to get cells to display in these 10 slots. Every time this is called, you will need to initialize and return a new UITableViewCell to display. (At this point dequeueReusableCellWithIdentifier: will return nil, because you don't have any offscreen cells to re-use yet)
When a user scrolls your list, cells will begin to go offscreen, and new cells will need to appear. You don't need to make new cells, because you have already created as many as will need to be onscreen at a time. You should call dequeueReusableCellWithIdentifier: to get a reference to a cell that has gone offscreen, which you can then re-use.
I would alter your code like this:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: CellIdentifier] autorelease];
}
Now you are checking for reusable cells before creating new ones.
For your second question,
In your example, tableView refers to the tableView that was passed in (see the "tableView" in your method signature). Separately, if your class has defined a property called tableView, then self.tableView will call the getter for this property.
When apple developed the UITableView for the first iphone they had a problem in performance when scrolling through it. Then one clever engineer discovered that the cause of this was that allocation of objects comes with a price, so he came up with a way to reuse cells.
dequeueReusableCellWithIdentifier method is used to returns a cell if it has been marked as ready for reuse.
So Whenever there are many number of rows in a table view and you are going to scroll it, then the cells which are just passed away from your previous screen before scrolling are get reused instead of creating new one.
And to know the ans of your second que. I think you should refer this link :
http://www.iphonedevsdk.com/forum/iphone-sdk-development/17669-when-use-self-objectname-just-objectname.html
To dequeue twice is not necessary, this block of code is broken.

UITableViewCell text filed disappear data on scrolling the tableview

I have a UITableView with custom cells, those cells contain some textFields. Here when I enter some data in textFields and I scroll the table view data it disappears, I think because every time it's creating new cell.
I resolved this issue by using an array and inserting every cell in that, but here I am not able to reuse cells, so we are wasting memory.
Can you tell me prefect way how I should handle this behavior?
Use an array to store the value of the every text field and set the value of the desired text field in the
- (UITableViewCell *)tableView:(UITableView *)tTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
method
It sounds as though it has something to do with how you are creating cells in the tableView:cellForRowAtIndexPath: method. But without seeing your implementation I can only make general suggestions (add your implementation to your question so people can be a bit more specific with their answers).
If you are worried about memory then use the UITableView's inbuilt cell reuse feature by creating your cells in the following way:
NSString *identifier = #"reuseIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
}
cell.textLabel.text = #"text relating to index path";
The above will check the tableView to see if there are any available cells for reuse. If there are none then nil will be returned. This is where you create a cell using the initWithStyle:reuseIdentifier: method which marks the cell as being suitable for reuse if it is scrolled out of view. This means that you will only ever instantiate, at maximum, the total number of cells that are visible at once, which keeps your table's memory footprint nice and low.
Note that we set the cell's textField.text outside of the nil check - this is to ensure that if we have retrieved a reusable cell we will overwrite its old text content with the text content relevant to the indexPath being passed into the method.

Getting name of cell from indexPath / Swip Gesture

The Situation
I'd like to be able to get information about the title of a cell within a UITableView when the user swipes the cell (to display the "delete" button).
The Code
When the user swipes a cell in the UITableView, this method is fired:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath*)indexPath
The Problem
I need to be able to get the name of the cell the user "swiped" so that my iOS (Obj-C) app can do various operations, etc.
Everything Else
I know that the indexPath has something, but I can't get an NSString from it (which is what I need).
I was also thinking that a workaround such as using a gesture recognizer instead of the above method might be able to provide me with more information about the cell.
Any ideas as to how I can get the name of the cell when the user "swipes to edit / delete"?
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *text = cell.textLabel.text;
// do something with text
}
Please, how does your table work in the first place - you don't seem to understand the basic principles of it. You back your table up with a data model. YOU provide and construct the cells for each indexPath, so you should know how to access the data in your model using that swiped indexPath, no?
Of course you could also ask the tableView delegate (yourself) for the swiped cell by calling tableView:cellForRowAtIndexPath: and then check the labels on that cell.
Have a look at the implementation of your tabkeView:cellForRowAtIndexPath: method all your info of how to get the data should be there...
All in all it's pretty easy, if you provide some code you sure will get more hints.

Changing an Object in UITableViewCell is also changing the Object of a reused UITableViewCell

Code updated to working version. Thanks again for the help :)
Hey guys. I have a UITableViewController set up to use a custom cell loaded from a nib. Here's my cellForRowAtIndexPath:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = #"PeopleFilterTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"PeopleFilterTableViewCell" owner:self options:nil];
cell = peopleFilterTableViewCell;
self.peopleFilterTableViewCell = nil;
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
PeopleFilterTableViewCell* tableViewCell = (PeopleFilterTableViewCell *) cell;
/* Set direct button name */
Person* personAtRow = [directsToShow objectAtIndex:indexPath.row];
[tableViewCell.directButton setTitle:personAtRow.name forState:UIControlStateNormal];
/* Set direct head count */
tableViewCell.headcountLabel.text = [NSString stringWithFormat:#"%d", personAtRow.totalHeadCount];
UIImage* unselectedImage = [UIImage imageNamed:#"filterButton.png"];
UIImage* selectedImage = [UIImage imageNamed:#"filterButtonClosed.png"];
UIButton* newFilterButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
/* Set filter button image */
if(personAtRow.filtered){
[newFilterButton setSelected:YES];
} else {
[newFilterButton setSelected:NO];
}
tableViewCell.filterButton = newFilterButton;
return cell;
}
This seems to work fine for me, but one issue has come up with the code after the /* set filter button image */ comment.
The filter button is a UIButton in my custom cell nib that is supposed to reflect the state of a model array containing 'Person' objects, which have a field that can be toggled to represent whether they are being filtered or not.
The way that I allow a user to update this model object is through a custom delegate method on my top level controller, which, whenever the user clicks the filter button, updates the model and the state of the button, and additionally updates a mapViewController with some data to show based on the state of the model:
- (void)updateViews:(id)sender {
UIImage* unselectedImage = [UIImage imageNamed:#"filterButton.png"];
UIImage* selectedImage = [UIImage imageNamed:#"filterButtonClosed.png"];
int row = [self rowOfCellSubView:sender];
Person* personToFilter = [self.directsToBeShown objectAtIndex:row];
NSLog(#"Filtering person with corpId: %#, name: %#", personToFilter.corpId, personToFilter.name);
if (personToFilter.filtered){
//update button image
[sender setImage:unselectedImage forState:UIControlStateNormal];
[sender setSelected:NO];
//add person back.
Person* directFiltered = [self.directsToBeShown objectAtIndex:row];
directFiltered.filtered = NO;
NSLog(#"Filtering person with corpId: %#, name: %#, filtered: %d", directFiltered.corpId, directFiltered.name, directFiltered.filtered);
} else {
//update button image
[sender setImage:selectedImage forState:UIControlStateSelected];
[sender setSelected:YES];
//remove person.
personToFilter.filtered = YES;
NSLog(#"Filtering person with corpId: %#, name: %#, filtered: %d", personToFilter.corpId, personToFilter.name, personToFilter.filtered);
}
[self updateSitesToShow];
[self.mapViewController performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:NO];
}
My issue comes with the updating of the state for the filter button. When my app loads the tableview, everything looks fine. When I click a filter button in a certain cell row the state of the button updates correctly, and my model objects are also updating correctly since I see the expected behavior from the mapView which I'm ultimately updating.
However, the issue is that when I click on the filterButton in one cell row and then scroll down a few rows, I notice another filter button in a different cell now has the same state as the one I clicked a few rows above. If I scroll back up again, the original button I clicked 'on' now seems to be 'off' but the row below it now appears 'on'. Of course all this is affecting is the actual display of the buttons. The actual state of the buttons is consistent and working correctly.
I know this issue must have something to do with the fact that my cells are being reused, and I'm guessing somehow the same buttons are being referenced for different cell rows. I'm at a loss as to how this is happening though, since I'm creating a new filter button for each cell, whether the cell is reused or not, and resetting the filterButton property of the cell to be the newly created object. Notice for example that the text of the headCount property, which is a UILabel also defined in the cell nib, is also being reassigned to a new String object for each cell, and it is displaying correctly for each row.
I've been struggling with this problem for a few days now. Any help or suggestions at all would be really appreciated.
Table views cache their cells to allow you to reuse them, which you're doing whenever you call dequeueReusableCellWithIdentifier:. You should call setSelected: before the end of your tableView:cellForRowAtIndexPath: method to synchronize the state of the button with the state of the Person instance that corresponds to the current row.
Another thing to consider is that creating new button instances each time you return a cell is pretty wasteful. Consider creating and configuring the buttons (setting titles, images, etc.) once per cell instance inside the if block where you're loading the cell from the nib file.
This is a typical issue that happens when you change the cell view structure after it has been dequeued, while you're allowed to change the cell structure only in the alloc/init stage. After dequeue or alloc/init you are allowed to customize the content only and not the structure.
In your case, when the cell (let's say it is row-0) is loaded from the nib, the internal subviews structure is created (as defined in the Nib) and filterButton instance is assigned to one of these subviews. But a few lines below you create a new UIButton and replace the filterButton instance with this new one, but the real button subview will remain the same! Now when you click a button, of course the "real" button (that is the button in the cell view hierarchy which has been originally created by the Nib) will be triggered, the callback called and the state changed.
Later, when you scroll up this row-0 cell, it is removed from screen and enqued and then re-used for another cell, let's say row-9. At this point, former cell row-0 is going to be reused for cell row-9, but setting filterButton has still no effect, as you're keep going using the original button loaded initially by the Nib for cell row-0. Of course you will see these buttons states to be messed during scrollings as they are reused by the queue mechanism differently each time (so row-0 --> row-9, later row-0 --> row-8 and so on).
The solution is simply to change the button status: [self.filterButton setSelected:NO|YES] and not change the cell view content.
So the golden rule is: NEVER change the cell structure after you've dequed it. If you need to change the structure then use DIFFERENT cell IDs. Of course the more customizable is the cell the easier is the possibility to reuse them.