Using dequeueReusableCellWithIdentifier with custom UITableViewCell - iphone

So I'm unsure how this dequeueReusableCellWithIdentifier works and if what I'm looking for is possible. I have a custom UITableViewCell with a BOOL showIcon. In the TableViewCell, if it's true, I show it, otherwise, I don't show this icon on my cell. In my cellForRowAtIndexPath, I grab the object in my array from my model, and set it for the UITableViewCell property.
This works at first on what is visible on my screen. Then as I scroll downwards on the table, it does not work and the values I should be seeing for the showIcon just don't show. And then when I scroll back to the top, the original icons that were there, are not there. Is the dequeueReusableCellWithIdentifier still what I want to be using in this case? Or am I doing something wrong with setting and showing data in it? Thanks a bunch.
CODE:
On my custom UITableViewCell, I have a
BOOL showIcon;
In my cellForRowAtIndexPath method, I use the UINib way of getting my custom UITableViewCell like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
static NSString *OrderTableViewCellIdentifier = #"OrderTableViewCellIdentifier";
OrderTableViewCell *cell = (OrderTableViewCell *)[tableView dequeueReusableCellWithIdentifier:OrderTableViewCellIdentifier];
if (cell == nil) {
UINib *cellNib = [UINib nibWithNibName:#"OrderTableViewCell" bundle:nil];
[cellNib instantiateWithOwner:self options:nil];
cell = self.TbvCell;
[cell.CheckmarkButton addTarget:self action:#selector(CheckmarkButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.TbvCell = nil;
}
Order *order = [orderArray objectAtIndexPath:row];
cell.order = order;
}
Then in my TableViewCell, it's something like this:
#property (nonatomic, retain) Order *order;
#property (nonatomic, retain) UIImageView *icon;
overridden setter:
- (void)setOrder:(Order *)newOrder {
if (!order.showIcon) {
icon.hidden = YES;
}
}

Your code reuse your cell so you have to cover all the case in your setOrder method. Try with:
- (void)setOrder:(Order *)newOrder {
if (!order.showIcon) {
icon.hidden = YES;
}
else {
icon.hidden = NO;
}
}
Or simpler:
- (void)setOrder:(Order *)newOrder {
icon.hidden = !order.showIcon;
}

Yes, dequeueReusableCellWithIdentifier works well with any custom UITableViewCell subclasses. However, keep in mind that this method is for memory saving, so you'll have to do the following (even with not subclassed, normal UITableViewCells):
This method returns an already used UITableViewCell instance (or nil if the table view doesn't have enough cells yet). It means that the cell won't be "empty"; you'll need to clear and re-set all its properties. For example, you'll need to be able do decide from the cell's corresponding NSIndexPath whether its icon has to be displayad, and what icon image you'd like to use at all.
So edit your code like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
static NSString *OrderTableViewCellIdentifier = #"OrderTableViewCellIdentifier";
OrderTableViewCell *cell = (OrderTableViewCell *)[tableView dequeueReusableCellWithIdentifier:OrderTableViewCellIdentifier];
if (cell == nil) {
UINib *cellNib = [UINib nibWithNibName:#"OrderTableViewCell" bundle:nil];
[cellNib instantiateWithOwner:self options:nil];
cell = self.TbvCell;
[cell.CheckmarkButton addTarget:self action:#selector(CheckmarkButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.TbvCell = nil;
}
// set your cell's properties to default values.
// e. g.: cell.showIcon = NO; and so on
Order *order = [orderArray objectAtIndexPath:row];
cell.order = order;
// then do a recustomization using the NSIndexPath's -row and -section properties
}
Hope this will help.

Related

Custom UITableViewCell Not Using .xib (Most Likely Because of Flaw in init Method)

I subclassed the UITableViewCell in order to customize it, but I think I'm missing something because: 1) It's not working and 2) There are a couple of things I'm confused on. Along with customizing the look of the .xib file, I also changed the backgroundView, and that part is working fine. The part that I least understand/am most confused about is the init method, so I posted that here. If it turns out that is correct, please tell me so I can post more code that may be the cause.
This is the init method, which I customized. I'm sort of confused around the "style" idea and I think I'm just returning a normal UITableViewCell with a different backgroundView. I mean, there's nothing in there that refers to the .xib or does anything but change the .backgroundView from the self:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier wait: (float) wait fadeOut: (float) fadeOut fadeIn: (float) fadeIn playFor: (float) playFor
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
CueLoadingView* lview = [[CueLoadingView alloc] initWithFrame:CGRectMake(0, 0, 320, 53)];
self.backgroundView = lview;
[self setWait:wait]; // in turn edits the lview through the backgrounView pointer
[self setFadeOut:fadeOut];
[self setFadeIn:fadeIn];
[self setPlayFor:playFor];
}
return self;
}
Other than the .xib and several setters and getters, this is the only real part of my code, that relates to retrieving a cell.
Additional Information:
1) This is my .xib, which is linked to the class.
2) This is the code that calls/creates the UITableView (the delegate/view controller):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"CueTableCell";
CueTableCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[CueTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier wait:5.0 fadeOut:1.0 fadeIn:1.0 playFor:10.0];
[cell updateBarAt:15];
}
return cell;
}
The easiest way (available since iOS 5.0) to create a custom table view cell in a nib file is to use registerNib:forCellReuseIdentifier: in the table view controller. The big advantage is that dequeueReusableCellWithIdentifier: then automatically instantiates a cell from the nib file if necessary. You don't need the if (cell == nil) ... part anymore.
In viewDidLoad of the table view controller you add
[self.tableView registerNib:[UINib nibWithNibName:#"CueTableCell" bundle:nil] forCellReuseIdentifier:#"CueTableCell"];
and in cellForRowAtIndexPath you just do
CueTableCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CueTableCell"];
// setup cell
return cell;
Cells loaded from a nib file are instantiated using initWithCoder, you can override that in your subclass if necessary. For modifications to the UI elements, you should override awakeFromNib (don't forget to call "super").
You have to load the cell from the .xib instead:
if ( cell == nil ) {
cell = [[NSBundle mainBundle] loadNibNamed:#"CellXIBName" owner:nil options:nil][0];
}
// set the cell's properties
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"CueTableCell";
CueTableCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"CueTableCell XibName" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = [array objectAtIndex:0];
}
return cell;
}

Unable to flush reusable UITableViewCell data

I have a UITableView with a custom UITableViewCell.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//create the cell
MyCell *cell = (MyCell*)[tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
cell.label.text = ..
cell.label2.text = ..
cell.label3.text = ..
Everything works fine, all my data loads properly, etc.
Now, I have a button on this View Controller that opens another view where the user can select which labels to display. So, for instance, display labels 1 and 3, but not 2...
Then, when Done is clicked, I want the tableView to be updated to reflect the new options, but since the cells were loaded with a reuseCellId, no changes are shown. How can I force the cells to recreate?
I thing that the best thing you could do is to store the cells configuration in some kind of structure (a set with the labels indices to be shown would be ok here) and alter this structure with your buttons and reload the table view. Then, on your tableView:cellForRowAtIndexPath: method, you should check that configuration structure in order to know what buttons should be visible.
This code may help:
#interface MyViewController : UIViewController
{
...
NSMutableSet *_labelsToShow;
}
...
#property (nonatomic, retain) NSMutableSet labelsToShow
#end
#implementation MyViewController
#synthesize labelsToShow = _labelsToShow;
- (void)dealloc
{
[_labelsToShow release];
...
}
//you may know which button has to add/remove each label, so this needs to be fixed with your logic
- (IBAction)myButtonAction:(id)sender
{
if (hasToShowLabel)
{
[self.labelsToShow addObject:[NSNumber numberWithInteger:labelIdentifier]];
} else
{
[self.labelsToShow removeObject:[NSNumber numberWithInteger:labelIdentifier]];
}
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"myCell";
MyCustomCell *cell = (MyCustomCell *)[tableView dequeReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault] autorelease];
}
cell.label0.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:0]]);
cell.label1.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:1]]);
cell.label2.hidden = (![self.labelsToShow containsObject:[NSNumber numberWithInteger:2]]);
...
return cell;
}
#end
Good luck with this!
This is NOT a good approach
One way you can do this by using different identifier when you want to refresh cells
I am not sure if there is any other better way of doing this.
I solved this issue by just destroying the tableview, and recreating it every time.

How to reuse a UITableViewCell subclass in IB

I created a UITableViewCell subclass. In the HomeViewController class that currently uses it, I do this:
#interface: (for HomeViewController)
#property (nonatomic, assign) IBOutlet UITableViewCell *customCell;
#implementation:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CustomTableViewCellIdentifier = #"CustomTableViewCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CustomTableViewCellIdentifier];
if (cell == nil) {
UINib *cellNib = [UINib nibWithNibName:#"CustomTableViewCell" bundle:nil];
[cellNib instantiateWithOwner:self options:nil];
cell = self.customCell;
self.customCell = nil;
}
NSUInteger row = [indexPath row];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
In the CustomTableViewCell.xib, my File's Owner is HomeViewController and I connect the outlet from File's Owner to the CustomTableViewCell. All of that works fine.
Now I want to have another subclass of UIViewController called DetailViewController to use this cell also. My File's Owner object is already used. I'm not super familiar with creating other objects in order to reuse this cell. Can someone explain what I need to do in this scenario? Thanks.
First, don't create a UINib object every time. Create it once and reuse it. It will run much faster.
Second, it looks like the only property of File's Owner that you're wiring up is customCell. If that's all you need, it would be simpler not to wire up a connection at all. Instead, make sure the cell is the first or only top-level object in the nib (by making it the first top-level object in the Objects section of the nib outline). Then you can access it like this:
+ (UINib *)myCellNib {
static UINib *nib;
static dispatch_once_t once;
dispatch_once(&once, ^{
nib = [UINib nibWithNibName:#"CustomTableViewCell" bundle:nil];
});
return nib;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CustomTableViewCellIdentifier = #"CustomTableViewCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CustomTableViewCellIdentifier];
if (cell == nil) {
NSArray *topLevelNibObjects = [self.class.myCellNib instantiateWithOwner:nil options:nil];
cell = [topLevelNibObjects objectAtIndex:0];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}

Slow scrolling in UITableView containing UI Elements in each cell

I have 3 segmented controls in each cell of a tableview on an iPad. The orientation is always landscape for the app and the number of cells vary for each run on the app. The app performs fine if the number of rows are around less than 10, but anywhere above that, the glitches start to appear.
For the kind of application I'm building, I could have as many as 70 rows ==> meaning, 210 UISegmentedControls, all alloced in the memory at once.
Is there a work around? Is there a way I can reuse these UISegmentedControls? If yes, how can I preserve the state of the segmented control?
Otherwise, can anybody propose a new solution? (Each segmented control has items 'A' and 'B' and there are three segmented controls representing three different parameters for each object corresponding to each row of the table).
UPDATE:
Here's the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
segmentedControl1 = (UISegmentedControl*)[array1 objectAtIndex:[indexPath row]];
segmentedControl1.frame = CGRectMake(180, 15, 100, 30);
[cell.contentView addSubview:segmentedControl1];
segmentedControl2 = (UISegmentedControl*)[array2 objectAtIndex:[indexPath row]];
segmentedControl2.frame = CGRectMake(450, 15, 100, 30);
[cell.contentView addSubview:segmentedControl2];
segmentedControl3 = (UISegmentedControl*)[array3 objectAtIndex:[indexPath row]];
segmentedControl3.frame = CGRectMake(725, 15, 100, 30);
[cell.contentView addSubview:segmentedControl3];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
Create a custom UITableViewCell subclass. Have three properties in it for your three UISegmentedControls and add them in the init method:
#interfate MyCell : UITableViewCell
#property (nonatomic, retain, readonly) UISegmentedControl *control1;
#property (nonatomic, retain, readonly) UISegmentedControl *control2;
#property (nonatomic, retain, readonly) UISegmentedControl *control3;
#end
#implementation
#synthesize control1 = _control1;
#synthesize control2 = _control2;
#synthesize control3 = _control2;
- (id)init
{
if ((self = [super init]))
{
_control1 = [[UISegmentedControl alloc] init...];
_control1.frame = CGRectMake(...);
[self addSubView:_control1];
// repeat with control2 & control3
}
//...
#end
Then, rather than storing arrays of UISegmentedControls you can then have arrays of NSNumber holding the selected index.
You'd then do something like:
cell.control1.selectedIndex = [[array1 objectAtIndex:indexPath.row] integerValue];
You could also create custom objects to hold this data and store them in one array.
You should be re-using UITableViewCells, which contain three generic UISegmentedControls. When tableView:cellForRowAtIndexPath: is called, you should set the correct values for each segmented control - always.
Those values have to be stored "somewhere else", outside cells, most likely in same place as where you get the other data for cells.
Update with draft code, should not compile as-is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = #"CellId";
static NSString *cellNib = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:cellNib owner:self options:nil];
for (id nibItem in nib)
{
if ([nibItem isKindOfClass:[UITableViewCell class]])
{
cell = (UITableViewCell *)nibItem;
break;
}
}
}
// Configure the cell, all values!
UISegmentedControl *seg = nil;
seg = (UISegmentedControl *)[cell viewWithTag:1];
seg.selectedSegmentIndex = 0
UISegmentedControl *seg = nil;
seg = (UISegmentedControl *)[cell viewWithTag:2];
seg.selectedSegmentIndex = 0
return cell;
}
The idea is that you create a custom UITableViewCell template in Interface Builder with 3 segmented Controls. Give each control a UNIQUE tag id number. Use the tag id to get access to each specific control and setup ALL VALUES - because you are reusing the same cells and by default they will contain old values.
Btw about cell non-selection... Well, there are many ways to do that, wrote even a blog about it "How to Disable UITableCell Selection". Yep, it's old and got title wrong, but should work.
Hope this helps :)
I've never seen a UITableView keep "live" more than one or two cells beyond those visible on the screen. Are you using dequeueReusableCellWithIdentifier to recycle your cells?
I suspect that your problem is something other than segmented controls. A UITableView has built-in methods to take care of loading and reloading classes as they go off-screen, so all 210 controls should not be in memory at once.
I would start by checking to make sure that you are using dequeueReusableCellWithIdentifier: correctly (especially if each UITableViewCell is the same class). Maybe also check for memory leaks.
You can allocate the segmented controls in your cell for row at index path when the cells are being created.
Allocate them in cell for row at index path. Allocate and add the segmented controls under the (cell == nil) condition.
Or
You can reuse 3 segmented controls for all the cells. And hold an array for keeping the changed values.

iPhone table view - problem with indexPath.row

I'm using indexPath.row do determine in which row of my tableview I do something. The title of my cells is containing a number which should be 1 in the first row and 18 in the last row, so I have 18 rows. This works for the first 11 rows, but after that, I have numbers in the title which seem to be generated randomly! Sometimes 16, then 5, then 18, then 12... and so on.
What's the problem with it/why does the indexPath.row variable behave like that?
My cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"myCell" owner:self options:nil];
cell = cell0;
self.cell0 = nil;
}
UILabel *label;
label = (UILabel *)[cell viewWithTag:1];
label.text = [NSString stringWithFormat:#"Cell %d", indexPath.row];
return cell;
}
Any more suggestions on how to solve the problem? I didn't get it working until now...
// Update with more code:
Here is how I declare the cell. It is in an XIB file (template "empty XIB") in which I just put the cell from the library in IB.
#interface myViewController : UITableViewController {
UITableViewCell *cell0;
}
#property (nonatomic, retain) IBOutlet UITableViewCell *cell0;
Then, at the top of the myViewController.m file:
#synthesize cell0;
My cellForRowAtIndexPath method is already posted above. It is equal to the cellForRowAtIndexPath method in the SDK documentation, and in Apple's example, it seems to work.
What are you trying to accomplish with cell0?
cell = cell0;
self.cell0 = nil;
It looks like you're creating a new cell, but somehow deciding to use an old one. The real culprit looks like the code that is loading the cell actually getting assigned anywhere.
Try just this instead:
if (cell == nil) {
cell = [[NSBundle mainBundle] loadNibNamed:#"myCell" owner:self options:nil];
}
Or perhaps:
if (cell == nil)
{
// TODO: try to avoid view controller
UIViewController *vc = [[UIViewController alloc] initWithNibName:#"IndividualContractWithResult" bundle:nil];
cell = (IndividualContractWithResult_Cell *) vc.view;
[vc release];
}
To would be easier to answer if you give the code where you create cells for your table view. It looks that there's a problem with reusing cells - you reuse previously created cells without setting a new value to it.
It sounds like you are not re-using cells but creating new ones when there are cells available. Look at the sample code for dequeueReusableCellWithIdentifier.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"MyCell"] autorelease];
}
cell.text = <your code here>;
return cell;
}
It would seem that you're incorrectly accessing a property here:
cell = cell0;
self.cell0 = nil;
Assuming that you have an instance variable named cell0, by setting it to nil, you may be releasing it before you're ready to use it.
The proper way to do this is:
cell = self.cell0;
self.cell0 = nil;
This way, if cell0 is declared as retain, you'll automatically get an autoreleased cell0 back, whereas if you reference cell0 directly (no self.), you'll get an unretained reference, which will disappear when self.cell0 = nil is called.
The advantage of using a nib-based cell here is that you can use outlets, rather than tags, to identify subviews. You've done the heavy lifting already, you might want to just add an outlet and subclass UITableViewCell to get access to the label.
You will need to retain and autorelease cell0, otherwise when you set self.cell0 = nil, then cell0 has no known references.
cell = [[cell0 retain] autorelease];
self.cell0 = nil;
You can also do this:
cell = self.cell0;
self.cell0 = nil;
.. Since any retain properties should implement their getters with the retain/autorelease pattern.