So i have been trying for a little while now to create a table view with expandable sections and one non expandable section. One of the expandable sections should have 3 text fields inside them in which you can edit the test inside the text field. I was able to get that working bt the moment you collapse the section and expand it again the textfield suddenly duplicates itself above and sometimes swaps itself out with the above cell. Ihavent been able to figure out why or how to make it not do that.
The idea is when the user enters text in the field and selects enter the text is stored into an array.
the code:
- (void)viewDidLoad{
[super viewDidLoad];
if (!expandedSections){
expandedSections = [[NSMutableIndexSet alloc] init];
}
manualSensorName = [[NSMutableArray alloc]initWithObjects: #"Sensor",#"",#"2",#"T", nil];
}
- (void)didReceiveMemoryWarning{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Expanding
- (BOOL)tableView:(UITableView *)tableView canCollapseSection:(NSInteger)section{
if (section>0) return YES;
return NO;
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if ([self tableView:tableView canCollapseSection:section])
{
if ([expandedSections containsIndex:section])
{
return 5; // return rows when expanded
}
return 1; // only top row showing
}
// Return the number of rows in the section.
return 1;
}
- (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];
}
// Configure the cell...
if ([self tableView:tableView canCollapseSection:indexPath.section]){
if (!indexPath.row){
// first row
cell.textLabel.text = #"Expandable"; // only top row showing
if ([expandedSections containsIndex:indexPath.section])
{
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrowUP.png"]];
cell.accessoryView = arrow;
}
else
{
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrowDOWN.png"]];
cell.accessoryView = arrow;
}
}
else {
if (indexPath.row == 1){
NSString *flightNumText = [manualSensorName objectAtIndex:indexPath.row];
cell.textLabel.text = flightNumText;
}
else if (indexPath.row == 2){
txtManualSensor = [[UITextField alloc] initWithFrame:CGRectMake(180, 5, 120, 30)];
txtManualSensor.placeholder = #"Select";
txtManualSensor.delegate = self;
txtManualSensor.autocorrectionType = UITextAutocorrectionTypeNo;
txtManualSensor.backgroundColor = [UIColor whiteColor];
[txtManualSensor setBorderStyle:UITextBorderStyleBezel];
[txtManualSensor setTextAlignment:NSTextAlignmentCenter];
[txtManualSensor setReturnKeyType:UIReturnKeyDone];
// UITextField *playerTextField = [[UITextField alloc] initWithFrame:CGRectMake(180, 5, 120, 30)];
// playerTextField.adjustsFontSizeToFitWidth = YES;
// playerTextField.textColor = [UIColor blackColor];
// playerTextField.placeholder = #"SAMPLE";
// playerTextField.tag = 200;
// playerTextField.delegate = self;
// [cell.contentView addSubview:playerTextField];
cell.textLabel.text = #"Sensor Name";
[cell addSubview:txtManualSensor];
}
else if (indexPath.row == 3){
cell.textLabel.text = #"Some Detail";
cell.accessoryView = nil;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
}
else {
cell.accessoryView = nil;
cell.textLabel.text = #"Normal Cell";
}
return cell;
}
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField {
[manualSensorName replaceObjectAtIndex:2 withObject:textField.text];
return YES;
}
-(BOOL) textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if ([self tableView:tableView canCollapseSection:indexPath.section]){
if (!indexPath.row){
[tableView beginUpdates];
// only first row toggles exapand/collapse
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSInteger section = indexPath.section;
BOOL currentlyExpanded = [expandedSections containsIndex:section];
NSInteger rows;
NSMutableArray *tmpArray = [NSMutableArray array];
if (currentlyExpanded) {
rows = [self tableView:tableView numberOfRowsInSection:section];
[expandedSections removeIndex:section];
}
else {
[expandedSections addIndex:section];
rows = [self tableView:tableView numberOfRowsInSection:section];
}
for (int i=1; i<rows; i++) {
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i inSection:section];
[tmpArray addObject:tmpIndexPath];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (currentlyExpanded) {
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrowDOWN.png"]];
[tableView deleteRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationFade];
cell.accessoryView = arrow;
}
else {
UIImageView *arrow = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"arrowUP.png"]];
[tableView insertRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationFade];
cell.accessoryView = arrow;
}
NSLog(#"tableview row is %ld in section %ld",(long)indexPath.row,(long)indexPath.section);
[tableView endUpdates];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSLog(#"selected row is %ld in section %ld",(long)indexPath.row,(long)indexPath.section);
if (indexPath.row == 1) {
// update text fields in cell table view
}
}
}
It may be as simple as replacing UITableViewRowAnimationTop by UITableViewRowAnimationFade:
When changing indexes in didSelectRowAtIndexPath, UITableViewCells change physical location (remember that the UITableView is a UIScrollView), and the scroller can't keep track of what your intent is.
UITableViewRowAnimationTop attempts to adjust the scrolling location, but fails.
Other design considerations:
Do not mix the model (the array of data to be displayed) with your view model (the UI displaying the model). In didSelectRowAtIndexPath, you should first re-order your model, then apply it to the cells
Consider not changing indexes on the fly: you may prefer a model that actually reflects the view structure, i.e. a tree.
Have you noticed you are not respecting - (void)tableView:(UITableView *)tableView and sometimes using self tableView:tableView or self.customTableView in the same method? You should use the tableView passed to you.
I have a method setting up value for table view for multi-selection row
- (id)initWithTitle:(NSString *)aTitle options:(NSArray *)aOptions matchingArray:(NSArray *)matchArray xy:(CGPoint)point size:(CGSize)size isMultiple:(BOOL)isMultiple
{
isMultipleSelection=isMultiple;
float height = MIN(size.height, DROPDOWNVIEW_HEADER_HEIGHT+[aOptions count]*44);
CGRect rect = CGRectMake(point.x, point.y, size.width, height);
if (self = [super initWithFrame:rect])
{
self.backgroundColor = [UIColor clearColor];
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = CGSizeMake(2.5, 2.5);
self.layer.shadowRadius = 2.0f;
self.layer.shadowOpacity = 0.5f;
_kTitleText = [aTitle copy];
_kDropDownOption = #[#"India",#"Swaziland",#"Africa",#"Australlia",#"Pakistan",#"Srilanka",#"Mexico",#"United Kingdom",#"United States",#"Portugal"];
_kMatchingArray = #[#"United States",#"Swaziland"];
finalarray=[[NSMutableArray alloc]init];
for(int i = 0;i<[_kMatchingArray count];i++)
{
for(int j= 0;j<[_kDropDownOption count];j++)
{
if([[_kMatchingArray objectAtIndex:i] isEqualToString:[_kDropDownOption objectAtIndex:j]])
{
NSLog(#"%d",j);
NSString *str = [NSString stringWithFormat:#"%d",j];
[finalarray addObject:str];
}
else {
}
}
}
NSLog(#"finalArray:%#",finalarray);
// NSLog(#"%#",_kMatchingArray);
self.arryData=[[NSMutableArray alloc]init];
_kTableView = [[UITableView alloc] initWithFrame:CGRectMake(DROPDOWNVIEW_SCREENINSET,
DROPDOWNVIEW_SCREENINSET + DROPDOWNVIEW_HEADER_HEIGHT,
rect.size.width - 2 * DROPDOWNVIEW_SCREENINSET,
rect.size.height - 2 * DROPDOWNVIEW_SCREENINSET - DROPDOWNVIEW_HEADER_HEIGHT - RADIUS)];
_kTableView.separatorColor = [UIColor colorWithWhite:1 alpha:.2];
_kTableView.separatorInset = UIEdgeInsetsZero;
_kTableView.backgroundColor = [UIColor clearColor];
_kTableView.dataSource = self;
_kTableView.delegate = self;
[self addSubview:_kTableView];
if (isMultipleSelection) {
UIButton *btnDone=[UIButton buttonWithType:UIButtonTypeCustom];
[btnDone setFrame:CGRectMake(rect.origin.x+182,rect.origin.y-45, 82, 31)];
[btnDone setImage:[UIImage imageNamed:#"done#2x.png"] forState:UIControlStateNormal];
[btnDone addTarget:self action:#selector(Click_Done) forControlEvents: UIControlEventTouchUpInside];
[self addSubview:btnDone];
}
}
return self;
}
using this i have create a tableview fetching the values
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_kDropDownOption count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cel lForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentity = #"DropDownViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentity];
cell = [[DropDownViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentity];
NSInteger row = [indexPath row];
// NSIndexPath *selectedIndexPath = [finalarray addObject:indexPath.row];
UIImageView *imgarrow=[[UIImageView alloc]init ];
NSLog(#"aray:%#",self.arryData);
if([self.arryData containsObject:indexPath]){
imgarrow.frame=CGRectMake(230,2, 27, 27);
imgarrow.image=[UIImage imageNamed:#"check_mark#2x.png"];
} else
imgarrow.image=nil;
[cell addSubview:imgarrow];
cell.textLabel.text = [_kDropDownOption objectAtIndex:row] ;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (isMultipleSelection) {
if([self.arryData containsObject:indexPath]){
[self.arryData removeObject:indexPath];
} else {
[self.arryData addObject:indexPath];
}
[tableView reloadData];
} else {
if (self.delegate && [self.delegate respondsToSelector:#selector(DropDownListView:didSelectedIndex:)]) {
[self.delegate DropDownListView:self didSelectedIndex:[indexPath row]];
}
// dismiss self
[self fadeOut];
}
}
I have two array one have total records of the tableview and another one have initially selected values.I have compare the two arrays and get matching indexpath. My problem was how to set check mark image on matched values row?
if ([_strProIDNS isEqualToString:strUNAssignNS])
{
tblViewCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
tblViewCell.accessoryType = UITableViewCellAccessoryNone;
UISegmentedControl changes tableView datasource.
After I described [myView addSubview:_tableView];
instead of [self.view addSubview:_tableView];,
daySegmentedControl process stopped working.
I have this code.
ViewController.m
#implementation ViewController
{
int _tableType;
NSArray *_data1;
NSArray *_data2;
}
#synthesize segment;
#synthesize daySegment;
#synthesize myView;
- (void)viewDidLoad
{
[super viewDidLoad];
_tableType = 1;
_data1 = #[#[#"A",#"B",#"C"]];
_data2 = #[#[#"D",#"E",#"F"]];
myView = [[UIView alloc]initWithFrame:CGRectMake(-1, 44, 340, 480)];
[self.view addSubview:myView];
myView.opaque = NO;
myView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.0f];
[self.view bringSubviewToFront:myView];
[self segmentView];
[self daySegmentView];
[self dayTableView];
}
- (void)segmentView
{
NSArray *SegmentContent = [NSArray arrayWithObjects:#"View1",#"View2",nil];
segment = [[UISegmentedControl alloc] initWithItems:WDSegmentContent];
segment.frame = CGRectMake(-2, 20, 326, 25);
segment.selectedSegmentIndex = 0;
[segment addTarget:self action:#selector(WDSegmentAction:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:segment];
}
- (void)WDSegmentAction:(id)sender
{
switch (segment.selectedSegmentIndex){
case 0:
[self dayTableView];
break;
case 1:
[self dayTableView];
break;
default:
break;
}
}
- (void)daySegmentView
{
NSArray *daySegmentContent = [NSArray arrayWithObjects:#"A",#"D",nil];
daySegment = [[UISegmentedControl alloc] initWithItems:daySegmentContent];
daySegment.frame = CGRectMake(0, 0, 326, 25);
daySegment.selectedSegmentIndex = 0;
[daySegment addTarget:self action:#selector(daySegmentAction:) forControlEvents:UIControlEventValueChanged];
[myView addSubview:daySegment];
}
- (void)daySegmentAction:(id)sender
{
switch (segment.selectedSegmentIndex){
case 0:
_tableType = 1;
[self.tableView reloadData];
break;
case 1:
_tableType = 2;
[self.tableView reloadData];
break;
default:
break;
}
}
- (void)dayTableView
{
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 30, 320, 480)];
_tableView.dataSource = self;
_tableView.delegate = self;
[myView addSubview:_tableView];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [_data1[section]count];
}
- (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];
}
NSString *data;
if (_tableType == 1) {
data = _data1[indexPath.section][indexPath.row];
} else if (_tableType == 2){
data = _data2[indexPath.section][indexPath.row];
}
cell.textLabel.text = data;
return cell;
}
#end
Any idea on how I could fix it?
I used different approach for changing segments. Just for illustration:
in segment changed method:
[_tableView reloadData];
In your numberOfRowsInSection:
if(_segControl.selectedSegmentIndex == 0)
{
return [dataSourceOne count];
}else
{
return [dataSourceTwo count];
}
}
In your heightForRowAtIndexPath
if(_segControl.selectedSegmentIndex == 0) { //one
return 60;
} else { //two
return 70;
}
In your cellForRowAtIndexPath
if(_segControl.selectedSegmentIndex == 0) {
//generate and populate cell for type one
}else
{
//generate and populate cell for type two
}
Use daySegment.selectedSegmentIndex instead of segment.selectedSegmentIndex in
- (void)daySegmentAction:(id)sender
I'm implementing in my PFQueryTableViewController ( Use Parse.com ) the works Swipe to Delete . When I go to perform the action of cancellation of the cell gives me this error and the app crashes.
Unigo 10/08/2013 15:40:50.683 [ 29142 : a0b ] User Online : between
Unigo 10/08/2013 15:40:54.495 [ 29142 : a0b ] * Assertion failure in
- [ UITableView _endCellAnimationsWithContext : ] , / SourceCache/UIKit_Sim/UIKit-2903.2/UITableView.m 1330 Unigo 10/08/2013
15:40:54.499 [ 29142 : a0b ] * Terminating app two to uncaught
exception ' NSInternalInconsistencyException ' , reason : 'Invalid
update : invalid number of rows in section 0 . The number of rows
contained in an existing section after the update ( 5 ) must be equal
to the number of rows contained in That section before the update ( 5
) , plus or minus the number of rows inserted or deleted from That
section (0 inserted , 1 deleted ) and plus or minus the number of rows
moved into or out of That section (0 moved in , 0 moved out). '
Can you help me figure out where I'm wrong ?
#implementation FFEsami
#synthesize FFNewLabelEsameInAttesa,FFViewEsameInAttesaAvviso,clock,ShowPanel,ArrayMutable;
-(id)initWithCoder:(NSCoder *)FFaDecoder {
self = [super initWithCoder:FFaDecoder];
if (self) {
self.parseClassName = FF_ESAMI_CLASS;
self.pullToRefreshEnabled = YES;
}
return self;
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
[self SetGraphSectionView];
[self queryForTable];
self.tableView.allowsSelectionDuringEditing = YES;
self.tableView.allowsMultipleSelectionDuringEditing = YES;
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
[self.navigationItem.leftBarButtonItem setTintColor:[UIColor whiteColor]];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
[self loadObjects];
}
#pragma mark - Parse
- (void)objectsDidLoad:(NSError *)error {
[super objectsDidLoad:error];
// This method is called every time objects are loaded from Parse via the PFQuery
}
- (void)objectsWillLoad {
[super objectsWillLoad];
// This method is called before a PFQuery is fired to get more objects
}
- (PFQuery *)queryForTable {
PFQuery *FFQueryPerTableView = [PFQuery queryWithClassName:FF_ESAMI_CLASS ];
[FFQueryPerTableView whereKey: FF_ESAMI_USER equalTo: [PFUser currentUser]];
[FFQueryPerTableView orderByDescending:FF_ESAMI_DATA_STRING];
if ([self.objects count] == 0) {
}
return FFQueryPerTableView;
}
-(void)SetGraphSectionView {
FFViewEsameInAttesaAvviso =[[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 50)] ;
FFNewLabelEsameInAttesa = [[UILabel alloc]initWithFrame:CGRectMake(60, -1, 200, 50)];
FFNewLabelEsameInAttesa.numberOfLines = 2;
FFNewLabelEsameInAttesa.textAlignment = NSTextAlignmentLeft;
FFNewLabelEsameInAttesa.textAlignment = NSTextAlignmentLeft;
FFNewLabelEsameInAttesa.lineBreakMode = NSLineBreakByTruncatingTail;
clock = [[UIImageView alloc] initWithFrame:CGRectMake(17, 11, 25, 25)];
// ShowPanel = [[UIImageView alloc] initWithFrame:CGRectMake(280, 27, 20, 20)];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 50.0;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
if(section==0){
PFQuery *query = [PFQuery queryWithClassName:FF_ESAMI_CLASS];
[query whereKey:FF_ESAMI_USER equalTo:[PFUser currentUser]];
[query whereKey:FF_ESAMI_STATUS equalTo:[NSNumber numberWithBool:YES]];
[query orderByAscending:FF_ESAMI_DATA_STRING];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!object) {
NSString *titolo = [NSString stringWithFormat:#"Nessun esame imminente"];
FFNewLabelEsameInAttesa.text = titolo;
FFNewLabelEsameInAttesa.font = [UIFont fontWithName:#"HelveticaNeue" size:13];
FFNewLabelEsameInAttesa.textColor = [UIColor grayColor];
FFNewLabelEsameInAttesa.numberOfLines = 2;
FFViewEsameInAttesaAvviso.backgroundColor = [UIColor clearColor];
clock.image= [UIImage imageNamed:#"FFIMG_InAttesa"];
} else {
NSString *titolo = [NSString stringWithFormat:#"%#", [object objectForKey:FF_ESAMI_TITOLO]];
FFNewLabelEsameInAttesa.text = titolo;
FFNewLabelEsameInAttesa.font = [UIFont fontWithName:#"HelveticaNeue" size:13];
FFNewLabelEsameInAttesa.textColor = [UIColor whiteColor];
FFViewEsameInAttesaAvviso.backgroundColor = [UIColor colorWithRed:(203/255.0) green:(162/255.0) blue:(86/255.0) alpha:(0.90)];
clock.image= [UIImage imageNamed:#"FFIMG_InAttesaBianco"]; }
}];
ShowPanel.image= [UIImage imageNamed:#"FFDownView"];
[FFViewEsameInAttesaAvviso addSubview:FFNewLabelEsameInAttesa];
[FFViewEsameInAttesaAvviso addSubview:clock];
[FFViewEsameInAttesaAvviso addSubview:ShowPanel];
}
return FFViewEsameInAttesaAvviso;
}
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 0;
}
- (FFCustomListaEsamiCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
FFCustomListaEsamiCell *cell = (FFCustomListaEsamiCell * )[self.tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
if (!cell) {
cell = [[FFCustomListaEsamiCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"Cell"];
}
NSString *text = [object objectForKey:FF_ESAMI_TITOLO];
cell.FFTitoloEsameLabel.numberOfLines = 2;
cell.FFTitoloEsameLabel.text = text;
NSDateFormatter *FFDataFormattata = [[NSDateFormatter alloc] init];
[FFDataFormattata setDateFormat:FF_DATE_FORMATTER];
cell.FFDataEsameLabel.text = [NSString stringWithFormat: #"%#",[FFDataFormattata stringFromDate:[object objectForKey:FF_ESAMI_DATA_STRING]]];
PFObject *rowObject = [self.objects objectAtIndex:indexPath.row];
if([[rowObject objectForKey:FF_ESAMI_STATUS] boolValue])
{
//DATO CONVALIDATO
cell.last.image = [UIImage imageNamed:#"FFIMG_InAttesa"];
/*UIImage *btn = [UIImage imageNamed:#"FF_ChechOk"];
[cell.FFInserisciConvalida setImage:btn forState:UIControlStateNormal];
[cell.FFInserisciConvalida addTarget:self
action:#selector(FFConvalidaDato)
forControlEvents:UIControlEventTouchUpInside];
[cell.FFAltreAzioni addTarget:self
action:#selector(ActionSheetForStateInAttesa)
forControlEvents:UIControlEventTouchUpInside];*/
} else {
//DATO NON CONVALIDATO
//UIImage *btn = [UIImage imageNamed:#"FFIMG_ClockTAG"];
/* [cell.FFInserisciConvalida setImage:btn forState:UIControlStateNormal];
[cell.FFInserisciConvalida addTarget:self
action:#selector(FFMettiInAttesa)
forControlEvents:UIControlEventTouchUpInside];
[cell.FFAltreAzioni addTarget:self
action:#selector(ActionSheetForStateConvalidato)
forControlEvents:UIControlEventTouchUpInside];*/
}
return cell;
}
/*
// Override if you need to change the ordering of objects in the table.
- (PFObject *)objectAtIndex:(NSIndexPath *)indexPath {
return [objects objectAtIndex:indexPath.row];
}
*/
/*
// Override to customize the look of the cell that allows the user to load the next page of objects.
// The default implementation is a UITableViewCellStyleDefault cell with simple labels.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForNextPageAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"NextPage";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = #"Load more...";
return cell;
}
*/
#pragma mark - Table view data source
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
PFObject *object = [self.objects objectAtIndex:indexPath.row];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
[self loadObjects];
}];[tableView reloadRowsAtIndexPaths:[tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationFade];
// [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
}
#end
You have this code:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 0;
}
which is telling the table to have 0 cells in it after the update when the table is expecting 4. If you are using static cells in the storyboard then you should not implement the numberOfRowsInSection function.
If you remove that code then you should have no problems.
EDIT:
Or if you do want the numberOfRowsInSection function then you need to hook up a datasource to the tableview. This would allow you to return the count of the datasource. Then, when you delete a row you would remove the corresponding object in your datasource so that the count is 1 less than it previously was.
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.