I am using an Expandable UITableview created by Tom Fewster. I want to tweak the example using two NSMutableArrays, which is a scenario whereby if someone wants to populate an expandable/collapse treeview table from webservice json data would want to achieve. So since in his example the GroupCell does not have an array of, I am wondering how can I do it? Please bear in mind that my Objective-C is still rusty hence, I'm asking this question.
With my attempt is only displaying the first ObjectAtIndex:indexPath:0 for the group.
I want to be able to populate the table and get output like this;
Group A
Row 1a
Row 2a
Row 3a
Group B
Row 1b
Row 2b
Group C
Row 1c
Row 2c
Row 3c
and so on.
You may use JSON data as well to explain your answer if you understand it better that way.
Here i want to populate the table with JSON data so the GroupCell show class_name and rowCell show subject_name. This is the console of what I am parsing from the JSON web-service;
(
{
"class_id" = 70;
"class_name" = Kano;
subject = (
"subject_id" = 159;
"subject_name" = "Kano Class";
}
);
},
{
"alarm_cnt" = 0;
"class_id" = 71;
"class_name" = Lagos;
subject = (
"subject_id" = 160;
"subject_name" = "Lagos Class";
}
);
},
{
"alarm_cnt" = 3;
"class_id" = 73;
"class_name" = Nasarawa;
subject = (
"subject_id" = 208;
"subject_name" = "DOMA Class";
},
"subject_id" = 207;
"subject_name" = "EGGON Class";
},
"subject_id" = 206;
"subject_name" = "KARU Class";
},
"subject_id" = 209;
"subject_name" = "LAFIA Class";
},
"subject_id" = 161;
"subject_name" = "Nasarawa State Class";
}
);
},
{
"alarm_cnt" = 2;
"class_id" = 72;
"class_name" = Rivers;
subject = (
"subject_id" = 162;
"subject_name" = "Rivers Class";
}
);
}
)
I have tried this here is my snippet
- (UITableViewCell *)tableView:(ExpandableTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RowCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSDictionary *d=[_sitesJson objectAtIndex:0] ;
NSArray *arr=[d valueForKey:#"subject_name"];
NSDictionary *subitems = [arr objectAtIndex:0];
NSLog(#"Subitems: %#", subitems);
NSString *siteName = [NSString stringWithFormat:#"%#",subitems];
cell.textLabel.text =siteName;
//}
NSLog(#"Row Cell: %#", cell.textLabel.text);
// just change the cells background color to indicate group separation
cell.backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.backgroundView.backgroundColor = [UIColor colorWithRed:232.0/255.0 green:243.0/255.0 blue:1.0 alpha:1.0];
return cell;
}
- (UITableViewCell *)tableView:(ExpandableTableView *)tableView cellForGroupInSection:(NSUInteger)section
{
static NSString *CellIdentifier = #"GroupCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *textLabel = (UILabel *)[cell viewWithTag:2];
NSDictionary *d2 = [_regionsJson objectAtIndex:0];
NSArray *arr2 = [d2 objectForKey:#"class_name"];
NSString *regions = [[arr2 objectAtIndex:section]objectAtIndex:0];
textLabel.textColor = [UIColor whiteColor];
textLabel.text = [NSString stringWithFormat: #"%# (%d)", regions, (int)[self tableView:tableView numberOfRowsInSection:section]];
NSLog(#"Group cell label: %#", textLabel.text);
// We add a custom accessory view to indicate expanded and colapsed sections
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ExpandableAccessoryView"] highlightedImage:[UIImage imageNamed:#"ExpandableAccessoryView"]];
UIView *accessoryView = cell.accessoryView;
if ([[tableView indexesForExpandedSections] containsIndex:section]) {
accessoryView.transform = CGAffineTransformMakeRotation(M_PI);
} else {
accessoryView.transform = CGAffineTransformMakeRotation(0);
}
return cell;
}
He, just need to update one single method little bit way
- (UITableViewCell *)tableView:(ExpandableTableView *)tableView cellForGroupInSection:(NSUInteger)section
{
static NSString *CellIdentifier = #"GroupCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSIndexPath *indexPath;
NSString *regions = [[_dataGroup objectAtIndex:section]objectAtIndex:0];
cell.textLabel.text = [NSString stringWithFormat: #"%# ", regions];
// We add a custom accessory view to indicate expanded and colapsed sections
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ExpandableAccessoryView"] highlightedImage:[UIImage imageNamed:#"ExpandableAccessoryView"]];
UIView *accessoryView = cell.accessoryView;
if ([[tableView indexesForExpandedSections] containsIndex:section]) {
accessoryView.transform = CGAffineTransformMakeRotation(M_PI);
} else {
accessoryView.transform = CGAffineTransformMakeRotation(0);
}
return cell;
}
May help it you.
HTH, Enjoy Coding !!
I think you need to create a TableView which will have a sections array, and each sections row will be populated using the corresponding sections array. Tapping on a section will expand it and it's all rows will be visible.
To meet your requirements, you could follow the below steps as well -
1) Your modal should have a array for sections. The sections array will contain the sections objects, name of the section and corresponding array of the rows.
2) Implement the data source methods of the table view like
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return [section count];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 50; // sections height
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return nil;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [[[UIView alloc] initWithFrame:CGRectMake(0 , 0, tableView.frame.size.width , 50)] autorelease];
[view setBackgroundColor:[UIColor redColor]];
view.layer.masksToBounds = YES;
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5 , 2 , view.frame.size.width - 10 , view.frame.size.height - 3)];
label.text = ((SectionObject *)[section objectAtIndex:indexPath.section]).sectionName;
label.backgroundColor = [UIColor clearColor];
label.textAlignment = NSTextAlignmentLeft;
label.textColor = [UIColor WwhiteColor];
label.clipsToBounds = YES;
label.font = [UIFont fontWithName:#"HelveticaNeue-CondensedBold" size:14.0f];
label.layer.masksToBounds = YES;
UIImageView *arrowImage = [[UIImageView alloc] initWithFrame:CGRectMake(view.frame.size.width - 30, 0, 17 , 17)];
[arrowImage setCenter:CGPointMake(arrowImage.center.x , (view.frame.size.height/2) ) ];
if(section == self.m_currentSelectedSection)
[arrowImage setImage:self.m_upArrowImage];
else
[arrowImage setImage:self.m_downArrowImage];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)];
button.tag = section;
[button addTarget:self action:#selector(sectionTapped:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
[view addSubview:label];
[label release];
[view addSubview:arrowImage];
[arrowImage release];
[view addSubview:button];
[button release];
view.clipsToBounds = YES;
return view;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger count = 0;
if(self.m_currentSelectedSection == section)
count = [((SectionObject *)[section objectAtIndex:indexPath.section]).rowArray count];
return count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 40.0;
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * cellId = #"cellIdentifier";
UITableViewCell *cell = nil;
cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:cellId];
if(cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
//customize cell
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
When ever any section will be tapped following event will be invoked
- (void) sectionTapped:(UIButton *)button
{
self.m_currentSelectedSection = button.tag;
[self performSelector:#selector(refreshView) withObject:nil afterDelay:POINT_ONE_SECOND];
if(m_winnerSlotList->at(self.m_currentSelectedSection).m_leaderboardList.size())
[self.m_leaderboardTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:button.tag] atScrollPosition:UITableViewScrollPositionTop animated:YES];
UIView *baseView = [button superview];
if(baseView)
{
for(int ii = 0 ; ii < [[baseView subviews] count] ; ii++ )
{
UIView *anyView = [[baseView subviews] objectAtIndex:ii];
if([anyView isKindOfClass:[UIImageView class]])
[(UIImageView *)anyView setImage:self.m_upArrowImage];
}
}
}
Initialize self.m_currentSelectedSection = 0, for the first time, this will show the rows for 0th section. As any section is tapped it's rows will be visible (corresponding section rows will expand) and the rows for the previous selected section will be hidden(previous section rows will collapse).
If you need to show more than one section as expanded than you need to keep track of all the section whether a section is expanded or not and accordingly load show/ hide the cells for the corresponding section.
Related
I am developing IOS App. Using tableview to expand and collpase. Add Button on TableviewCell for check or Uncheck. Example I am Selected First Row button. Than scrolling tableview and select last header to last row button selected. Than again scrolling and see first index selected button image hidden.
Code..
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (self.sectionNames.count > 0) {
_tableView.backgroundView = nil;
return self.sectionNames.count;
}
return 0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSMutableArray *arrayOfItems = [self.sectionItems objectAtIndex:section];
return arrayOfItems.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (self.sectionNames.count) {
return [self.sectionNames objectAtIndex:section];
}
return #"";
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section; {
return 44.0;
}
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section {
// recast your view as a UITableViewHeaderFooterView
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
// header.contentView.backgroundColor = [UIColor colorWithHexString:#"#484848"];
header.contentView.backgroundColor = [UIColor lightGrayColor];
header.textLabel.textColor = [UIColor whiteColor];
header.textLabel.font = [UIFont fontWithName:#"SourceSansPro SemiBold" size:15.0];
UIImageView *viewWithTag = [self.view viewWithTag:kHeaderSectionTag + section];
if (viewWithTag) {
[viewWithTag removeFromSuperview];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell= [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = nil;
if (cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// for(UIView *subview in [cell subviews]) {
// [subview removeFromSuperview];
// }
UILabel *sectionLabel = [[UILabel alloc]initWithFrame:CGRectMake(35.0f, 12.0f, 120.0f, 20.f)];
sectionLabel.font = [UIFont systemFontOfSize:14.0f];
sectionLabel.textColor = [UIColor blackColor];
sectionLabel.lineBreakMode = NSLineBreakByWordWrapping;
sectionLabel.numberOfLines = 3;
[cell addSubview:sectionLabel];
NSArray *section = [self.sectionItems objectAtIndex:indexPath.section];
sectionLabel.text = [section objectAtIndex:indexPath.row];
UIButton *button = [[UIButton alloc]initWithFrame: CGRectMake(5.0f, 9.0f, 25.0f, 25.0f)];
button.layer.borderColor=[[UIColor colorWithRed:244.0f/255.0f
green:129.0f/255.0f
blue:32.0f/255.0f
alpha:1.0] CGColor];
[button.layer setBorderWidth: 1.0];
button.tag = indexPath.section;
NSString *data = [section objectAtIndex:indexPath.row]; //For Selected Fliters
for (int i=0; i<[filterArray count]; i++) {
NSDictionary *dict = [filterArray objectAtIndex:i];
NSString *fliterTickValue = [dict valueForKey:#"filterValue"];
if ([fliterTickValue isEqualToString:data]) {
UIImage *img = [UIImage imageNamed:#"tick.png"];
[button setImage: img forState:UIControlStateNormal];
}else{
UIImage *img = [UIImage imageNamed:#""];
[button setImage: img forState:UIControlStateNormal];
}
}
[button addTarget:self action:#selector(fliterFields:) forControlEvents:UIControlEventTouchDown];
[cell addSubview:button];
return cell;
}
Imho, your calls [cell addSubview:] is not correct. In case that cell is re-used, it will already have those subviews and resulting layout of your cell is not quite clear.
I suggest you to design your cell in xib and load it when needed. Then your cellForRowAtIndexPath becomes simpler and deals mostly with data populating. Subviews you don't need at the moment can be hidden and so on. If you need code and design example - let me know.
I have a main view controller. It contains a table view controller ( in a container ) , i want to round out the tableview so it shows similarly to the facebook login one.
Code i have used in the child view controller ( which is a tableview controller ) in viewDidLoad :
self.tableView.layer.cornerRadius = 10.0f;
self.tableView.layer.masksToBounds = YES;
self.tableView.clipsToBounds = YES;
self.tableView.backgroundColor = [UIColor clearColor];
Result :
As you can see , when the field is selected , the corners SEEM rounded , but the white space remains. How can i make them rounded even when it isn't selected ?
Using : XCODE 5 , IOS 7
As you requested me to do, here is the code you wanted:
for (CALayer *subLayer in self.tableView.layer.sublayers)
{
subLayer.cornerRadius = 10;
subLayer.masksToBounds = YES;
}
Try this
#interface CustomtableViewController : UITableViewController<UITableViewDelegate, UITableViewDataSource>
{
UITextField * username;
UIButton * submit;
}
#implementation CustomtableViewController
- (void)viewDidLoad
{
UIView *newView = [[UIView alloc]initWithFrame:CGRectMake(10, 70, 300, 45)];
submit = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[submit setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
//[submit setTitleColor:[UIColor colorWithWhite:0.0 alpha:0.56] forState:UIControlStateDisabled];
[submit setTitle:#"Login" forState:UIControlStateNormal];
[submit.titleLabel setFont:[UIFont boldSystemFontOfSize:14]];
[submit setFrame:CGRectMake(10.0, 15.0, 280.0, 44.0)];
[newView addSubview:submit];
[self.tableView setTableFooterView:newView];
[super viewDidLoad];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
//self.tableView.contentOffset = CGPointMake( 10, 320);
[self.tableView setContentInset:UIEdgeInsetsMake(50,0,0,0)];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if ([indexPath section] == 0) {
username = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
username.adjustsFontSizeToFitWidth = YES;
username.textColor = [UIColor blackColor];
if ([indexPath row] == 0) {
username.placeholder = #"example#gmail.com";
username.keyboardType = UIKeyboardTypeEmailAddress;
username.returnKeyType = UIReturnKeyNext;
cell.textLabel.text = #"Username";
username.clearButtonMode = YES;
}
else {
username.placeholder = #"minimum 6 characters";
username.keyboardType = UIKeyboardTypeDefault;
username.returnKeyType = UIReturnKeyDone;
username.secureTextEntry = YES;
cell.textLabel.text = #"Password";
username.clearButtonMode = UITextFieldViewModeAlways;
}
username.backgroundColor = [UIColor whiteColor];
username.autocorrectionType = UITextAutocorrectionTypeNo; // no auto correction support
username.autocapitalizationType = UITextAutocapitalizationTypeNone; // no auto capitalization support
username.textAlignment = NSTextAlignmentLeft;
username.tag = 0;
username.clearButtonMode = UITextFieldViewModeAlways; // no clear 'x' button to the right
[username setEnabled: YES];
[cell.contentView addSubview: username];
}
// Configure the cell...
return cell;
}
Here, i've created just two textfields for username and password. You can use the else if condition to insert any no of textfields in each of the successive rows according to your needs.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat:#"User Login"];
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 50;
}
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
return #"";
}
So, my code here is just used for creating a login page with two textfields(Username and Password) and a Login button. You can modify my code according to your needs. Cheers!
When I scroll UITableView my tableview cells automatically uncheck. But when I press done button it give me selected rows I can't understand why is this happening.
My Tableview code is as below :
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
idArray = [[NSMutableArray alloc]init];
NSLog(#"Cat Array : %#",catArray);
isSelectAllBtnClicked = NO;
serviceArray = [[NSArray alloc]initWithObjects:#"Anal", #"Cuffed", #"Foreplay", #"Masturbation", #"Scat", #"Blindfolded",#"Deep throat", #"French Kissing", #"Missionary", #"Shower for 2", #"Bottom", #"Dinner Date", #"Full Bondage", #"Mutual Masturbation", #"Spanking", #"Boy on Boy", #"Dirty Talk",#"Girl on Girl", #"On top", #"Strap on", #"Choking", #"Doggy", #"Girlfriend Experience", #"Oral", #"Striptease", #"CIM", #"Dominate", #"Golden Shower",#"Oral Mutual", #"Submissive", #"COB", #"Fantasy", #"Kissing", #"Overnight", #"Top", #"COF", #"Fetish", #"Light Bondage", #"Rim me",#"Touching", #"Couples", #"Fisting", #"Lingerie", #"Rim you", #"Toys for me", #"Cuddling", #"Foot fetish", #"Massage", #"Role Play", #"Toys for you", nil];
[myTableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *reuseIdentifier = #"reuseIdentifier";
UITableViewCell *cell = [[UITableViewCell alloc]init];
if (cell != NULL)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
}
cell.selectionStyle = UITableViewCellEditingStyleNone;
cell.backgroundView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"list_row_bg.png"]] autorelease];
UILabel *catListLbl = [[UILabel alloc] initWithFrame:CGRectMake(20, 10, 310, 20)];
NSString *strValue = [[NSUserDefaults standardUserDefaults]valueForKey:#"service"];
if ([strValue isEqualToString:#"service"])
{
catListLbl.text = [serviceArray objectAtIndex:indexPath.row];
topHaderLabel.text = #"Choose Services";
}
else
{
catListLbl.text = [catArray objectAtIndex:indexPath.row];
topHaderLabel.text = #"Choose Category";
}
catListLbl.textColor = [UIColor colorWithRed:244/255.0 green:29/255.0 blue:94/255.0 alpha:1.0];
catListLbl.backgroundColor = [UIColor clearColor];
[cell addSubview:catListLbl];
if (isSelectAllBtnClicked) {
UIButton *unCheckBtn = [[UIButton alloc]initWithFrame:CGRectMake(270, 10, 20, 20)];
[unCheckBtn setBackgroundImage:[UIImage imageNamed:#"checkbox_check.png"] forState:UIControlStateNormal];
unCheckBtn.tag = indexPath.row + 200;
//NSLog(#"%i",unCheckBtn.tag);
[cell addSubview:unCheckBtn];
[myTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
[self tableView:myTableView didSelectRowAtIndexPath:indexPath];
}
else {
UIButton *unCheckBtn = [[UIButton alloc]initWithFrame:CGRectMake(270, 10, 20, 20)];
[unCheckBtn setBackgroundImage:[UIImage imageNamed:#"checkbox.png"] forState:UIControlStateNormal];
unCheckBtn.tag = indexPath.row + 200;
//NSLog(#"%i",unCheckBtn.tag);
[cell addSubview:unCheckBtn];
}
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int buttonTag = indexPath.row + 200;
//NSLog(#"%i",buttonTag);
UIButton *tempBtn = (UIButton *)[self.view viewWithTag:buttonTag];
[tempBtn setBackgroundImage:[UIImage imageNamed:#"checkbox_check.png"]forState:UIControlStateNormal];
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
int buttonTag = indexPath.row + 200;
NSLog(#"%i",buttonTag);
UIButton *tempBtn = (UIButton *)[self.view viewWithTag:buttonTag];
[tempBtn setBackgroundImage:[UIImage imageNamed:#"checkbox.png"]forState:UIControlStateNormal];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int count;
NSString *strValue = [[NSUserDefaults standardUserDefaults]valueForKey:#"service"];
if ([strValue isEqualToString:#"service"])
{
count = [serviceArray count];
}
else
{
count = [catArray count];
}
return count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 40;
}
thanks in advance.
when ever you scroll the table
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
method executes and you have not mentioned any condition by which it can be judged that this row was previously selected or not. you have to insert a condition along with indexPath.row and check whether it is prviously selected or not
try like this ,when you scroll the tableview every time isSelectAllBtnClicked value is 0 that's why every time button changed.
when you scroll the table every time new cell created try to avoid that one
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier = [NSString stringWithFormat:#"%d",indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell.selectionStyle = UITableViewCellEditingStyleNone;
cell.backgroundView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"list_row_bg.png"]] autorelease];
UILabel *catListLbl = [[UILabel alloc] initWithFrame:CGRectMake(20, 10, 310, 20)];
NSString *strValue = [[NSUserDefaults standardUserDefaults]valueForKey:#"service"];
if ([strValue isEqualToString:#"service"])
{
catListLbl.text = [serviceArray objectAtIndex:indexPath.row];
topHaderLabel.text = #"Choose Services";
}
else
{
catListLbl.text = [catArray objectAtIndex:indexPath.row];
topHaderLabel.text = #"Choose Category";
}
catListLbl.textColor = [UIColor colorWithRed:244/255.0 green:29/255.0 blue:94/255.0 alpha:1.0];
catListLbl.backgroundColor = [UIColor clearColor];
[cell addSubview:catListLbl];
if (isSelectAllBtnClicked) {
UIButton *unCheckBtn = [[UIButton alloc]initWithFrame:CGRectMake(270, 10, 20, 20)];
[unCheckBtn setBackgroundImage:[UIImage imageNamed:#"checkbox_check.png"] forState:UIControlStateNormal];
unCheckBtn.tag = indexPath.row + 200;
//NSLog(#"%i",unCheckBtn.tag);
[cell addSubview:unCheckBtn];
[myTableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
[self tableView:myTableView didSelectRowAtIndexPath:indexPath];
}
else {
UIButton *unCheckBtn = [[UIButton alloc]initWithFrame:CGRectMake(270, 10, 20, 20)];
[unCheckBtn setBackgroundImage:[UIImage imageNamed:#"checkbox.png"] forState:UIControlStateNormal];
unCheckBtn.tag = indexPath.row + 200;
//NSLog(#"%i",unCheckBtn.tag);
[cell addSubview:unCheckBtn];
}
}
}
This is my solution for setting custom grouped table view cell backgrounds:
- (UIView *)top
{
if (_top) {
return _top;
}
_top = [[UIView alloc] init];
[_top setBackgroundColor:[UIColor blueColor]];
return _top;
}
// dot dot dot
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger maxRow = [tableView numberOfRowsInSection:section] - 1;
if (maxRow == 0) {
[cell setBackgroundView:[self lonely]];
} else if (row == 0) {
[cell setBackgroundView:[self top]];
} else if (row == maxRow) {
[cell setBackgroundView:[self bottom]];
} else {
[cell setBackgroundView:[self middle]];
}
}
Obviously it doesn't work as expected which brings me here, but it does work when I don't use cached views:
UIView *background = [[UIView alloc] init];
if (maxRow == 0) {
[background setBackgroundColor:[UIColor redColor]];
} else if (row == 0) {
[background setBackgroundColor:[UIColor blueColor]];
} else if (row == maxRow) {
[background setBackgroundColor:[UIColor yellowColor]];
} else {
[background setBackgroundColor:[UIColor greenColor]];
}
[cell setBackgroundView:background];
UPDATE: After Jonathan pointed out that I can't use the same view for more than one cell, I decided to follow the table view model where it has a queue of reusable cells. For my implementation, I have a queue of reusable background views (_backgroundViewPool):
#implementation RootViewController {
NSMutableSet *_backgroundViewPool;
}
- (id)initWithStyle:(UITableViewStyle)style
{
if (self = [super initWithStyle:style]) {
_backgroundViewPool = [[NSMutableSet alloc] init];
UITableView *tableView = [self tableView];
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
return self;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 6;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (section == 0) {
return 1;
}
return 10;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[cell textLabel] setText:[NSString stringWithFormat:#"[%d, %d]", [indexPath section], [indexPath row]]];
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
UIView *backgroundView = [cell backgroundView];
[_backgroundViewPool addObject:backgroundView];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger maxRow = [tableView numberOfRowsInSection:section] - 1;
UIColor *color = nil;
if (maxRow == 0) {
// single cell
color = [UIColor blueColor];
} else if (row == 0) {
// top cell
color = [UIColor redColor];
} else if (row == maxRow) {
// bottom cell
color = [UIColor greenColor];
} else {
// middle cell
color = [UIColor yellowColor];
}
UIView *backgroundView = nil;
for (UIView *bg in _backgroundViewPool) {
if (color == [bg backgroundColor]) {
backgroundView = bg;
break;
}
}
if (backgroundView) {
[backgroundView retain];
[_backgroundViewPool removeObject:backgroundView];
} else {
backgroundView = [[UIView alloc] init];
[backgroundView setBackgroundColor:color];
}
[cell setBackgroundView:[backgroundView autorelease]];
}
It works except when you scroll really fast. Some of the background views disappear! I suspect the background views are still being used in more than one cell, but I really don't know what's going on because the background views are supposed to be removed from the queue once it's reused making it impossible for the background view to be used in more than one visible cell.
I've been looking into this since I have posted this question. The current solutions for custom background views for grouped table view cells online are unsatisfactory, they don't used cached views. Additionally, I don't want to have use the solution proposed by XJones and jszumski because it's gonna get hairy once reusable custom cells (e.g., text field cell, switch cell, slider cell) are taken into account.
Have you considered using 4 separate cell identifiers for the "lonely, "top", "bottom", and "middle" cases and setting the backgroundView only once when initializing the cell? Doing it that way lets you leverage UITableView's own caching and reuse without having to write an implementation on top of it.
Update: An implementation for a grouped UITableViewController subclass that reuses background views with a minimal number of cell reuse identifiers (Espresso's use case). tableView:willDisplayCell:forRowAtIndexPath: and tableView:didDisplayCell:forRowAtIndexPath: do the heavy lifting to apply or reclaim each background view, and the pooling logic is handled in backgroundViewForStyle:.
typedef NS_ENUM(NSInteger, JSCellBackgroundStyle) {
JSCellBackgroundStyleTop = 0,
JSCellBackgroundStyleMiddle,
JSCellBackgroundStyleBottom,
JSCellBackgroundStyleSolitary
};
#implementation JSMasterViewController {
NSArray *backgroundViewPool;
}
- (void)viewDidLoad {
[super viewDidLoad];
// these mutable arrays will be indexed by JSCellBackgroundStyle values
backgroundViewPool = #[[NSMutableArray array], // for JSCellBackgroundStyleTop
[NSMutableArray array], // for JSCellBackgroundStyleMiddle
[NSMutableArray array], // for JSCellBackgroundStyleBottom
[NSMutableArray array]]; // for JSCellBackgroundStyleSolitary
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 5;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 2) {
return 1;
} else if (section == 3) {
return 0;
}
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = indexPath.section;
NSInteger row = indexPath.row;
static NSString *switchCellIdentifier = #"switchCell";
static NSString *textFieldCellIdentifier = #"fieldCell";
static NSString *textCellIdentifier = #"textCell";
UITableViewCell *cell = nil;
// apply a cached cell type (you would use your own logic to choose types of course)
if (row % 3 == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:switchCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:switchCellIdentifier];
UISwitch *someSwitch = [[UISwitch alloc] init];
cell.accessoryView = someSwitch;
cell.textLabel.text = #"Switch Cell";
}
} else if (row % 3 == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:textFieldCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:textFieldCellIdentifier];
UITextField *someField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 80, 30)];
someField.borderStyle = UITextBorderStyleRoundedRect;
cell.accessoryView = someField;
cell.textLabel.text = #"Field Cell";
}
} else {
cell = [tableView dequeueReusableCellWithIdentifier:textCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:textCellIdentifier];
cell.textLabel.text = #"Generic Label Cell";
}
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.text = [NSString stringWithFormat:#"[%d, %d]", section, row];
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// apply a cached background view
JSCellBackgroundStyle backgroundStyle = [self backgroundStyleForIndexPath:indexPath tableView:tableView];
cell.backgroundView = [self backgroundViewForStyle:backgroundStyle];
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
JSCellBackgroundStyle backgroundStyle = [self backgroundStyleForIndexPath:indexPath tableView:tableView];
NSMutableArray *stylePool = backgroundViewPool[backgroundStyle];
// reclaim the background view for the reuse pool
[cell.backgroundView removeFromSuperview];
if (cell.backgroundView != nil) {
[stylePool addObject:cell.backgroundView];
}
cell.backgroundView = nil; // omitting this line will cause some rows to appear without a background because they try to be in two superviews at once
}
- (JSCellBackgroundStyle)backgroundStyleForIndexPath:(NSIndexPath*)indexPath tableView:(UITableView*)tableView {
NSInteger maxRow = MAX(0, [tableView numberOfRowsInSection:indexPath.section] - 1); // catch the case of a section with 0 rows
if (maxRow == 0) {
return JSCellBackgroundStyleSolitary;
} else if (indexPath.row == 0) {
return JSCellBackgroundStyleTop;
} else if (indexPath.row == maxRow) {
return JSCellBackgroundStyleBottom;
} else {
return JSCellBackgroundStyleMiddle;
}
}
- (UIView*)backgroundViewForStyle:(JSCellBackgroundStyle)style {
NSMutableArray *stylePool = backgroundViewPool[style];
// if we have a reusable view available, remove it from the pool and return it
if ([stylePool count] > 0) {
UIView *reusableView = stylePool[0];
[stylePool removeObject:reusableView];
return reusableView;
// if we don't have any reusable views, make a new one and return it
} else {
UIView *newView = [[UIView alloc] init];
NSLog(#"Created a new view for style %i", style);
switch (style) {
case JSCellBackgroundStyleTop:
newView.backgroundColor = [UIColor blueColor];
break;
case JSCellBackgroundStyleMiddle:
newView.backgroundColor = [UIColor greenColor];
break;
case JSCellBackgroundStyleBottom:
newView.backgroundColor = [UIColor yellowColor];
break;
case JSCellBackgroundStyleSolitary:
newView.backgroundColor = [UIColor redColor];
break;
}
return newView;
}
}
#end
Although you could very easily get away with dumping all views into one reuse pool, it complicates some of the looping logic and this way is easier to comprehend.
First and foremost, I would check why this kind of caching is necessary. If it's a performance problem, I would check that the problem is indeed the views, and not something else like too many blended layers!
Regarding the caching, there are several approaches. At least three come to mind:
For each of the four backgrounds, register an own cell reuse identifier. Then set the background view depending on the reuse identifier.
Use an own cache for the background views, and reuse background views from there.
Use the same class for background views on all cells, and set the content on them only.
The first solution is quite easy to implement, but it holds the risk that the UITableView ends up holding lots of cells for reusing that are not needed. Also, if you need more types of cells, you would have to provide cells for each type/background combination.
While the second solution reuses cell backgrounds, you have to write an own cache for those, and to set/unset backgrounds where necessary.
The third solution only works if the background view can be configured to show the background for the respective cell. It would reuse the content only, not the background views themselves.
Here is an early screenshot of a test for the second solution:
Here is the implementation:
#implementation RootViewController
{
NSMutableDictionary *_backgroundViews;
}
- (void)viewDidLoad
{
_backgroundViews = [NSMutableDictionary dictionary];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 100;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return section / 10 + 1;
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.backgroundView = nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.backgroundColor = [UIColor clearColor];
if (!cell.backgroundView || ![cell.backgroundView isKindOfClass:[UIImageView class]]) {
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger maxRow = [tableView numberOfRowsInSection:section] - 1;
NSString *imageName = nil;
UIEdgeInsets insets = UIEdgeInsetsZero;
if (maxRow == 0) {
// single cell
imageName = #"singlebackground";
insets = UIEdgeInsetsMake(12, 12, 12, 12);
} else if (row == 0) {
// top cell
imageName = #"topbackground";
insets = UIEdgeInsetsMake(12, 12, 0, 12);
} else if (row == maxRow) {
// bottom cell
imageName = #"bottombackground";
insets = UIEdgeInsetsMake(0, 12, 12, 12);
} else {
// middle cell
imageName = #"middlebackground";
insets = UIEdgeInsetsMake(0, 12, 0, 12);
}
NSMutableSet *backgrounds = [_backgroundViews objectForKey:imageName];
if (backgrounds == nil) {
backgrounds = [NSMutableSet set];
[_backgroundViews setObject:backgrounds forKey:imageName];
}
UIImageView *backgroundView = nil;
for (UIImageView *candidate in backgrounds) {
if (candidate.superview == nil) {
backgroundView = candidate;
break;
}
}
if (backgroundView == nil) {
backgroundView = [[UIImageView alloc] init];
backgroundView.image = [[UIImage imageNamed:imageName] resizableImageWithCapInsets:insets];
backgroundView.backgroundColor = [UIColor whiteColor];
backgroundView.opaque = YES;
}
cell.backgroundView = backgroundView;
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[cell textLabel] setText:[NSString stringWithFormat:#"[%d, %d]", [indexPath section], [indexPath row]]];
return cell;
}
If you would like to check it out, here are the images I used (non-retina only and too big, but hey, it's only an example):
singlebackground.png:
topbackground.png:
middlebackground.png:
bottombackground.png:
EDIT - using images as a background view
Given your comments on my answer it seems like you want to display images in the background view of your cells. It is not clear if these images are compiled into your app as resources or downloaded from a service. Regardless, you can use the same UIImage instance in multiple UIImageView instances. So as you create your cells, you can create a new UIImageView on the fly for use as the background view and then set the image property to the appropriate UIImage based on the cell's indexPath.
If the images are compiled into your app then [UIImage imageNamed:#""] uses an iOS implemented cache and will perform well. If you are downloading images (presumably on a background thread) then you will need to implement a disk and/or memory cache for your image data.
ORIGINAL ANSWER
When you configure your cell in tableView:cellForRowAtIndexPath: use the cell identifier to use the built-in caching of the tableView to cache cells with the various background views for you.
Something like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *singleCellID = #"single";
static NSString *firstCellID = #"first";
static NSString *middleCellID = #"middle";
static NSString *lastCellID = #"last";
NSString *cellID = nil;
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger maxRow = [tableView numberOfRowsInSection:section] - 1;
UIColor *color = nil;
if (maxRow == 0) {
// single cell
cellID = singleCellID;
} else if (row == 0) {
// top cell
cellID = firstCellID;
} else if (row == maxRow) {
// bottom cell
cellID = lastCellID;
} else {
// middle cell
cellID = middleCellID;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
if (cell == nil) {
if (cellID == singleCellID) {
// create single cell
cell = ...
cell.backgroundView = ...
}
else if (cellID == firstCellID) {
// create first cell
cell = ...
cell.backgroundView = ...
}
else if (cellID == lastCellID) {
// create last cell
cell = ...
cell.backgroundView = ...
}
else {
// create middle cell
cell = ...
cell.backgroundView = ...
}
}
}
[EDIT]
Ok, so, as far as you use custom background view, I think you should assign your background view to cell's .backgroundView property in the tableView:cellForRowAtIndexPath: method and do not use your own views caching mechanism, because table view caches entire cell with all it's subviews - you assign background view when you create cell and later just update it's backgroundColor with proper value (in your case, based on index path).
Also, this is just a suggestion, your background view might be obscured with cell's other content (e.g. you added something to .contentView) - try setting cell / contentView .alpha value to 0.5 to be able to see through it. Code is still related - this method is called every time UITableView needs new cell to display on the screen
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = nil;
static NSString* identifer = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:identifer];
if(cell==nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifer];
cell.backgroundView = [YourCustomView new];//assign your custom background view here
}
cell.textLabel.text = [NSString stringWithFormat:#"%d",indexPath.row];
//update background view's color based on index path row
if(indexPath.row==0)
cell.backgroundView.backgroundColor = [UIColor redColor];
else if(indexPath.row==1)
cell.backgroundView.backgroundColor = [UIColor yellowColor];
else
cell.backgroundView.backgroundColor = [UIColor blueColor];
return cell;
}
you can not use a view twice at the same time, which would occure when you have more than 3 cells. The reuising mechanism of the table should be sufficient enough.
I am not sure why u want to handle the backgroundViews seperatly from the cells.
Anyways, i altered your code so that there is no bug with missing backgroundViews:
NOTE! i did use ARC.
static NSString *identifierSingle = #"single";
static NSString *identifierTop = #"top";
static NSString *identifierBtm = #"btm";
static NSString *identifierMid = #"mid";
#implementation RootViewController {
NSMutableDictionary *_backgroundViewPool;
}
- (id)initWithStyle:(UITableViewStyle)style
{
if (self = [super initWithStyle:style]) {
_backgroundViewPool = [[NSMutableDictionary alloc] init];
UITableView *tableView = [self tableView];
[tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"cell"];
}
return self;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 6;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (section == 0) {
return 1;
}
return 10;
}
- (NSString *)tableView:(UITableView *)tableView identifierForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger maxRow = [tableView numberOfRowsInSection:section] - 1;
if (maxRow == 0) {
// single cell
return identifierSingle;
} else if (row == 0) {
// top cell
return identifierTop;
} else if (row == maxRow) {
// bottom cell
return identifierBtm;
} else {
// middle cell
return identifierMid;
}
}
- (UIColor *)tableView:(UITableView *)tableView colorForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger maxRow = [tableView numberOfRowsInSection:section] - 1;
UIColor *color = nil;
if (maxRow == 0) {
// single cell
color = [UIColor blueColor];
} else if (row == 0) {
// top cell
color = [UIColor redColor];
} else if (row == maxRow) {
// bottom cell
color = [UIColor greenColor];
} else {
// middle cell
color = [UIColor yellowColor];
}
return color;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *colorIdentifier = [self tableView:tableView identifierForRowAtIndexPath:indexPath];
NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[cell textLabel] setText:[NSString stringWithFormat:#"[%d, %d]", [indexPath section], [indexPath row]]];
[[cell textLabel] setBackgroundColor:[UIColor clearColor]];
NSMutableSet *set = [self backgroundPoolForIdentifier:colorIdentifier];
UIView *backgroundView = [set anyObject];;
if (backgroundView) {
[set removeObject:backgroundView];
} else {
backgroundView = [[UIView alloc] init];
[backgroundView setBackgroundColor:[self tableView:tableView colorForRowAtIndexPath:indexPath]];
}
[cell setBackgroundView:backgroundView];
return cell;
}
#pragma mark - Table view delegate
- (NSMutableSet *)backgroundPoolForIdentifier:(NSString *)identifier {
NSMutableSet *set = [_backgroundViewPool valueForKey:identifier];
if (!set) {
set = [[NSMutableSet alloc] init];
[_backgroundViewPool setValue:set forKey:identifier];
}
return set;
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
[[self backgroundPoolForIdentifier:cell.reuseIdentifier] addObject:cell.backgroundView];
}
#end
Your original implementation didnt work because in cellForRowAtIndexPath: you sometimes returning a nil object. UITableView framework then passes that *cell object to willDisplayCell:(UITableViewCell*).
ie:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
....
return cell;
// cell maybe nil
If you do indeed perfer using your own caching mechanism, you can simply return a plain UITableViewCell object, dequeued if available or create a new one if none is available for reuse.
ie:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
return [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]
|| [UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]
;
}
Then, there is the "view can only be added to 1 superview limit" causing your cached view to appear jumping.
Tried several things to do this but finally got satisfied on this very basic solutions, i know it's not really a charming one but it gave me smooth scrolling, you can try this if you like:
NSMutableArray *_viewArray;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
#define kTotalNoOfRows 1000
_viewArray = [[NSMutableArray alloc] initWithCapacity:kTotalNoOfRows];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
for (int i = 0; i < kTotalNoOfRows; i++) {
UIView * backGroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
if (kTotalNoOfRows == 0)
[backGroundView setBackgroundColor:[UIColor redColor]];
else if (i == 0)
[backGroundView setBackgroundColor:[UIColor greenColor]];
else if (i == (kTotalNoOfRows - 1))
[backGroundView setBackgroundColor:[UIColor blueColor]];
else
[backGroundView setBackgroundColor:[UIColor yellowColor]];
[_viewArray addObject:backGroundView];
}
return kTotalNoOfRows;
}
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = nil;
static NSString* middleCell = #"middleCell";
cell = [tableView dequeueReusableCellWithIdentifier:middleCell];
if(cell==nil) {
NSInteger maxRow = [tableView numberOfRowsInSection:indexPath.section] - 1;
if (maxRow != 0 && indexPath.row != 0 && indexPath.row != maxRow) {
middleCell = nil;
}
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:middleCell];
cell.backgroundView = [_viewArray objectAtIndex:indexPath.row];//assign your custom background view here
[cell.textLabel setBackgroundColor:[UIColor clearColor]];
}
cell.textLabel.text = [NSString stringWithFormat:#"%d",indexPath.row];
return cell;
}
Also I would like to mention my journey towards here; So what I have tried is
created a dictionary of Views:
UIView * _topView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
UIView * _bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
UIView * _middleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
UIView * _lonelyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[_topView setBackgroundColor:[UIColor redColor]];
[_bottomView setBackgroundColor:[UIColor greenColor]];
[_middleView setBackgroundColor:[UIColor blueColor]];
[_lonelyView setBackgroundColor:[UIColor yellowColor]];
_viewDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
_topView, #"topView",
_bottomView, #"bottomView",
_middleView, #"middleView",
_lonelyView, #"lonelyView", nil];
returned copy of these view with unarchiver
- (UIView *) getBackgroundViewWith : (NSInteger) maxRow currentRow : (NSInteger) row{
if (maxRow == 0) {
return (UIView *)[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:[_viewDictionary valueForKey:#"lonelyView"]]];//[[_viewDictionary valueForKey:#"lonelyView"] copy];
} else if (row == 0) {
return (UIView *)[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:[_viewDictionary valueForKey:#"topView"]]];//[[_viewDictionary valueForKey:#"topView"] copy];
} else if (row == maxRow) {
return (UIView *)[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:[_viewDictionary valueForKey:#"bottomView"]]];//[[_viewDictionary valueForKey:#"bottomView"] copy];
} else {
return (UIView *)[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:[_viewDictionary valueForKey:#"middleView"]]];//[[_viewDictionary valueForKey:#"middleView"] copy];
}
return nil;
}
But it crash, while scrolling table with SIGBART. Thus gave up with this.
It's been a while since I've worked with tableviews, but I vaguely recall running into this problem. I believe the calls to the tableView:willDisplayCell:forRowAtIndexPath: method are threaded. When the user scrolls very fast multiple calls can get out simultaneously. In that case, given your current code, it is possible for multiple cells to get assigned the same view which will then cause the blank spaces.
If you use #synchronized(anObject){} to prevent multiple threads from running the same code simultaneously, you should be able to prevent the problem.
#synchronized (self) {
UIView *backgroundView = nil;
for (UIView *bg in _backgroundViewPool) {
if (color == [bg backgroundColor]) {
backgroundView = bg;
break;
}
}
if (backgroundView) {
[backgroundView retain];
[_backgroundViewPool removeObject:backgroundView];
} else {
backgroundView = [[UIView alloc] init];
[backgroundView setBackgroundColor:color];
}
}
According to my understanding of apple docs, when a cell is dequeued, it still has all it's views and settings you previously set.
Therefore, if you set a background view to cell it would still be there when it's dequeued and if it's a new cell it won't have background view.
I believe you don't need the background view pool since the OS handles that for you, so you can just reuse the BG view as you reuse the cell and do something like that in willDisplayCell: only
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSInteger maxRow = [tableView numberOfRowsInSection:section] - 1;
UIColor *color = nil;
if (maxRow == 0) {
// single cell
color = [UIColor blueColor];
} else if (row == 0) {
// top cell
color = [UIColor redColor];
} else if (row == maxRow) {
// bottom cell
color = [UIColor greenColor];
} else {
// middle cell
color = [UIColor yellowColor];
}
UIView *backgroundView = nil;
//***This is the different part***//
if (cell.backgroundView != nil) {
NSLog(#"Old Cell, reuse BG View");
backgroundView = cell.backgroundView;
} else {
NSLog(#"New Cell, Create New BG View");
backgroundView = [[UIView alloc] init];
[cell setBackgroundView:[backgroundView autorelease]];
}
[backgroundView setBackgroundColor:color];
}
Like that there is no need for the code didEndDisplayingCell: as well.
i have a tableview. i add image to the cells when clicked on the cell in didselectrow method by using cell.conteview addsubview. but the problem is if i click on 1st cell it changes the image and when i click on another cell image will appears but the old image is not removed from the previous cell. This is happening for all cells in table view if a cell is clicked.
i used the code as follows
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
Practices *bPractices = [topics objectAtIndex:indexPath.row];
UIImageView *clickView;
//[cell.contentView removeFromSuperview];
clickView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 315, 82)];
clickView.image = [UIImage imageNamed:#"list_bg_hover.png"];
[cell.contentView addSubview:clickView];
[clickView release];
UILabel *labelText= [[UILabel alloc]initWithFrame:CGRectMake(90, 30, 320, 20)];
labelText.text=bPractices.practices_title;
labelText.backgroundColor=[UIColor clearColor];
[cell.contentView addSubview:labelText];
}
pls help me how to solve this issue
Thanks in advance
I did something similar, but not with images but buttons. If a cell wasn't selected yet and gets tabbed, the size is changed and a certain and individual number of buttons is added. If another cell was selected, this one gets closed.
Code from form the UITableViewController
interface:
int openedCellIndex;//<-what cell is selected
int buttonCounter;
implementation:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ( indexPath.row != openedCellIndex )
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[TRTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier]
autorelease];
//cell.frame = CGRectMake (0,0, 320, 100);
}
id <P_E_P1Article> article = [self.fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = article.name;
if ( self.cellBackgroundColor )
cell.backgroundColor = self.cellBackgroundColor;
return cell;
}
else {
//get article
id <P_E_P1Article> article = [self.fetchedResultsController objectAtIndexPath:indexPath];
//number of buttons
int buttons = [article.specification count];
int height = 50 * ceil(buttons / 2) + 50;
//construct special cell
UITableViewCell *cell = [[[TRTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"CellBig"]
autorelease];
cell.frame = CGRectMake (0,0, 320, 150);
cell.textLabel.text = #"";
//add label
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(20, 10, 280, 30)];
label.font = [UIFont boldSystemFontOfSize:17];
label.text = article.name;
label.backgroundColor = [UIColor clearColor];
[cell addSubview:label];
[label release];
if ( buttonMapper == nil )
self.buttonMapper = [NSMutableDictionary dictionaryWithCapacity:10];
//NSLog (#" bla: %#", article.isFreePrice);
//see if we have a free prized article
//create the buttons
NSEnumerator *enumerator = [article.specification objectEnumerator];
id <P_E_P1ArticleSpecification> spec;
int count = 0;
NSArray *specs =
[self sortedArticleSpecifications:article];
for ( spec in specs ) {
//see which row and col the button is in
int row = floor ( count / 2 );
int col = count % 2;
//define button position
int left = 20 + col * 145;
int top = 45 + row * 50;
//create button
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(left, top, 135, 40);
[cell addSubview:button];
[button setTitleColor:[UIColor blackColor] forState:0];
[button addTarget:self
action:#selector(buttonTapped:)
forControlEvents:UIControlEventTouchUpInside];
//remember which article the button is attached to
buttonCounter++;
button.tag = buttonCounter;
[buttonMapper setValue:spec forKey:[NSString stringWithFormat:#"bla%d",buttonCounter]];
count++;
}
if ( self.cellBackgroundColor )
cell.backgroundColor = self.cellBackgroundColor;
return cell;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ( openedCellIndex == indexPath.row )
{
id <P_E_P1Article> article = [self.fetchedResultsController objectAtIndexPath:indexPath];
int count = [article.specification count];
int height = 50 * ceil(count / 2.) + 50;
return height;
}
return 50;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self openRow:indexPath.row];
[self.searchBar resignFirstResponder];
}
- (void) openRow:(int) index
{
if ( index == openedCellIndex ) return;
int oldIndex = openedCellIndex;
openedCellIndex = index;
NSUInteger indexArr[] = {0, oldIndex};
NSIndexPath *oldPath = [NSIndexPath indexPathWithIndexes:indexArr length:2];
NSUInteger indexArr2[] = {0, openedCellIndex};
NSIndexPath *newPath = [NSIndexPath indexPathWithIndexes:indexArr2 length:2];
//update view
[(UITableView *)self.view beginUpdates];
if ( oldIndex >= 0 )
[(UITableView *)self.view reloadRowsAtIndexPaths:[NSArray arrayWithObject:oldPath]
withRowAnimation:UITableViewRowAnimationFade];
if (openedCellIndex >=0 )
[(UITableView *)self.view reloadRowsAtIndexPaths:[NSArray arrayWithObject:newPath]
withRowAnimation:UITableViewRowAnimationFade];
[(UITableView *)self.view endUpdates];
}
You could also subclass UITableViewCell and handle the select/deselect process in the setSelected method. Then use your custom cell as the type for the prototype cell instead of the default.