I've created a table view in an iPhone app using Interface Builder (IB). My table style is set to Grouped, and displays that way in IB. However, whenever I build and run, it shows up as a Plain style in the simulator.
I have another view set to Grouped and don't experience this problem. Has anyone run into this problem before?
The rest of the view is not created programmatically, so I'd like to avoid doing that for this view. There must be something I'm missing.
The only tableView method I'm doing much of anything in is the cell handler method where I'm incorporating a text box into select fields:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
}
if (indexPath.section == 0) {
if (indexPath.row == 0) {
cell.textLabel.text = #"Title";
UITextField *listTitleTextField = [ [ UITextField alloc ] initWithFrame: CGRectMake(150, 10, 145, 38) ];
listTitleTextField.adjustsFontSizeToFitWidth = YES;
listTitleTextField.textColor = [UIColor blackColor];
listTitleTextField.font = [UIFont systemFontOfSize:17.0];
listTitleTextField.placeholder = #"Your Title";
listTitleTextField.backgroundColor = [UIColor whiteColor];
listTitleTextField.autocorrectionType = UITextAutocorrectionTypeNo; // no auto correction support
listTitleTextField.autocapitalizationType = UITextAutocapitalizationTypeNone; // no auto capitalization support
listTitleTextField.textAlignment = UITextAlignmentRight;
listTitleTextField.keyboardType = UIKeyboardTypeDefault; // use the default type input method (entire keyboard)
listTitleTextField.returnKeyType = UIReturnKeyDone;
listTitleTextField.tag = 0;
listTitleTextField.delegate = self;
listTitleTextField.clearButtonMode = UITextFieldViewModeUnlessEditing; // no clear 'x' button to the right
if (self.wishList.listTitle == nil || [self.wishList.listTitle length] == 0) {
listTitleTextField.text = #"";
}
else {
listTitleTextField.text = self.wishList.listTitle;
}
[listTitleTextField setEnabled: YES ];
[cell addSubview: listTitleTextField ];
[listTitleTextField release];
}
else if (indexPath.row == 1) {
cell.detailTextLabel.text = #"Pick an Icon";
cell.textLabel.text = #"List Icon";
}
}
else {
cell.textLabel.text = #"Add Wish";
}
return cell;
}
We had this and it stumped us for about an hour. Finally we found that we had failed to set the NIB Name property of the view controller inside the navigation controller (inside the tab bar controller, in our main XIB!). Without that set, all the changes we made to the XIB file of our table view were completely ignored. Yet the code worked fine otherwise, and there was very little to point us to our mistake.
I bet something similar is going on in your case. Make sure your XIB (where you set the table style) is actually being used.
What is your controller's base class? I had the same problem, until I switched my controller from subclassing a UITableViewController to just a standard UIViewController. It would seem if you're using a UITableViewController, it has its own built-in UITableView and ignores the one you specify in IB.
Still not sure why the "grouped" style setting is not taking effect from the Interface Builder. However, you can manually set it before the view is created here:
- (id)initWithStyle:(UITableViewStyle)style {
// Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
style = UITableViewStyleGrouped;
if (self = [super initWithStyle:style]) {
}
return self;
}
When initialising the view just do the following:
CustomTableViewController *viewController = [[CustomTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
Rather than just init. This solves it for me.
You need to specify the Nib name when you create the class or else it will not look for the default nib named the same as your class. For some reason Apple decided to include its own table view as a default.
For example if your class name is "Settings":
settingsController = [[Settings alloc] initWithNibName:#"Settings" bundle: nil];
Does the table view respond to events and get populated with data as you'd expect? - it sounds like the outlets are not connected properly to me. I'd double check the datasource and delegate connections from the tableview to the controller. May be worth deleting the tableview from IB and re-adding it and re-connecting it as well. I've seen IB act a little finicky in the odd case.
Related
I'm creating a Settings View for my app, and in that view is a UITableView. I'm creating custom cells to meet my needs, but I'm having issues - only the last cell is getting [layoutSubviews]. Am I doing something wrong?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//int type = (indexPath.row == 0?1:0);
//if(indexPath.row == 6) type = 2;
NSLog(#"row %i created", indexPath.row);
TableCell *cell = [[TableCell alloc] initWithType:indexPath.row];
cell.textLabel.text = #"Test cell";
return cell;
}
And in my custom cell:
#implementation TableCell
UIImageView *shadowView;
int row;
- (id) initWithType:(int)type {
row = type;
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
self.backgroundColor = [UIColor clearColor];
self.backgroundView = [[UIView alloc] init];
UIImage *shadowImage = [UIImage imageNamed:#"CellShadow"];
shadowImage = [shadowImage resizableImageWithCapInsets:UIEdgeInsetsMake(14, 14, 14, 14)];
shadowView = [[UIImageView alloc] initWithImage:shadowImage];
[self.contentView addSubview:shadowView];
//[self.contentView sendSubviewToBack:shadowView];
NSLog(#"agreed, row %i created", row);
[self layoutSubviews];
return self;
}
- (void) layoutSubviews {
NSLog(#"row: %i", row);
[super layoutSubviews];
shadowView.frame = CGRectMake(
0, 0,
self.contentView.frame.size.width,
self.contentView.frame.size.height
);
}
#end
Continuously, only the last cell #6, is reported when I rotate, or when layoutSubviews should be called. Any suggestions?
Do not call layoutSubviews directly. Use [self setNeedsLayout] or [self layoutIfNeeded]. But do not call these at all in the cell's init method.
Also, do not call [[TableCell alloc] initWithType:indexPath.row]; directly, either. Instead, use...
- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath
Once you've built that cell, you can tell it it's row, but be aware that the cells get recycled as the table scrolls, so you must update that value on every call to cellForRowAtIndexPath.
The cells ought to get layout again (without you making any calls direct or indirect) when the table view is resized.
See the tableview doc here.
You should never call layoutSubviews directly, it will be called automatically by iOS once the cell is ready to display. You should also deque the cell as #danh is recommending. If you're not very comfortable with all this, then I'd really recommend you have a look at the free Sensible TableView framework, which automates creating these kind of settings views (I create mine in a couple of lines, really).
The issue was of my own poor code. Using cell.backgroundView helped a lot here.
Never Call layoutSubviews by yourself. It will be called when ever frames of subview in cell are changed. Even if just change the text of labels in your custom cell wont call layoutSubviews. Ue the deque of cells for reusing for better performance. As it wont allocate cell every time. And in you code looks like has lot of memory issues since cell allocated wont be released and new cell is created.
I have an app consisting of a TabBar with a few TabBarControllers. One Controller contains a very simple table, which is supposed to display the contents of a NSMutableDictionary. When you hit the appropriate button, the Dictionary is updated in a separate Controller and the view switches to the UITableViewController, displaying the newly updated table.
I can see the Dictionary being updated. But the TableView never reflects the changes. In fact, it seems to display the changes only the 1st time I enter that screen.
I have tried [self table.reloadData] and while it gets called, the changes aren't reflected to the UITableView.
Does anyone have any suggestions? I am happy to post code, but am unsure what to post.
Update: the table is updated and refreshed properly only the 1st time it is displayed. Subsequent displays simply show the original contents.
Background:
The tableview gets filled from a dictionary: appDelegate.currentFave. The tableview should get refreshed each time the ViewController is invoked by the TabBarController.
- (void)viewWillAppear:(BOOL)animated
{
NSLog(#"in viewWillAppear");
[super viewWillAppear:animated];
[self loadFavesFile];
[self.tableView reloadData];
}
// load the Favorites file from disk
- (void) loadFavesFile
{
// get location of file
NSString *path = [self getFavesFilePath];
// The Favorites .plist data is different from the Affirmations in that it will never be stored in the bundle. Instead,
// if it exists, then use it. If not, no problem.
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
// read Faves file and store it for later use...
NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
appDelegate.sharedData.dictFaves = tempDict;
// grab the latest quote. Append it to the list of existing favorites
NSString *key = [NSString stringWithFormat:#"%d", appDelegate.sharedData.dictFaves.count + 1];
NSString *newFave = [NSString stringWithFormat:#"%#", appDelegate.currentFave];
[appDelegate.sharedData.dictFaves setObject:newFave forKey:key];
} else {
NSLog(#"Favorites file doesn't exist");
appDelegate.sharedData.dictFaves = nil;
}
}
// this gets invoked the very first call. Only once per running of the App.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// reuse or create the cell
static NSString *cellID = #"cellId";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
// allow longer lines to wrap
cell.textLabel.numberOfLines = 0; // Multiline
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.font = [UIFont fontWithName:#"Chalkduster" size:(16)];
cell.textLabel.textColor = [UIColor yellowColor];
// NOTE: for reasons unknown, I cannot set either the cell- or table- background color. So it must be done using the Label.
// set the text for the cell
NSString *row = [NSString stringWithFormat:#"%d", indexPath.row + 1];
cell.textLabel.text = [appDelegate.sharedData.dictFaves objectForKey:row];
return cell;
}
I found the problem. I was not properly initializing and assignng the TableView in my view controller. See below
- (void)viewDidLoad
{
[super viewDidLoad];
tableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
tableView.backgroundColor=[UIColor blackColor];
self.view = tableView;
}
Assuming the code you have put up is correct, you want to use [self.table reloadData]. You have the . in the wrong place.
I had this same problem yesterday, for me it turned out I had set the wrong file owner in interface builder and hadn't set up the data source and delegates for the table view properly.
Try going into interface builder and right-clicking on the file owner, this should show you if anything isn't connected up properly.
You should make sure that your Interface Builder connections are set up properly, but what this problem really sounds like is that you have your UITableViewCell setup code in cellForRowAtIndexPath: inside your if(cell == nil) statement. Which it shouldn't be. Let me explain. If you have a list of cells, and you want to set the titles to each cell to a string in an array called myArray, right now your (incorrect) code looks like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
}
return cell;
}
Can you see the problem with that logic? The cell will only get an updated title if no reusable cell can be found, which, in your case, sounds like the situation. Apple says that you should create a 'new' cell each time cellForRowAtIndexPath: is called, which means that you put all of your setup code outside of the if(cell == nil) check.
Continuing with this example, the proper code would look like this:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
if (cell == nil) {
// No cell to reuse => create a new one
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"cellIdentifier"] autorelease];
}
[[cell textLabel] setText:[myArray objectAtIndex:[indexPath row]]];
return cell;
}
This way, the cell gets assigned the proper string whether or not a reusable cell is found and so calling reloadData will have the desired effect.
Background
have a custom UITableCellView in which I'm using a custom UITextField that I've added as a subview (i.e. not using normal UITableCellView views
in the scenario is pressing on the cell => jump to screen to modify value (via pushViewController / navigationControl). Then after changing hitting the BACK button to go back to the UITableView
using this approach there is no specific call back for that scenario, so I've been using the approach where you trap this using the general viewDidAppear method of the UITableViewController - the technique I'd used to update the change was:
Code:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.myNormalCell.textLabel.textColor = myColor;
[self.tableView reloadData];
}
But what I note is that the above code:
works for for normal/existing fields in a UITableViewCell
does NOT work for my custom textField subviews I've put in my custom UITableViewCell
QUESTION - How to, in this use case, get my custom fields to be udpated/shown on the UITableView when I come back to it after making a change?
For example:
do I need to somehow set my custom field/subview as "needs to be updated"?
do I need to override reloadData somewhere/somehow to set the custom field?
EDIT: Add some code:
CODE FROM cellForRowAtIndexPath
(a) Code that works with standard UITableViewCell
self.lookAheadDaysCell = (ConfigCell*)[tableView dequeueReusableCellWithIdentifier:#"LookAheadWeeks"];
if (self.lookAheadDaysCell == nil) {
self.lookAheadDaysCell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"LookAheadWeeks"] autorelease];
self.lookAheadDaysCell.textLabel.text = #" Weeks into Future";
self.lookAheadDaysCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
self.lookAheadDaysCell.detailTextLabel.text = [self.weConfig.lookAheadWeeks stringValue];
return self.lookAheadDaysCell;
(b) Code that doesn't work with custom field in custom cell
self.lookAheadDaysCell = [ConfigCell cellForReuseId:#"LookAheadWeeks"
TableView:tableView
Title:#"Number of Weeks"
ShowHelp:true
Tag:9999
Delegate:self
UseTextField:false
ContentText:[self.weConfig.lookAheadWeeks stringValue]];
return self.lookAheadDaysCell;
CODE FROM CUSTOM CELL
Interface:
#interface ConfigCell : UITableViewCell <UITextFieldDelegate> {
UITextField *_textField;
UILabel *_titleLabel;
}
#property (nonatomic, retain) UITextField *textField;
#property (nonatomic, retain) UILabel *titleLabel;
+ (ConfigCell*) cellForReuseId:(NSString *)reuseId TableView:(UITableView*)tableView Title:(NSString*)titleStr ShowHelp:(BOOL)showHelp Tag:(NSInteger)tag Delegate:(id)delegate UseTextField:(BOOL)useTextField ContentText:(NSString*)text;
#end
Key Methods:
+ (ConfigCell*) cellForReuseId:(NSString *)reuseId TableView:(UITableView*)tableView Title:(NSString*)titleStr ShowHelp:(BOOL)showHelp Tag:(NSInteger)tag Delegate:(id)delegate UseTextField:(BOOL)useTextField ContentText:(NSString*)text
{
// Get Cell (via reuse or create a new one)
ConfigCell *cell = (ConfigCell*)[tableView dequeueReusableCellWithIdentifier:reuseId];
if (cell == nil) {
// Create Cell
cell = [[[ConfigCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseId] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// View 1 - Title Label
<<cut>>
// View 2 - TextField for entry
cell.textField = [[[UITextField alloc] initWithFrame:CGRectMake(0,0,0,0)] autorelease];
cell.textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
cell.textField.font = [UIFont systemFontOfSize:16];
cell.textField.textAlignment = UITextAlignmentLeft;
cell.textField.text = text;
if (useTextField) {
cell.textField.delegate = delegate;
cell.textField.tag = tag;
cell.textField.returnKeyType = UIReturnKeyDone;
} else {
cell.textField.userInteractionEnabled = false;
}
[cell.contentView addSubview:cell.textField];
}
return cell;
}
well I did fix it by putting the following line in "viewDidAppear"
self.lookAheadDaysCell.textField.text = [self.weConfig.lookAheadWeeks stringValue];
however it doesn't really help me understand why I didn't need this line when I was using the standard text fields in a UITableViewCell before...noting I'm effectively setting the value of the cell text by reference
I have a custom UITableView cell that I've added a textbox to for editing, that shows and hides based on the edit mode. I've also tried adding a vertical line that shows when editing, and it does that, but I'm running into some drawing issues. I just added a green checkmark rightView to start working on input validation feedback, and I'm seeing similar issues.
Here is the code for the cell, and part of my cellForRowAtIndexPath.
#import <UIKit/UIKit.h>
#interface EditableCellStyle2 : UITableViewCell {
CGRect editRect;
UITextField *editField;
UIView *lineView;
}
#property (nonatomic, readonly, retain) UITextField *editField;
#property (nonatomic, readonly, retain) UIView *lineView;
#end
#import "EditableCellStyle2.h"
#implementation EditableCellStyle2
#synthesize editField;
#synthesize lineView;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code.
editRect = CGRectMake(83, 12, self.contentView.bounds.size.width-83, 19);
editField = [[UITextField alloc] initWithFrame:editRect];
editField.font = [UIFont boldSystemFontOfSize:15];
editField.textAlignment = UITextAlignmentLeft;
editField.textColor = [UIColor blackColor];
editField.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:editField];
self.editField.enabled = NO;
self.editField.hidden = YES;
lineView = [[UIView alloc] initWithFrame:CGRectMake(80, 0, 1, self.contentView.bounds.size.height)];
self.lineView.backgroundColor = [UIColor lightGrayColor];
[self.contentView addSubview:lineView];
self.lineView.hidden = YES;
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state.
}
-(void)layoutSubviews
{
[super layoutSubviews]; // layouts the cell as UITableViewCellStyleValue2 would normally look like
editRect = CGRectMake(83, 12, self.contentView.frame.size.width-self.detailTextLabel.frame.origin.x-10, 19);
editField.frame = editRect;
}
- (void)willTransitionToState:(UITableViewCellStateMask)state {
[super willTransitionToState:state];
if (state & UITableViewCellStateEditingMask) {
self.detailTextLabel.hidden = YES;
self.editField.enabled = YES;
self.lineView.hidden = NO;
self.editField.hidden = NO;
}
}
- (void)didTransitionToState:(UITableViewCellStateMask)state {
[super didTransitionToState:state];
if (!(state & UITableViewCellStateEditingMask)) {
self.editField.enabled = NO;
self.editField.hidden = YES;
self.lineView.hidden = YES;
self.detailTextLabel.hidden = NO;
self.editField.text = self.detailTextLabel.text;
}
}
- (void)dealloc {
[editField release];
[lineView release];
[super dealloc];
}
#end
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// handling every section by hand since this view is essentially static. Sections 0, 1, 2, and 4 use a generic editable cell.
// Section 3 uses the multiline address cell.
static NSString *CellIdentifier = #"Cell";
EditableCellStyle2 *cell = (EditableCellStyle2 *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (indexPath.section == 0 || indexPath.section == 1 || indexPath.section == 2 || indexPath.section == 4) {
if (cell == nil) {
cell = [[[EditableCellStyle2 alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease];
}
}
// Configure the Odometer
if (indexPath.section == 0) {
NSArray *array = [sectionsArray objectAtIndex:indexPath.section];
NSDictionary *dictionary = [array objectAtIndex:indexPath.row];
cell.textLabel.text = #"Odometer";
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", [dictionary objectForKey:#"Odometer"]];
cell.tag = kOdometer;
cell.editField.text = cell.detailTextLabel.text;
cell.editField.placeholder = #"Odometer";
cell.editField.tag = kOdometer;
cell.editField.keyboardType = UIKeyboardTypeNumberPad;
// Create a view for the green checkmark for odometer input validation and set it as the right view.
UIImage *checkImage = [UIImage imageNamed:#"tick.png"];
UIImageView *checkImageView = [[[UIImageView alloc] initWithImage:checkImage] autorelease];
cell.editField.rightView = checkImageView;
cell.editField.rightViewMode = UITextFieldViewModeAlways;
}
return cell;
}
There is more to it but all the cells are built the same way.
The problems are that, when in edit mode, the vertical lines will display properly. When I leave edit mode, any cells that were off screen when I go to normal mode still have the vertical line (it doesn't get hidden). Also, now that I've added the imageView for the checkmark indicator, any cells that are off screen when switching modes gain the checkmark. (only section 0 sets it up).
I've also noticed that if i do cell.setNeedsDisplay, the text label and detail text label won't update if the data source has been updated. I have to do [self.tableView reloadData] which skips any active animations.
I'm sure these issues are related to me using a custom cell + dequeueReusableCellWithIdentifier, but I can't find exactly what.
Any feedback or a push in the right direction would be appreciated.
Edit:
Not using reusable cells seems to have resolved the above issues. I'm still open to feedback on the cell code.
I forgot one other issue that may or may not be related. One of my cells has a "tap to view list" button. If I enter data into the cells while in edit mode, then hit that button to choose some info from a list (it displays a modal table view), when I dismiss the modal view, all of the cells' edited data has reverted to their original state. I'm not calling reload data when I dismiss the modal view controller. I thought this might be fixed by not using reusable cells but it isn't.
You need to prepare the cell for reuse. Try adding this to the EditableCellStyle2 implementation:
- (void)prepareForReuse {
[super prepareForReuse];
[self didTransitionToState:UITableViewCellStateDefaultMask];
}
Maybe you trimmed too much for your post, but in the posted code your reusable cell handling is all wrong.
First of all, each different type of cell needs its own CellIdentifier. In your case (judging from your code comment), that means at least a different identifier for section 3 versus sections 0, 1, 2, and 4. You may also want to do a separate identifier for section 0, so you don't have to keep removing and readding that checkmark. The different identifier needs to be used for both the dequeueReusableCellWithIdentifier: and initWithStyle:reuseIdentifier:` for the appropriate sections.
The second problem is that you are not resetting the cells correctly. There are two "kinds" of initialization that must be done to a UITableViewCell: initialization that is the same for every cell of its type, and initialization that depends on the specific row being displayed. The first kind can (and should) only be done once, when a new cell is allocated. The second kind must be done every time through tableView:cellForRowAtIndexPath:. You seem to be doing the first correctly for your EditableTableCell2 class in its init method, but I see nowhere in there where you do the per-row initialization: you never reset selected, or the cell state, or the contents of the edit field, or remove the checkImageView since you are using the same kind of cell for section 0 versus the other sections. If you want, the reset selected, state, and clearing out the checkbox image and field contents can be done in prepareForReuse on your EditableTableCell2 class.
The third problem, which is almost certainly due to over-trimming, is that you never create this "multiline address" cell for section 3. You'll end up maybe reusing a random EditableTableCell2, or maybe crashing on an exception from the framework when you return nil from tableView:cellForRowAtIndexPath:.
I have a UITableView where I have the backgroud color set via
UIView *myView = [[UIView alloc] init];
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
cell.backgroundView = myView;
[myView release];
The problem I find is that when I edit a table (via setEditing:YES...) some cells of the same color invariable are next to each other. How do I force UITableView to fully redraw. reloadData is not doing a great job.
Is there are deep-cleaning redraw?
I had this issue before so I'll share with you how I solved it:
You can use a boolean flag (say it's called needsRefresh) to control the behavior of cell creation in -cellForRowAtIndexPath:
An example:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell || needsRefresh) {
cell = [[[UITableViewCell alloc] init....] autorelease];
}
//.....
return cell;
}
So, when you need a hard reload, set the needsRefresh flag to YES. Simple as a pimple.
For me the accepted answer didn't really work since I had no idea when to set the needsRefresh back to YES.
What worked for me was:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:customCellIdentifier];
if(nil == cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:customCellIdentifier];
}
//.....
return cell;
}
And then you change the customCellIdentifier value whenever you need to. This way the cells are also still reusable if you switch back to the original cell identifier.
The accepted method seems dirty, it just makes a bunch of new cells that are stored along with the bad ones. Here are a couple of solutions depending on your situation:
1.
first, for the situation described in the question you should not dump your cells and create new views on every cycle. You need to tag your view and then get it back when from the cell when you get a reuse cell:
- (UITableViewCell*) tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath {
UITableViewCell *cell = [tableView dequeueResuableCellWithIdentifier:SOME_ID];
if(!cell) {
cell = [[UITableViewCell alloc] init];
UIView *myView = [[UIView alloc] init];
cell.backgroundView = myView;
[myView setTag:5]; //<------
}
UIView *myView = [cell viewWithTag:5]; //<------
if ((indexPath.row % 2) == 0)
myView.backgroundColor = [UIColor greenColor];
else
myView.backgroundColor = [UIColor whiteColor];
return cell;
}
//then just reload the tableview.
2.
...or even better, why not just use the cell backgrouncolor and update that without creating a view.
3.
A sure way to really clear out old cached cells it to simply recreate the UITableView object.
4.
In most cases you dont need to destroy these cells, just keep track of your elements and update them after getting the reusable cell.You can tag all your elements, keep a array reference to them, find them thought the view hierarchy... Im sure theres a bunch of other ways.
5.
heres a one liner to directly purge all cells, although not best practice to mess with the internals of objects like this as they might change in future versions:
[(NSMutableDictionary*)[tableview valueForKey:#"_reusableTableCells" ] removeAllObjects];
I was able to solve this by adding a refresh variable to the table datasource. I used a dictionary for each cell, but there's an extra key called #"refresh":#"1", indicating the cell needs refreshing. Once it's updated, I set that key's value to #"0". So whenever the table is reloaded, make sure the key goes back to #"0" again.
#define TABLE_VIEW_CELL_DEFAULT_ID #"cellIdentifier"
#property (nonatomic, strong) NSString *tableViewCellIdentifier;
#property (nonatomic) NSUInteger tableViewCellIdentifierCount;
// By using a different cell identifier, this effectively flushes the cell
// cache because the old cells will no longer be used.
- (void) flushTableViewCellCache
{
self.tableViewCellIdentifierCount++;
self.tableViewCellIdentifier = [NSString stringWithFormat:#"%#%i", TABLE_VIEW_CELL_DEFAULT_ID, self.tableViewCellIdentifierCount];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.tableViewCellIdentifier];
if (cell == nil) {
cell = [[MyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:self.tableViewCellIdentifier];
}
// rest of method...
}