Reuse Cell with Subview (programmatically) - iphone

I'm having an issue programmatically adding a subview to a UITableView cell. I am attempting to add a UITextFiled to 2 cells and a UILabel to another cell in one section of my table. The table has 3 sections and most of it is manually created. One of the cells, when selected calls an action that inserts four more cells. If the same cells is selected again, the four cells will be removed. When this action occurs, the subviews get mixed up and are on the wrong cells. The keyboard also does not respond to the done key.
I try to create the subviews inside the if (cell == nil) conditional statement. Here is my code that creates the cells (sorry for it being so messy, I've been trying a ton of things):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSLog(#"cell created");
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
if (indexPath.section == 0 && indexPath.row == 0 && ([cellText objectAtIndex:indexPath.row] == #"Weight")) {
exerciseWeight = [[[UITextField alloc] initWithFrame:CGRectMake(0, 12, 295, 30)] autorelease];
exerciseWeight.textAlignment = UITextAlignmentRight;
exerciseWeight.adjustsFontSizeToFitWidth = YES;
exerciseWeight.textColor = [UIColor blackColor];
exerciseWeight.backgroundColor = [UIColor clearColor];
exerciseWeight.tag = 89899;
exerciseWeight.keyboardType = UIKeyboardTypeNumberPad;
[exerciseWeight setReturnKeyType:UIReturnKeyDone];
exerciseWeight.clearButtonMode = UITextFieldViewModeNever;
[exerciseWeight setEnabled: YES];
[exerciseWeight setDelegate:self];
[cell addSubview:exerciseWeight];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryNone;
} else if (indexPath.section == 0 && indexPath.row == 1) {
NSLog(#"Created metric subview");
metric = [[[UILabel alloc] initWithFrame:CGRectMake(100, 5, 180, 30)] autorelease];
metric.textAlignment = UITextAlignmentRight;
metric.textColor = [UIColor grayColor];
cell.selectionStyle = UITableViewCellSelectionStyleGray;
metric.tag = 89898;
[cell addSubview:metric];
} else if (([cellText count] - 1) == indexPath.row){
exerciseReps = [[[UITextField alloc] initWithFrame:CGRectMake(0, 12, 295, 30)] autorelease];
exerciseReps.textAlignment = UITextAlignmentRight;
exerciseReps.adjustsFontSizeToFitWidth = YES;
exerciseReps.backgroundColor = [UIColor clearColor];
exerciseReps.tag = 89890;
exerciseReps.keyboardType = UIKeyboardTypeNumberPad;
[exerciseReps setReturnKeyType:UIReturnKeyDone];
exerciseReps.clearButtonMode = UITextFieldViewModeNever;
[exerciseReps setEnabled:YES];
[exerciseReps setDelegate:self];
[cell addSubview:exerciseReps];
} else if (indexPath.section == 2) {
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
} else {
exerciseWeight = (UITextField *)[cell viewWithTag:89899];
metric = (UILabel *)[cell viewWithTag:89898];
exerciseReps = (UITextField *)[cell viewWithTag:89890];
}
if (indexPath.section == 0) {
cell.textLabel.text = [cellText objectAtIndex:indexPath.row];
}
// Configure the cell...
if (indexPath.section == 0 && indexPath.row == 0) {
cell.textLabel.textAlignment = UITextAlignmentLeft;
exerciseWeight.placeholder = #"160";
} else if (indexPath.section == 0 && indexPath.row == 1) {
metric.text = metricUnit;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if (cell.textLabel.text == #"Repetitions"){
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryNone;
exerciseReps.placeholder = #"10";
} else if (indexPath.section == 1) {
cell.textLabel.text = #"Log New Set";
cell.textLabel.textAlignment = UITextAlignmentCenter;
cell.accessoryType = UITableViewCellAccessoryNone;
} else if (indexPath.section == 2) {
cell.textLabel.text = #"History";
} else if (cell.textLabel.text == #" Pounds") {
cell.textLabel.textColor = [UIColor grayColor];
cell.textLabel.font = [UIFont systemFontOfSize:14];
} else if (cell.textLabel.text == #" Kilograms") {
cell.textLabel.textColor = [UIColor grayColor];
cell.textLabel.font = [UIFont systemFontOfSize:14];
} else if (cell.textLabel.text == #" Miles") {
cell.textLabel.textColor = [UIColor grayColor];
cell.textLabel.font = [UIFont systemFontOfSize:14];
} else if (cell.textLabel.text == #" Kilometers") {
cell.textLabel.textColor = [UIColor grayColor];
cell.textLabel.font = [UIFont systemFontOfSize:14];
}
return cell;
// [exerciseWeight release];
// [metric release];
// [exerciseReps release];
}

looks like you're going to have some fun here. But it will be satisfying once you're done :-)
I'm not sure the code you posted is the bit you need to be worrying about. Defining what you see in your table (primarily) are methods like heightForRowAtIndexPath, numberOfSectionsInTableView and numberOfRowsInSection. If you get those right you will get the display you want. So when you tap a cell you will be modifying your underlying data structures, then calling reloadData - causing the table view to call those methods all over again.
Regarding laying out those cells I don't see you using the cell's contentView at all. Have you taken a look at the Cocoa With Love blog, specifically Easy custom UITableView drawing and UITableView construction, drawing and management (revisited)? I have also used the xib method of defining table view cells as described in the Apple docs, Table View Programming Guide for iOS and given the way Xcode just keeps improving interface builder's interaction with code would probably continue to do so. I was very much against IB but now see the value in using it since Xcode 4 took away the pain of switching apps to use it, there are a lot fewer opportunities to make mistakes.
What I'm thinking is that when you "add a view" to a cell you should just switch to another cell that you have defined and load the cell, after reloadData, as required. When you want to add cells you change your underlying data structures and let the table display itself.
On the question of cell identifiers - you can either use one cell identifier and save only the cost of creating a cell, factoring out everything common that you do to all your cells, OR you can create cells with a unique identifier per type of cell and reuse cell variant TK421 when your section/row indicates a TK421 is required. I tend to do the second. I don't actually know which one performs better, but I'm sure that either is better than no reuse at all.

Related

Mixing UITextView and plain Title/Right Detail cells in same iOS iPhone app tableview

I've researched many StackOverflow.com questions, and there seems to be a lot of
confusion over how to get a UITextView displayed into a cell of a Table(View). Here's what
I want: a table where cell rows 0 and 3, for example, are simple Title/Right Detail text cells, and where cells (rows) 1 & 2 have embedded UITextViews that display a clickable phone number and a clickable URL for a website, respectively.
My first attempt set up and allocated UITextView subViews outside of the "if (cell == nil)"
code block:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize infoTableData;
#synthesize infoDetailTableData;
#synthesize cellImages;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.infoTableData = [[NSMutableArray alloc] initWithObjects: #"Address", #"Telephone", #"Website", #"Hours", #"Reservations", #"Parking", #"Takeout", #"Cash only?", nil];
self.cellImages = [[NSMutableArray alloc] initWithObjects:
#"compass_24x24.png",
#"mobilephone2_24x24.png",
#"web_24x24.png",
#"calendar_24x24.png",
#"clock2_24x24.png",
#"parking_meter_24x24.png",
#"shopping_bag_24x24.png",
#"credit_cards_24x24.png", nil];
self.infoDetailTableData = [[NSMutableArray alloc] initWithObjects:
#"123 Main St.",
#"555-555-5555",
#"www.genericrestaurant.com",
#"11:30-2:30,Su:11;5:30-10;F-Sa:10:30,Su:9",
#"Recommended",
#"Parking lot (in rear); on-street",
#"No",
#"Credit cards accepted", nil];
}
#pragma mark Table Source, Delegate Protocol code
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.infoTableData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ATTEMPT #1: place subView allocate code outside of 'if (cell == nil)' block...
// Causes cell corruption issues upon table scrolling...
static NSString *cellIdentifier = #"DetailInfoCell"; // same ID as used in storyBoard cell setup...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
cell.textLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:14.0f];
cell.textLabel.text = [self.infoTableData objectAtIndex: indexPath.row];
UIImage *cellImage = [UIImage imageNamed:[self.cellImages objectAtIndex:indexPath.row]]; // ditto: cellImages = array of strings
cell.imageView.image = cellImage;
// Add UITextView to cell for phone, website rows
if (indexPath.row == 1 || indexPath.row == 2)
{
cell.detailTextLabel.text = #"";
// Using constants = embarrassing hack -- figure out proper way
UITextView *tv = [[UITextView alloc] initWithFrame:CGRectMake(cell.frame.origin.x + (cell.frame.size.width)/2.1f,
cell.frame.origin.y + 2.0f,
(cell.frame.size.width)/2.0f,
cell.frame.size.height)];
if (indexPath.row == 1)
tv.dataDetectorTypes = UIDataDetectorTypePhoneNumber;
else
tv.dataDetectorTypes = UIDataDetectorTypeLink;
tv.editable = NO;
tv.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
tv.textAlignment = NSTextAlignmentRight;
//tv.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // or use this instead?
tv.contentMode = UIViewContentModeRight;
tv.backgroundColor = [UIColor clearColor];
tv.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
tv.text=[self.infoDetailTableData objectAtIndex: indexPath.row];
[cell.contentView addSubview:tv];
}
else
{
cell.detailTextLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
cell.detailTextLabel.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
}
return cell;
}
#end
But when I scrolled the table, cell contents overwrote each other. After further research on this website, I learned that it's supposedly a big no-no to set up and allocate the UITextView subViews outside of the "if (cell == nil)" block, because when a cell is dequeued for re-use (using "dequeueReusableCellWithIdentifier"), it may already have had a UITextView subView added to it, so UITextView subViews will be added to cells that already have them, which causes cell corruption issues during scrolling.
So my second attempt tried to get around that problem by using tags to keep track of cells:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ATTEMPT #2: put subView allocate block INSIDE 'if (cell == nil)' block
// Now, subViews are blank -- they do not appear in the cell rows (even during scrolling)
static NSString *cellIdentifier = #"DetailInfoCell"; // same ID as used in storyBoard cell setup...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifier];
UITextView *tv1 = nil;
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
// Using constants = embarrassing hack -- figure out proper way
UITextView *tv = [[UITextView alloc] initWithFrame:CGRectMake(cell.frame.origin.x + (cell.frame.size.width)/2.1f,
cell.frame.origin.y + 2.0f,
(cell.frame.size.width)/2.0f,
cell.frame.size.height)];
tv.tag = indexPath.row; // keep track of this cell, for later re-use...
if (indexPath.row == 1)
tv.dataDetectorTypes = UIDataDetectorTypePhoneNumber;
else
tv.dataDetectorTypes = UIDataDetectorTypeLink;
tv.editable = NO;
tv.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
tv.textAlignment = NSTextAlignmentRight;
//tv.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // or use this instead?
tv.contentMode = UIViewContentModeRight;
tv.backgroundColor = [UIColor clearColor];
tv.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
tv.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
[cell.contentView addSubview:tv];
}
cell.textLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:14.0f];
cell.textLabel.text = [self.infoTableData objectAtIndex: indexPath.row]; // infoTableData = data source array of strings...
if (indexPath.row == 1 || indexPath.row == 2)
{
cell.detailTextLabel.text = #"";
tv1 = (UITextView *)[cell.contentView viewWithTag: indexPath.row]; // get pointer to 'correct' re-usable cell...
tv1.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
}
else
{
cell.detailTextLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
cell.detailTextLabel.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
}
return cell;
}
This second attempt failed. The UITextViews weren't showing up at all. The cell
"detail text areas" were blank.
Yet more research on this site revealed postings which claimed that you had to clear any
subViews on returned cells from the call to [tableView dequeueReusableCellWithIdentifier...],
so that adding a new UITextView subView to the cell wouldn't cause problems. So my third
attempt tried this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ATTEMPT #3: keep subView allocate code INSIDE 'if (cell == nil)' block; try to clear subViews from cells
// Still does not work: subViews still blank
static NSString *cellIdentifier = #"DetailInfoCell"; // same ID as used in storyBoard cell setup...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellIdentifier];
UITextView *tv1 = nil;
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
// Using constants = embarrassing hack -- figure out proper way
UITextView *tv = [[UITextView alloc] initWithFrame:CGRectMake(cell.frame.origin.x + (cell.frame.size.width)/2.1f,
cell.frame.origin.y + 2.0f,
(cell.frame.size.width)/2.0f,
cell.frame.size.height)];
tv.tag = indexPath.row; // keep track of this cell, for later re-use...
if (indexPath.row == 1)
tv.dataDetectorTypes = UIDataDetectorTypePhoneNumber;
else
tv.dataDetectorTypes = UIDataDetectorTypeLink;
tv.editable = NO;
tv.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
tv.textAlignment = NSTextAlignmentRight;
//tv.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // or use this instead?
tv.contentMode = UIViewContentModeRight;
tv.backgroundColor = [UIColor clearColor];
tv.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
tv.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
[cell.contentView addSubview:tv];
}
else
{
// Clear subviews, if any?
// Tried this: did not work: subViews still blank
[[[cell contentView] subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
// Tried this: did not work: subViews still blank
for (UIView *view in cell.contentView.subviews)
{
if ([view isKindOfClass:[UIView class]])
{
[view removeFromSuperview];
}
}
}
cell.textLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:14.0f];
cell.textLabel.text = [self.infoTableData objectAtIndex: indexPath.row]; // infoTableData = data source array of strings...
if (indexPath.row == 1 || indexPath.row == 2)
{
cell.detailTextLabel.text = #"";
tv1 = (UITextView *)[cell.contentView viewWithTag: indexPath.row]; // get pointer to 'correct' re-usable cell...
tv1.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
}
else
{
cell.detailTextLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
cell.detailTextLabel.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
}
return cell;
}
This had no affect whatsoever. UITextView subViews were still not appearing in the cells.
Yet even more research. Yet another posting said that:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
will always return a cell in iOS6, so that code within the "if (cell == nil)" block
will never even execute -- the returned cell will never be nil. Sure enough, that's
what I found out: no breakpoint was ever being reached inside my "if (cell == nil)"
code block -- the code was never being executed.
My fourth attempt was just to add a "magic" line of code that yet another posting
suggested:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
which I'm guessing roughly translates to: "always return a new cell, not a used
one, and don't worry about performance or memory issues" (I sense the internet-sphere rolling its eyes at about this point), and sure enough, that did the trick, once I removed the UITextView allocate/setup code from within the "if (cell == nil)" and put it back where it was originally (outside the block, so that it always executes).
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ATTEMPT #4:
// According to one particular posting on StackOverflow.com, 'dequeueReusableCellWithIdentifier' will
// *always* return a cell in iOS6 and above. Go figure... Use 'nil' for cell id on
// dequeueReusableCellWithIdentifier
// For some reason, this works...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
}
cell.textLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:14.0f];
cell.textLabel.text = [self.infoTableData objectAtIndex: indexPath.row];
UIImage *cellImage = [UIImage imageNamed:[self.cellImages objectAtIndex:indexPath.row]];
cell.imageView.image = cellImage;
if (indexPath.row == 1 || indexPath.row == 2)
{
cell.detailTextLabel.text = #"";
UITextView *tv = [[UITextView alloc] initWithFrame:CGRectMake(cell.frame.origin.x + (cell.frame.size.width)/2.1f,
cell.frame.origin.y + 2.0f,
(cell.frame.size.width)/2.0f,
cell.frame.size.height)];
if (indexPath.row == 1)
tv.dataDetectorTypes = UIDataDetectorTypePhoneNumber;
else if (indexPath.row == 2)
tv.dataDetectorTypes = UIDataDetectorTypeLink;
tv.editable = NO;
tv.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
tv.textAlignment = NSTextAlignmentRight;
//tv.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // or use this instead?
tv.contentMode = UIViewContentModeRight;
tv.backgroundColor = [UIColor clearColor];
tv.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
tv.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
[cell.contentView addSubview:tv];
}
else
{
cell.detailTextLabel.font = [UIFont fontWithName:#"Trebuchet MS" size:12.0f];
cell.detailTextLabel.text = [self.infoDetailTableData objectAtIndex: indexPath.row];
}
return cell;
}
But this seems to be a hack. Can someone post some very simple, basic iPhone
app code (I guess for iOS6/4S or equivalent, but does it really matter?) that
represents the canonical, proper way to fill a TableView so that some rows
are simple Title/Right Detail "text-only" cells, and some cells are UITextView
subView cells with phone numbers/website links in the "details" section? Am
I required to subClass a UITableViewCell or use some "prepareForReuse"
method?
Note: I'm using a storyBoard to set up one, Dynamic Prototype cell, with an
identifier 'string'.

Duplicates of text in UITextField when inside UITableView

I have created a UITextField in a UITableView. I type the data in and close the keyboard. However when I scroll down and hide the UITextField and then scroll back up again, the 'UITextField' data is duplicated as seen below:
Original Load of View:
Typed in Data:
After hidden textfield and then started editing again:
- (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];
}
if ([indexPath section] == 0) { // Email & Password Section
cell.textLabel.text = #"Subject";
} else {
cell.textLabel.text = #"Task";
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if ([indexPath section] == 0) {
UITextField *subject = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
subject.adjustsFontSizeToFitWidth = YES;
subject.textColor = [UIColor blackColor];
if ([indexPath row] == 0) {
subject.placeholder = #"Maths";
subject.keyboardType = UIKeyboardTypeEmailAddress;
subject.returnKeyType = UIReturnKeyNext;
}
subject.backgroundColor = [UIColor clearColor];
subject.autocorrectionType = UITextAutocorrectionTypeNo;
subject.autocapitalizationType = UITextAutocapitalizationTypeWords;
subject.tag = 0;
subject.clearButtonMode = UITextFieldViewModeNever;
[cell.contentView addSubview:subject];
} else {
UITextView *task = [[UITextView alloc] initWithFrame:CGRectMake(102, 0, 185, 40)];
task.text = #"fasfashfjasfhasfasdjhasgdgasdhjagshjdgashjdgahjsdghjasgasdashgdgjasd";
task.editable = NO;
task.scrollEnabled = NO;
task.userInteractionEnabled = NO;
task.textColor = [UIColor colorWithRed: 62.0/255.0 green: 85.0/255.0 blue:132.0/255.0 alpha:1.0];
task.backgroundColor = [UIColor clearColor];
}
return cell;
}
Like Richard said, cells are reused (that's what the identifier purpose is), and that's why you test in your tableView:cellForRowAtIndexPath: for a nil value returned by dequeueReusableCellWithIdentifier:.
If a cell already exists (ie. was allocated earlier) and is not displayed anymore, dequeueReusableCellWithIdentifier: will use this cell to display the content of the newly appearing cell.
What you are doing is adding your UITextView every time your cells are displayed and not created. So each time a cell is gets scrolled out of the screen and a new cell pops in, you append a new UITextView in the cell. You should add subviews only in the if (cell == nil) part of your method. As the content of your cells are rather different, I'd recommend using two distinct identifiers.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifierForSection0 = #"Cell0";
static NSString *CellIdentifierForSection1 = #"Cell1";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: [indexPath section] == 0 ? CellIdentifierForSection0 : CellIdentifierForSection1];
if (cell == nil) {
if ([indexPath section] == 0) { // Email & Password Section
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifierForSection0];
cell.textLabel.text = #"Subject";
UITextField *subject = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
subject.adjustsFontSizeToFitWidth = YES;
subject.textColor = [UIColor blackColor];
if ([indexPath row] == 0) {
subject.placeholder = #"Maths";
subject.keyboardType = UIKeyboardTypeEmailAddress;
subject.returnKeyType = UIReturnKeyNext;
}
subject.backgroundColor = [UIColor clearColor];
subject.autocorrectionType = UITextAutocorrectionTypeNo;
subject.autocapitalizationType = UITextAutocapitalizationTypeWords;
subject.tag = 0;
subject.clearButtonMode = UITextFieldViewModeNever;
[cell.contentView addSubview:subject];
} else {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifierForSection1];
cell.textLabel.text = #"Task";
UITextView *task = [[UITextView alloc] initWithFrame:CGRectMake(102, 0, 185, 40)];
task.text = #"fasfashfjasfhasfasdjhasgdgasdhjagshjdgashjdgahjsdghjasgasdashgdgjasd";
task.editable = NO;
task.scrollEnabled = NO;
task.userInteractionEnabled = NO;
task.textColor = [UIColor colorWithRed: 62.0/255.0 green: 85.0/255.0 blue:132.0/255.0 alpha:1.0];
task.backgroundColor = [UIColor clearColor];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
return cell;
}
Note that this code is mainly for example purpose, and could be greatly reducted. Moreover, you should use subclass(es) of UITableViewCell like Richard suggested, as it will help organizing your code and make it more reusable.
BUT do NOT use drawRect: to add subviews. This is unnecessary and will impact performances. drawRect: should only be used if you intend to make real drawing like with CoreAnimation or CoreGraphics. Adding subview should be done in initWithFrame: or initWithCoder: depending of your use of Interface Builder or not.
Remember cells get reused, therefore the subviews are added each time it's reused. If you're going to add subviews to a cell you're best off creating a subclass of UITableViewCell and adding the subviews in the drawRect: method of that subclass. That way the modifications are part of the cell and aren't added each time the cell is reused.

Load all cells in UITableView before scrolling

I have a UITableView with 8 cells(sections) in total in which 6 of them contain textFields as subviews and out of 2 one containing button and the other containing text view.Now I have got a problem here,as we are all aware that when ever the view appears only,the visible cells loads.So when my app gets loaded,I am able to view 5 cells,the first 4 containing text fields and the 5th cell with button.I can see the array count is 4 when i displayed it through logging in console i.e.
there are 4 objects in the array
Now after scrolling the table view,I can observe the following:
there are 6 objects in the array
So,what's happening is If I am to save the data entered in the table view,or say edit to make changes to existing data,I am forced to scroll the table view and enter the values in last cell too.So that cannot be the case always with the user because we can't expect him/her to scroll the table view and enter the complete form/table view cell entries.What ever changes he/she makes they will and just click done/save what ever it is..!
More over I am having a picker view as input view for last cell containing text field as subview.Hence in my picker view did select row method I am facing crash problem with last cell/section(textField as subview) containing a picker view here is my code snippet for the method:
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
switch (tagValues)
{
case 105:
{
self.textField = [self.fields objectAtIndex:3];
self.textField.text = [self.reminder objectAtIndex:row];
}
break;
case 107:
{
//Crash occurring in this case,sometimes its working wonder why
self.textField = [self.fields objectAtIndex:5];
self.textField.text = [self.group objectAtIndex:row];
}
break;
}
}
**Note**:I am using the same textField for adding as subview to all 6 cells,but each one with unique tags and own purpose,i.e. one for editing text using key board,one with date picker,one with general picker etc...
I wonder why some times after selecting values from picker view(last section cell) crashes some times and some times works fine.Some times it crashes at the above line commented in case 107,some times in main autorelease pool.Some time thread as shown in snap below:
So is there any way that we can load all cells at once so that all the text fields get added to array before scrolling.There cannot be any problem I believe
More Detailed EDITED CODE for understanding:
- (UITableViewCell *)tableView:(UITableView *)atableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSString *identifier = #"UITableViewCell";
NSString *cellIdentifierF = nil;
static NSString *firstCellIdentifier = #"FirstCell";
static NSString *secondCellIdentifier = #"SecondCell";
static NSString *thirdCellIdentifier = #"ThirdCell";
static NSString *fourthCellIdentifier = #"FourthCell";
static NSString *fifthCellIdentifier = #"FifthCell";
static NSString *sixthCellIdentifier = #"SixthCell";
static NSString *seventhCellIdentifier = #"SeventhCell";
static NSString *eightCellIdentifier = #"EightCell";
if(indexPath.section == 0 && indexPath.row == 0)
{
cellIdentifierF = firstCellIdentifier;
}
else if(indexPath.section == 1 && indexPath.row == 0)
{
cellIdentifierF = secondCellIdentifier;
}
else if(indexPath.section == 2 && indexPath.row == 0)
{
cellIdentifierF = thirdCellIdentifier;
}
else if(indexPath.section == 3 && indexPath.row == 0)
{
cellIdentifierF = fourthCellIdentifier;
}
else if(indexPath.section == 4 && indexPath.row == 0)
{
cellIdentifierF = fifthCellIdentifier;
}
else if(indexPath.section == 5 && indexPath.row == 0)
{
cellIdentifierF = sixthCellIdentifier;
}
else if(indexPath.section == 6 && indexPath.row == 0)
{
cellIdentifierF = seventhCellIdentifier;
}
else if(indexPath.section == 7 && indexPath.row == 0)
{
cellIdentifierF = eightCellIdentifier;
}
UITableViewCell *cell = (UITableViewCell *)[atableView dequeueReusableCellWithIdentifier:cellIdentifierF];
atableView.backgroundColor = [UIColor clearColor];
textField = [[[UITextField alloc]initWithFrame:CGRectMake(15, 12, 300, 24)]autorelease];
textField.textColor = [UIColor whiteColor];
textField.delegate = self;
tagValues = textField.tag;
switch (indexPath.section)
{
case 0:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: firstCellIdentifier])
{
textField.placeholder = #"Enter name";
[self.textField setValue:[UIColor purpleColor]
forKeyPath:#"_placeholderLabel.textColor"];
textField.tag = 101;
textField.text = reminderInstance.Name;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
NSLog(#"Value = %#",textField.text);
[cell.contentView addSubview:textField];
[self.fields addObject:textField];
}
}
}
break;
case 1:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: secondCellIdentifier])
{
textField.tag = 102;
textField.text = reminderInstance.Event;
NSLog(#"Value = %#",textField.text);
[cell.contentView addSubview:textField];
[self.fields addObject:textField];
}
}
}
break;
case 2:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: thirdCellIdentifier])
{
textField.placeholder = #"Click here to set date and time";
[self.textField setValue:[UIColor purpleColor]
forKeyPath:#"_placeholderLabel.textColor"];
textField.inputView = self.datePicker;
textField.text = reminderInstance.Date;
textField.tag = 103;
NSLog(#"Value = %#",textField.text);
[cell.contentView addSubview:textField];
[self.fields addObject:textField];
}
}
}
break;
case 3:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: fourthCellIdentifier])
{
textField.tag = 105;
textField.text = reminderInstance.numDays;
textField.inputView = self.reminderPicker;
NSLog(#"Value = %#",textField.text);
[cell.contentView addSubview:textField];
[self.fields addObject:textField];
}
}
}
break;
case 4:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: fifthCellIdentifier])
{
checkboxButton = [[[UIButton alloc] initWithFrame:CGRectMake(16,1,120, 44)]autorelease];
[checkboxButton setImage:[UIImage imageNamed:#"ewee.png"] forState:UIControlStateNormal];
[checkboxButton addTarget:self action:#selector(toggleButton:) forControlEvents:UIControlEventTouchUpInside];
NSString *one = reminderInstance.selString;
NSNumber* i = [NSNumber numberWithInt:[one intValue]];
BOOL isOn = [i boolValue];
if(isOn)
{
[checkboxButton setImage:[UIImage imageNamed:#"checkarrow.png"] forState:UIControlStateNormal];
}
else
{
[checkboxButton setImage:[UIImage imageNamed:#"ewee.png"] forState:UIControlStateNormal];
}
NSLog(#"String Val = %#",one);
[checkboxButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
[checkboxButton setImageEdgeInsets:UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0)];
[cell addSubview:checkboxButton];
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(55, 10, 225, 24)];
label.text = #"Every Year";
label.textColor = [UIColor whiteColor];
label.backgroundColor = [UIColor clearColor];
[cell addSubview:label];
cell.textLabel.textColor = [UIColor whiteColor];
[label release];
}
}
}
break;
case 5:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: sixthCellIdentifier])
{
textField.placeholder = #"Enter the number here";
[self.textField setValue:[UIColor purpleColor]
forKeyPath:#"_placeholderLabel.textColor"];
textField.text = num;
textField.text = reminderInstance.number;
textField.tag = 106;
textField.userInteractionEnabled = YES;
textField.keyboardType = UIKeyboardTypeNumberPad;
NSLog(#"Value = %#",textField.text);
[cell.contentView addSubview:textField];
[self.fields addObject:textField];
}
}
}
break;
case 6:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"reminderbuttonxl.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: seventhCellIdentifier])
{
cell.backgroundColor = [UIColor clearColor];
textView.clipsToBounds = YES;
textView = [[UITextView alloc]initWithFrame: CGRectMake(-2, -3, 307, 154)];
UIImageView *imgView = [[[UIImageView alloc]initWithFrame: textView.frame]autorelease];
imgView.image = [UIImage imageNamed: #"buttonbg_text.png"];
[textView addSubview: imgView];
[textView sendSubviewToBack: imgView];
textView.backgroundColor = [UIColor clearColor];
textView.delegate = self;
textView.tag = 11;
tagValues = textView.tag;
textView.textColor = [UIColor purpleColor];
[cell.contentView addSubview:textView];
textView.text = #"Your birthday wishes/other reminder body here";
NSLog(#"Value = %#",textView.text);
}
}
}
break;
case 7:
{
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
//Only add content to cell if it is new
if([cellIdentifierF isEqualToString: eightCellIdentifier])
{
textField.tag = 107;
textField.inputView = self.groupPicker;
tagValues = textField.tag;
textField.text = reminderInstance.remGroup;
NSLog(#"Value = %#",textField.text);
[cell.contentView addSubview:textField];
[self.fields addObject:textField];
}
}
}
break;
default:
break;
}
int size = [fields count];
NSLog(#"there are %d objects in the array", size);
return cell;
}
I have made several attempts to find out what exactly is going wrong also I checked some questions here and there.But couldn't find any efficient/appr. answer or that solves my issue.
So anyone please help me with valuable suggestions or sample code snippets.
Thanks all in advance :)
It would make sense that there will be a lot of issues coming from the way you are building the form (which is actually the case)
what I've been doing myself when I need to build a such a form is to use build a normal view with all the controls whatever long it is and have that form in a scroll view and set its content size with the size of the view in it. that would let you have more control on how you want that form to look.
and I don't think I've seen an app using a tableview for a form entry.
you can use interface builder to build your form (just cheat on Xcode and make the main view and the scroll vew large enough so u can see everything on the builder, then resize it to the appropriate size when you are done)
It sounds like you might be better off not using a table view at all, but using a scroll view instead. If you really do have a fixed amount of things that you are going to display, you can just lay these out on a scroll view as views (instead of table cells) and your code will get a lot simpler.
If what you want out of the table view is just the appearance, you can simply use a background image that looks the way you want, and overlay the views and controls over that background. You should be able to do all of this in Interface Builder.
If you take this approach, the code in your view controller will now be just what you need to handle the text input and button presses, and you don't need to worry about a table view delegate or data source.
Although this seems to be already solved, what could have done as a "dirty hack" is to make the entire table scroll till bottom before performing any operation. Something like [myTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; This way it would be forced to load all the UITableCellView. Remember, this is not preferred way, but can work in case minimum code change is required.
What happens is that UITableView loads only the visible cells, and all the time that the UITableView is scrolled, the callback cellForRowAtIndexPath: is called. But let me get this straight. Why do you need a UITableView to make a form? And as I can see, you are loading one cell per section... And, you are only initializing the cell. What you should do. Subclass UITableViewCell and create your objects there.
then call like this:
cell = [[[MyCustomCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
Then you can call exactly the controller you want. And you have to control the behavior of your UITableView whenever it is scrolled. like this:
case 7:
{
if (cell == nil)
{
cell = [[[MyCustomCellEigth alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifierF] autorelease];
cell.backgroundColor = [[UIColor alloc]initWithPatternImage:[UIImage imageNamed:#"buttonbg-1.png"]];
/*this should be done in the Custom Cell init method
if([cellIdentifierF isEqualToString: eightCellIdentifier])
{
textField.tag = 107;
textField.inputView = self.groupPicker;
tagValues = textField.tag;
textField.text = reminderInstance.remGroup;
NSLog(#"Value = %#",textField.text);
[cell.contentView addSubview:textField];
[self.fields addObject:textField];
}
*/
}
else
{
//Control content
((MyCustomCell*)cell).myTextField.text = #"SOME TEXT";
}
}
break;
About the picker. It probably only works once you have scrolled all table, because it references a textField that will only be added to the Array once the correspondent cell become visible.
There isn't much more to do. But follow this and you should be fine. First, Subclass UITableViewCell. You can create the controls yourself programmatically or with a xib. This will give you more control over the cells itself. About the picker, instead of creating a array with controls, create it with the values it should have. Once someone has typed something, save it using the UITextFieldDelegate.
Hope it helps.
EDIT:
You must create a Subclass of the UITableViewCell. On Xcode, create a new file. Select objective-c Class. On the next screen, on the field subclass of, select UITableViewCell and name your cell accordingly. This will create a class that will override the initWithStyle:reuseIdentifier: method. Inside this method, you should put your init code.
like this:
#import <UIKit/UIKit.h>
#interface MyCustomCellOne : UITableViewCell
{
UITextField *textField;
}
#end
#import "MyCustomCellOne.h"
#implementation MyCustomCellOne
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
textField = [[UITextField alloc] initWithFrame:"YOUR FRAME"];
textField.tag = 107;
textField.text = #"SOME TEXT";
[self addSubview:textField];
}
return self;
}

UITableView with two types of resusable cell not working properly

I am trying to create a table view which uses two types of cells - default and subtitle.
I tried to use a single reusable cell (*cell) which seemed to work fine until I got to the bottom cell which was off screen - when this came into view it was a duplicate of the first visible cell.
I thought that I could try to add a second type of cell (*cellB) and when I did it seemed to solve the problem however it would crash every so often with the following error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
I know that I am doing something wrong and I am pretty sure I am not implementing the reusable cells properly but after several days of frustration I would really appreciate some advice.
P.S. I have searched many previous posts but none seem to cover the issue of using two types of cells on the same table.
Thanks in advance.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
UITableViewCell *cellB = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCellB"];
switch (indexPath.section) {
case 0:
if (cell == nil) {
// The only subtitle cell
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:#"UITableViewCell"];
}
[[cell textLabel] setText:title];
[[cell textLabel] setTextColor:[UIColor whiteColor]];
[[cell detailTextLabel] setText:[NSString stringWithFormat:#"Entry: £%#",price]];
[[cell detailTextLabel] setTextColor:[UIColor colorWithWhite:1.0 alpha:.8]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
break;
case 1:
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCell"];
UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"PhotoFrame.png"]];
cell.backgroundView = cellBackView;
}
[lImage setFrame:CGRectMake(0, 23, 320, 200)];
[cell.contentView addSubview:lImage];
break;
case 2:
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCell"];
UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"PaperTop.png"]];
cell.backgroundView = cellBackView;
}
break;
case 3:
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCell"];
UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"Paper.png"]];
cell.backgroundView = cellBackView;
}
break;
case 4:
// I'm pretty sure this bit is wrong but if I use (cell == nil) the first cell is shown instead
if (cellB == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCellB"];
UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"PaperBottom.png"]];
cell.backgroundView = cellBackView;
}
break;
default:
break;
}
return cell;
}
If cellB isn't null, you're still returning cell and not cellB.
Edit:
To fix, add code in the case 4 block as follows:
case 4:
if (cellB == nil) {
// No changes in here
}
else { //
cell = cellB; // Add these 3 lines
} //
break;
This method is called once for each cell that needs to be filled. If you scroll the table, rows will go off the screen and be queued with their reuse identifier. When rows scroll onto the table, this method is called to fill the cells as they become visible.
So first, you only want to dequeue one cell each time this method is called. By dequeuing cells of type UITableViewCell' andUITableViewCellB' you are dequeuing one that isn't going to be used. So you need to determine which type of cell you need before you dequeue one, and then dequeue the right type (by its reuse identifier).
Second, the purpose of the cell queuing mechanism is so you don't have to do things like customize the cell appearance every time it appears in the view. If a cell with that type of appearance is already in the queue, then it should come out of the queue already set up, and you only need to put the data into it. This is done for performance (speed), but it may not make a lot of difference in your case.
I may be wrong about this, and I'll correct my answer if I am, but the error message may be because the number of sections returned by numberOfSections and/or the number or rows returned by numberOfRowsInSection: is not correct and doesn't match the data source. It is trying to access an element of the data source that doesn't exist.
What kind of data source are you using, and can you show the code for numberOfSections and numberOfRowsInSection:?
UPDATE with Correction
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCell"];
UIView *cellBackView; // move this outside of the switch block
switch (indexPath.section) {
case 0:
[[cell textLabel] setText:title];
[[cell textLabel] setTextColor:[UIColor whiteColor]];
[[cell detailTextLabel] setText:[NSString stringWithFormat:#"Entry: £%#",price]];
[[cell detailTextLabel] setTextColor:[UIColor colorWithWhite:1.0 alpha:.8]];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
break;
case 1:
cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"PhotoFrame.png"]];
cell.backgroundView = cellBackView;
[lImage setFrame:CGRectMake(0, 23, 320, 200)];
[cell.contentView addSubview:lImage];
break;
case 2:
cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"PaperTop.png"]];
cell.backgroundView = cellBackView;
break;
case 3:
cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"Paper.png"]];
cell.backgroundView = cellBackView;
break;
case 4:
cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"PaperBottom.png"]];
cell.backgroundView = cellBackView;
break;
default:
break;
}
return cell;
}
As cgull pointed out, when [indexPath section] is 4, you're initializing cellB, but still returning cell, which may or may not have a valid cell in it.
You need to have one variable that holds the cell you're going to return, no matter the path taken through the switch:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString identifier = #"UITableViewCell";
if( [indexPath section] == 4 ){
identifier = #"UITableViewCellB";
}
// Try to dequeue the appropriate cell kind
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
switch (indexPath.section) {
//other cases...
case 4:
// You now have only one variable that can possibly hold a cell.
// If section is 4, it's either nil or a UITableViewCellB kind.
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCellB"];
// Set up new cell
}
break;
default:
break;
}
return cell;
}
cgull is correct.
Replace case 4 with this:
case 4:
if (cellB == nil) {
// assign to the correct cell
cellB = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"UITableViewCellB"];
UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:#"PaperBottom.png"]];
cell.backgroundView = cellBackView;
}
return cellB; // <--- return the correct cell

Align cells to bottom and top of UITableView, leaving stretchable gap in middle

I have a table view with five cells displaying the settings of my app. I want the first four cells to appear at the top. The last cell isn't actually a setting, it says "Legal" and takes you to the EULA, so I want it to appear at the bottom.
Now I know I could use – tableView:viewForHeaderInSection: and– tableView:heightForHeaderInSection: to create some padding, but I really don't like hardcoding in dimensions this way. I also don't want to use UIButton, because I want it to be exactly the same style as the rest of the cells.
Does anyone know the best practice for going about this?
I'm assuming you're using a grouped table view, like the settings app. What I've done is created a group section between the top and bottom group sections, to which I've add a 'transparent' row cell, that I size dynamically.
I've created a simple demo for you using hardcoded values, but hopefully you'll get the idea. Obviously you would get the number of sections and the number of rows from your data structure:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
int rtn = 0;
if (section == 0)
rtn = 4;
else if (section == 1)
rtn = 1;
else if (section == 2)
rtn = 1;
return rtn;
}
/*
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 44.0f;
}
*/
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int TOTALCELLS = 6;
float CONSTANT = 0.5f;
if (indexPath.section == 1)
return self.view.frame.size.height - (44.0f * (CONSTANT + TOTALCELLS));
return 44.0f;
}
- (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 = #"My Switch";
UISwitch *boolSwitch = [[UISwitch alloc]initWithFrame:CGRectZero];
boolSwitch.on = YES;
[cell setAccessoryView:boolSwitch];
[boolSwitch release];
} else if (indexPath.row == 1) {
cell.textLabel.text = #"My TxtField";
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
UITextField *txtField = [[UITextField alloc]initWithFrame:CGRectMake(0,0,175,20)];
txtField.text = #"Default Value";
txtField.textColor = [UIColor darkGrayColor];
[txtField setClearButtonMode:UITextFieldViewModeAlways];
[cell setAccessoryView:txtField];
[txtField release];
} else if (indexPath.row == 2) {
cell.selectionStyle=UITableViewCellSelectionStyleNone;
UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(0,0,280,22)];
slider.minimumValue = 0.0f;
slider.maximumValue = 10.0f;
slider.value = 0.7f;
slider.minimumValueImage = [UIImage imageNamed:#"minus.png"];
slider.maximumValueImage = [UIImage imageNamed:#"plus.png"];
[cell setAccessoryView:slider];
[slider addTarget:self action:#selector(sliderChange:forEvent:) forControlEvents:UIControlEventValueChanged];
[slider release];
} else if (indexPath.row == 3) {
cell.textLabel.text = #"My MultiValue";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.detailTextLabel.text = #"Default selection";
}
} else if (indexPath.section == 1) {
UIView *transCell = [[UIView alloc] initWithFrame:CGRectZero];
transCell.backgroundColor = [UIColor clearColor];
cell.backgroundView = transCell;
[transCell release];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
} else if (indexPath.section == 2) {
cell.textLabel.text = #"Legal";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
The critical parts are in cellForRowAtIndexPath, where the transparent row is created. The trick here is to add the transparent view to the cell's backgroundView, and set the cell selection style to None.
UIView *transCell = [[UIView alloc] initWithFrame:CGRectZero];
transCell.backgroundColor = [UIColor clearColor];
cell.backgroundView = transCell;
[transCell release];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
and in heightForRowAtIndexPath, the dynamic sizing of the transparent row.
self.view.frame.size.height - (44.0f * (CONSTANT + TOTALCELLS));
TOTALCELLS is the total of all of the cells, including the transparent one. You may need to do additional work on this algorithm, as it doesn't take into consideration what happens when number of cells grows beyond what is visible, and I haven't tested it with various combinations of navigation and toolbars.
I hope this works for you.