UITableViewCell is nil if it's not visible - iphone

In my app I'm using ELCTextfieldCell. The idea is to use the data entered by the user for some calculations. But there is the problem. I have about 14 cells and, of course, they can't all fit on a screen. So when I click OK the app is checking if all fiels are filled in:
BOOL complete = YES;
for (int i = 0; i < [cellTextArray count] - [self.numberOfBools intValue]; i++) {
NSIndexPath *iPath = [NSIndexPath indexPathForRow:i inSection:0];
ELCTextfieldCell *theCell = (ELCTextfieldCell *)[self.tableView cellForRowAtIndexPath:iPath];
if (!theCell.rightTextField.text)
complete = NO;
}
This code works perfectly if all the cells are visible, but if some are out, then the complete becomes NO. The output of theCell in gdb is:
(gdb) po theCell
Can't print the description of a NIL object.
Can somebody push me in a right direction please? :)
All help will be appreciated, thank you.
EDIT
self.numberOfBools is just an NSNumber with total of bools in these rows. They are using UISwitches, not UITextField as the other cells, so I excluded them from the check.

Complete needs to be calculated on the data that is backing the cells rather than on the cells themselves. Let me explain.
The cell is a visual representation of your data and when it is not in view the run-time will release it and that is why it is nil.
However cellForRowAtIndexPath: creates the cell from data right? (or it is normal to do so) so when the user updates the cell.rightTextField you should update data.rightTextField and then complete should be looking something akin to ... and this is pseudocode not compilable
complete = YES;
for (Data* data in myDataSet) {
if (!data.rightTextField)
complete = NO;
}
So, in summary cells represent data and they are not guaranteed to persist. You yourself can ensure the data is persisted; therefore test for completeness on the data and not on the cells.

This is by design. Cells in UITableView are reusable, and they can be released when invisible to save resources. You have to store your data somewhere else than in cells.

I agree with Damo and anticyclope here. You should have updated the
theCell.rightTextField.text
into your datasource of your table in the first place.
Then you could make your calculation based on the datasource.
Let textFieldArray be the datasource of your table.
NSMutableArray *textFieldArray;
myTableView.dataSource = textFieldArray;
Then need to update the datasource when the textfield is updated.
Finally you can process the datasource.

Related

Iterate all cells in s UITableView

I want to iterate all uitablewview cells and display the text of a uitextview contained in each cell.
My table can have many rows and to reach all you must scroll. I made an implementation, but it displays the text only for current visible cells in scroll, for the others gives me null.
for (int i = 0; i < [propertiesTableView numberOfRowsInSection:0]; i++) {
UITableViewCell* cell = [propertiesTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
UITextView* tx = (UITextView*)[cell viewWithTag:2];
NSString* temp = tx.text;
NSLog(#"%#", temp);
}
How to fix this?
This happens because only the visible cells are instantiated (remember the dequeueReusableCellWithIdentifier: function?). You should extract the needed information from the table data source.
Yes this behavior is expected, because of performance considerations the table view does not holds all cells initialized all the time. Your solution is just to update your data source - if it's an array for example, iterate though it and change the values accordingly - this is the power of MVC (fast UI, separate model)

iPhone: smooth scroll through the UITableView

I have a UITableView, and custom cells on it. On cell I have a UILabel, but before I set text to UILabel I did really hard work on text...like find the text in another text, highlight some words on it, and only then I set it to label. So when I scroll my list, it has delay because of this hard work. Any idea how to improve performance ? Maybe to do all hard work in another thread ??
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath {
static NSString *customCellIdentifier =
#"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
customCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomTableRow"
owner:self options:nil];
if (nib.count > 0) {
cell = self.customTableRow;
}
}
self.myLabel.text = [self giveMeTheTextThatINeed];
return cell;
}
[self giveMeTheTextThatINeed] - did a hard work on text that takes some time.
Make a new thread for every cell. this thread calls [self giveMeTheTextThatINeed:indexPath], and resets the label(s) in the cell. I'm assuming you can't get your data any faster, so you want to maintain the scrolling in the table and spin the hard work out to the thread. When the thread is finished, update the cell. You see this a lot in cells with a thumbnail image where the thumbnail only gets uploaded after a while, and is blank or has a placeholder there first.
Any way for you to precompute the values you'll need? In other words, start doing your "hard work" (in another thread) when the app starts, and store it somewhere so that, if it's ready, you can just grab it when the table view asks for it. It's hard to answer without more detail about what the hard work is and how much data we're talking about.
I don't know about just doing the hard work on another thread as you suggested, since you still have to give something to tableView:cellForRowAtIndexPath:. I suppose you could return some kind of template cell at first, and then update it with reloadRowsAtIndexPaths:withRowAnimation: when the hard work is done.
I believe the method giveMeTheTextThatINeed needs to take the current cell as one of the parameters (or another parameter dependent on the cell content), e.g.: [self giveMeTheTextThatINeed:indexPath]. Otherwise you could store the text as an instance variable and set it in all cells from that variable.
So, with that in mind, the easiest way is to store the result of the computation in an additional dictionary, where indexPath (or the other parameter) would be the key:
self.myLabel.text = [self->myDictionary objectForKey:indexPath];
Now, you could either pre-populate that dictionary before the cells are drawn (e.g. in viewWillAppear), or cache them once they are calculated so that they are not recalculated when the cells are scrolled, e.g.:
NSString* calculatedText = [self->myDictionary objectForKey:indexPath];
if(calculatedText == nil)
{
calculatedText = [self giveMeTheTextThatINeed:indexPath];
[self->myDicationary setValue:calculatedText forKey:indexPath];
}
self.myLabel.text = calculatedText;

Looping through UITableViewCells of a UITableView

So I've been trying to load cells from a UITableView and put those into an NSMutableArray to be iterated through later. I have a method called populateArrayWithCells which works fine until I put in more than 6 cells. For some reason the object returned is nil. My code is below, StocksAndAccounts is the UITableView and I have subclassed the UITableViewCells in section 1.
- (void) populateArrayWithCells
{
for (int i = 0; i < [StocksAndAccounts numberOfRowsInSection:1]; i++) {
if ([StocksAndAccounts cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:1]] == nil) {
NSLog(#"object at index %i is nil", i);
}
[stocksCells addObject:[StocksAndAccounts cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:1]]];
}
}
This function, when called through an IBAction, returns the cell successfuly until the index is 6, in which case it is nil and crashes because it's trying to add an object that is nil. Any help with this problem would be greatly appreciated.
I think you've misunderstood how UITableViews work. The cells in a tableview are only loaded when they need to be displayed, and they are recycled, so if you can only see six cells on screen at a time then the table will only ever contain six cells and it will just keep updating and reusing those same six as you scroll up and down.
Instead of storing data in your table cells, store it in an array of custom objects in your view controller and use those objects to populate the cells when the table requests them from your datasource methods.
That way if you ever need to use that data for something other than displaying in the table, you can re-create it from the original object instead of trying to copy it out of the table cell.
You are doing it backwards.
Your NSMutableArray (and preferably a sturdier model than that) should be holding what you want to display in your UITableView, not the other way around. Please review the Model-View-Controller design pattern.

How can a UITableViewCell know of its own indexPath?

The standard Grouped UITableView style allows UITableViewCells to be drawn with rounded corners at the top and bottom of each section. How is this accomplished? How does the cell know its own location within its section, and how does it know when to change its rounded edges?
I want to make my own rounded cells, and I have images to use, but don't know when to show which image
Note: I already know how the UITableView works, and I know how to use it. I just thought that since a UITableView is able to automatically draw rounded corners at the correct places, I should be able to as well, without needing to add anything to my data source or delegate.
NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];
int rows = [(UITableView *)self.superview numberOfRowsInSection:indexPath.section];
if (indexPath.row == 0 && rows == 1) {
// the one and only cell in the section
}
else if (indexPath.row == 0) {
//top
}
else if (indexPath.row != rows - 1) {
//middle
}
else {
//bottom
}
It's very simple. suppose cell is the object, whose position is to be found out.
UITableView* table = (UITableView *)[cell superview];
NSIndexPath* pathOfTheCell = [table indexPathForCell:cell];
NSInteger sectionOfTheCell = [pathOfTheCell section];
NSInteger rowOfTheCell = [pathOfTheCell row];
There is sectionLocation method of UITableViewCell that returns integer telling you what you need:
1 - middle cell
2 - top cell
3 - bottom cell
4 - single cell
I had no issues using this in several production apps since 2010.
UPDATE: one of our binaries was automatically rejected recently (end of 2018) because we were using 'sectionLocation' property, so it's not a good option anymore.
Add something like this into your header files and you can use it:
typedef NS_ENUM(NSInteger, MMMTableViewCellLocation) {
MMMTableViewCellLocationUndefined = 0,
MMMTableViewCellLocationMiddle = 1,
MMMTableViewCellLocationTop = 2,
MMMTableViewCellLocationBottom = 3,
MMMTableViewCellLocationSingle = 4
};
#interface UITableViewCell ()
/** Undocumented method of UITableViewCell which allows to know where within section the cell is located,
* so the cell can draw its borders properly. */
- (MMMTableViewCellLocation)sectionLocation;
/** Override this one to know when the value of sectionLocation changes. */
- (void)setSectionLocation:(MMMTableViewCellLocation)sectionLocation animated:(BOOL)animated;
#end
You can use
- (NSIndexPath *)indexPathForCell:(UITableViewCell *)cell
for this issue. In my example I am using this to scroll the cell (with custom content) to the top of the view.
If you need more robust and general stuff, take a look at http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html - Matt Gallagher shows what you need, pretty effectively. He basically recreates UITableViewController from UIViewController, while adding ability to use your own custom graphics. I'm just working on applying this to one my projects, so far it looks it would do the job.
Unfortunately, I have found no solution to this problem, and have resorted to subclassing UITableViewController and UITableViewCell into a generic solution that I can extend as necessary.
You don't do this in cell. Rounded corners are drawn in [tableView viewForHeaderInSection] and viewForFooterInSection.
The way I do it is to use Plain tableview style, then use these two views for roundness and cells are normal, no rounds.
Without getting into who draws what, you can know which cell is the last cell in its section inside of cellForRowAtIndexPath very easily.
You're passed in the indexPath of the cell you need to provide, right? You're also passed the tableView.
call [tableView numberofRowsInSection:indexPath.section] and if it's == ([indexPath.row]-1) you know you're being asked to supply the last cell in that section.
At the time that cellForRowAtIndexPath is being called, the cell is guaranteed to be at the indexPath passed in.
To expand upon Darren's answer (which I found most useful, thanks Darren!), what you can do is to iterate through all of the superviews' until you find the parent UITableView. This should be future proof since you do not rely on a fixed hierarchy of views.
I use a recursive method that will return the UITableView if it finds one or return nil if there is none.
- (UITableView *)parentTableViewOf:(UIView *)view {
Class class = [view.superview class];
NSLog(#"Class : %#", NSStringFromClass(class));
if([view.superview isKindOfClass:[UITableView class]]) {
return (UITableView *)view.superview;
} else {
return [self parentTableViewOf:view.superview];
}
return nil;
}
So far I've used this one and it seems to work without hiccups. Hope it helps! :)
The cells dont know where they go...The table view has cells, You are the one telling the table view WHAT goes in the cell. You do this in the DataSource where you implement cellForRowAtIndexPath...The way this works :
An index path has a row and a section
For a grouped table view
A section pertains to a group, and a row pertains to 1 entry in that section,
the way UITableView knows how many rows are in a section and how many sections there are is the DataSources methods numberOfSectionInTableView and the method numberOfRowsInSection, this will make the right calls to cellForRowAtIndexPath, it is up to you to recognize which section and row is being queried and you need to build your cell according to these specifications.
A good way to do this i s you can have a Dictionary with keys of section names and values of NSArray with the values that go in that section.
So you implementation for numberOfSectionsInRows would look like
return [[dictionary allKeys] count]
And the implmentation of numberOfRowsInSection would look like
NSString* key=[[dictionary allKeys] objectAtIndex:sectionNumber]
return [[dictionary objectForKey:key] count]
You can always refer to the UITableView programming guide at http://developer.apple.com/iphone/library/documentation/UserExperience/Conceptual/TableView_iPhone/Introduction/Introduction.html
Hope that helps
Simply add a property to your custom UITableViewCell (depending on implementation) class that contains an int, NSNumber, or an NSIndexPath specifying which one it is. In you're using a data structure instead, then put it in you element in that data structure. Then you simply set the property when you create the data structure, something like elt.id=i, and then you access it in the cellForRowAtIndexPath, something like if (elt.id == 0 || elt.id == n-1) where n is the number of rows in your section.
I might have totally missed your question, but if I did, just comment and I'll post again.

iPhone SDK: Inserting and updating a UITableView with a new row

I have a tableView that needs to be updated after information has been inserted from another view. If I perform a
[self.tableView reloadData];
The very next time I insert more information in another view and try to reload the table, all the currently visible rows are duplicated.
In other words, when I start up the app I have:
tableView:
Row 1
Row 2
Then I submit some information that will also show up in the table and suddenly I have:
tableView
Row 1
Row 2
Row 3 <- info I just added
Row 1
Row 2
My numberOfRowsInSection implementation looks like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [ItemsController sharedItemsController].count;
}
My cellForRowAtIndexPath implementation looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemsController* controller = [ItemsController sharedItemsController];
NSMutableArray* recentItems = controller.listOfRecentItems;
CustomCell *cell = nil;
NSUInteger row = [indexPath row];
if( row < recentItems.count )
{
Items* item = [recentItems objectAtIndex:row];
if( recentCellData == nil )
recentCellData = [[NSMutableDictionary alloc] initWithCapacity:[indexPath length]];
if( [recentCellData count] > 0 )
cell = [recentCellData objectForKey:[NSString stringWithFormat:#"%d", row]];
if (cell == nil) {
UIViewController * view1 = [[UIViewController alloc] initWithNibName:#"CustomCell" bundle:nil];
cell = (CustomCell*)[view1 view];
[recentCellData setObject:cell forKey:[NSString stringWithFormat:#"%d",row]];
}
// do some other stuff here
}
// Set up the cell
return cell;
}
What's the best way to update the table and avoid duplicating the currently visible rows.
Thank in advance for all the help!
The error isn't in how you're reloading the table, it's in how you're providing data to it. Set a breakpoint in the data source methods and the method that adds new rows to see where you're going wrong.
You'll only end up with five items if tableView:numberOfRowsinSection: returns 5. Thats the simple answer to your question, but I see other problems here. I'm wondering why you have this test: row < recentItems.count. Is that array the same thing as [ItemsController sharedItemsController].count? You really need to be using the same array for both methods.
(Also, it's not a syntax error, but you shouldn't use the property syntax for things that aren't declared as properties. You should write [recentItems count] instead.)
I'm also confused by the code you use to set up the cell. Cells are meant to be reusable. That is, you create one cell, then reconfigure it every time in your implementation of tableView:cellForRowAtIndexPath:. Your code creates a cell for each item in your list. This is very memory-inefficient, and will likely crash your program due to insufficient memory on the iPhone if you keep lots of cells in memory like this.
The recommended approach is to call dequeueReusableCellWithIdentifier:. If that returns nil, then you set up a cell using the initWithFrame:reuseIdentifier: initializer. The table view is very smart, and will only ask you to redraw the cell when it needs you to.
Your recentCellData dictionary looks really shaky to me, too. What if you insert an item after the item with key #"2"? All the items with key #"3" onward will need to be shifted one element to the right to work the way you expect. That's a ton of bookkeeping that seems rather unnecessary to me. If you really needed something like this -- and to be clear, I don't think you do -- why wouldn't you use an NSMutableArray, which is much easier to use?
I added a bit more info above.