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.
Related
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;
}
So I have a subclass UITableViewCell named MCProductCell, which is loaded from a NIB. The problem is that when the table is released, the dealloc method of my custom cell is not called even once.
Here is some sample code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"MCProductCellIdentifier";
MCProductCell *cell = (MCProductCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Boolean value needed to determine if it is a reused cell or not. If it's not reused we have
// to start the thread that loads the image. For reused cells, that thread is started at the
// end of the scrolling
BOOL recycled = YES;
if (cell == nil) {
NSLog(#"cell alloc");
recycled = NO;
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MCProductCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
MCProduct *product = [products objectAtIndex:indexPath.row];
cell.product = product;
cell.cartViewController = self;
cell.productImage = product.cachedThumbnailImage;
if (product.cachedThumbnailImage == nil) {
cell.productImage = [ViewControllerUtils getDefaultImage];
if (!recycled)
[NSThread detachNewThreadSelector:#selector(loadImage:) toTarget:cell withObject:cell.product.imageThumbnailUrl];
}
return cell;
}
And for some reason, when I first present my UIViewController, that contains the table, the dealloc method of my custom cell is called ONCE.
The problem is that in the dealloc method I want to remove the cell as an observer, and if it isn't called, then the cell isn't removed as an observer.
Also the tableview is an outlet.
I figured out, it must be because the retain count of the cell is not going down to 0.
Which means you have another retain.
My more experienced colleague thinks its because you are using the detachNewThreadSelector, which probably retains the cell.
He suggested you would load the image by using some type of asynchrony image such as
https://github.com/nicklockwood/AsyncImageView/
Good luck.
How is the 'cell.cartViewController' property defined? If it's retaining your controller object (self), then you probably have a retain cycle in there!
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.
I am facing a crashing problem in cellForRowAtIndexPath tableview delegate method
#interface EventListView : UIViewController <UITableViewDelegate, UITableViewDataSource>
IBOutlet UITableView *tblView;
NSMutableArray *arr_EventValues,*arr_Event_Details;
NSMutableArray *arr_EventListDetails;
#property(nonatomic, retain)NSMutableArray *arr_EventValues,*arr_EventListDetails, *arr_Event_Details;
#property(nonatomic, retain)UITableView *tblView;
- (void)viewDidLoad
{
appdelegate = (VibesGuideAppDelegate *)[[UIApplication sharedApplication] delegate];
ViewCalendar = [[CalendarView alloc] initWithNibName:#"CalendarView" bundle:[NSBundle mainBundle]];
[self.navigationController.navigationBar setHidden:YES];
self.arr_Event_Details = [[NSMutableArray alloc] init];
self.arr_EventValues = [[NSMutableArray alloc] init];
[super viewDidLoad];
}
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self.arr_EventListDetails count] > 0)
{
return [self.arr_EventListDetails count];
}
return 0;
}
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
// ------------------------------- Custom cell ------------------------------
Customcell *cell = (Customcell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[[Customcell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.text = #"Hello";
return cell;
}
* -[EventListView tableView:cellForRowAtIndexPath:]: message sent to deallocated instance 0x60e01b0 and I used tableview from xib and set all connections and also got arrayvalues in numberOfRowsInSection in method but in cellForRowAtIndexPath method is not called so please give me idea my issue....
Thanks in advance.
At first it seems your some variable gets released. Make sure you have properly allocated it. If you have declared #property for it you better use the variable as self.variable. You can use retain and in case of IOS 5 strong in property declaration.
Just to be sure you can track if any variable gets released via setting NSZombieEnabled to YES. With zombies enabled, messages to deallocated objects will no longer behave strangely or crash in difficult-to-understand ways, but will instead log a message and die in a predictable and debugger-breakpointable way. You can set NSZombieEnabled by the following steps.
Select Product from the menu bar above. Keep alt/option pressed and select "Test..." or "Run...". Go to the Arguments tab, and add NSZombieEnabled YES in the "Environment Variables" section.
Your tableview itself is already released - the error message says that you send the cellForRowAtIndexPath message to a deallocated instance of the table view - so your problem lies somewhere in not retaining or releasing the EventListView and cannot be seen in the code displayed here.
Check this one:
Table DataSource and delegate is set or not.
Used array in cellForRowAtIndexPath is properly set with property and synthesized as well used with self. name.
First of all you dequeue the cell and then create a new one. This is not a good practice, if you are able to dequeue a cell you should not create a new one. You should have something like this:
Customcell *cell = (Customcell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[Customcell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
fix this and let me know if you are still running on the same problem.
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.