I have a UITableView that has 2 different custom UITableViewCell with 2 different unique identifiers.
I load the first custom UITableViewCell on Load and then the second custom UITableViewCell on Cell Select.
I know that my problem is related to the way I am reusing my Cells. But I've tried using
routineSearchSelectedResultCell * cell = (routineSearchSelectedResultCell*)[tableView dequeueReusableCellWithIdentifier:nil]; and the result is that the new UITableViewCell is empty and the properties do not get populated.
I've also tried [[[cell contentView] subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)]; but give me the same results.
How could I go around this problem??
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (idp && idp.row == indexPath.row ) {
return [self tableViewRoutineSelectedResult:tableView cellForRowAtIndexPath:indexPath];
}
NSString *cellIdentifier = #"routineSearchCell";
routineSearchCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[routineSearchCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
cell.routineName.text = _myDownloadedInfo[#"routines"][indexPath.row][#"routine"][#"routineName"];
cell.routineAuthor.text = _myDownloadedInfo[#"routines"][indexPath.row][#"routine"][#"username"];
return cell;
}
- (routineSearchSelectedResultCell *)tableViewRoutineSelectedResult:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary* myRoutine = _myDownloadedInfo[#"routines"][indexPath.row][#"routine"];
[self sortOutRoutineImages:myRoutine[#"routineType"]];
NSString *cellIdentifier = #"routineSearchSelectedResultCell";
routineSearchSelectedResultCell * cell = (routineSearchSelectedResultCell*)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[routineSearchSelectedResultCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
cell.label1.text =#"test";
cell.contentView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"BlackGradientSearch"]];
cell = [self iconRoutineImagesController:cell];
cell.imgInstruction.image = [UIImage imageNamed:#"InstructionSearchWhite"];
cell.imgVideos.image = [UIImage imageNamed:#"VideoSearchWhite"];
cell.routineName.text = myRoutine[#"routineName"];
[cell.download setBackgroundImage:[UIImage imageNamed:#"DownloadSearchWhite"] forState:UIControlStateNormal];
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ( idp ) {
NSIndexPath* pIdp = [[NSIndexPath alloc] init];
pIdp = idp;
idp = indexPath;
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView reloadRowsAtIndexPaths:#[pIdp] withRowAnimation:UITableViewRowAnimationRight];
[tableView endUpdates];
} else {
idp = [[NSIndexPath alloc]init];
idp = indexPath;
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationLeft];
[tableView endUpdates];
}
}
try to override
-(void)prepareForReuse
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCellWithIdentifier:. For performance reasons, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell. If the cell object does not have an associated reuse identifier, this method is not called. If you override this method, you must be sure to invoke the superclass implementation.
see the document
I think you are using the labels and they are overlapping..
Try to use this, It might help you..
for(UIView *view in cell.subviews){
if([view isKindOfClass:[UILabel class]]){
[view removeFromSuperview];
}
}
Related
I'm working on a prototype (I'm way too green on Objective C) which has a tableview with two different custom prototype cells, each with their own set of components, layout and height. The expected behavior is that when selected, the row expands and shows cell type b, and the previously selected row returns to the former height and cell type.
Ok, so the issue I'm having is that I can't get the previously selected row to change it's cell type, the height is working ok so I end up displaying a shorter version of the expanded cell but not having the components and layout of the desired cell type, and if I scroll through the tableview and return to the location where the previously selected row is it displays the correct cell type, as if it hasn't refreshed the rest of the rows.
Here's my code for tableView cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier;
UITableViewCell *cell = nil;
if(indexPath.row == self.selectedIndex){
CellIdentifier = #"ExpandedCell";
CollapsedCell *collapsedCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell = collapsedCell;
}else{
CellIdentifier = #"LocationCell";
LocationCell *locationCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
locationCell.transparentBorder.layer.borderWidth = 5.0f;
locationCell.transparentBorder.layer.borderColor = [UIColor colorWithRed:255/255 green:255/255 blue:255/255 alpha:0.7].CGColor;
cell = locationCell;
}
BHPlace *currentPlace = self.data[indexPath.row];
NSString *name = currentPlace.name;
LocationCell *locationCell = (LocationCell*)cell;
locationCell.nameLabel.text = name;
cell.backgroundColor = [UIColor clearColor];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
and here's the code for my didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:self.selectedIndex inSection:0] animated:YES];
[self.tableView beginUpdates];
self.selectedIndex = (int)indexPath.row;
NSArray *paths = #[indexPath];
[self.tableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
[self goToMarker:self.data[indexPath.row]];
}
As usual I'm sure it's a simple issue and there's something I'm not getting right but can't find the right approach,
Thank you all for your time
You just need to reload both the previously selected and the newly selected rows -
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSMutableArray *paths=[NSMutableArray new];
if (self.selectedIndexPath != nil) {
[paths addObject:self.selectedIndexPath];
[tableView deselectRowAtIndexPath:self.selectedIndexPath animated:YES];
}
[self.tableView beginUpdates];
self.selectedIndexPath = indexPath;
[paths addObject:indexPath];
[self.tableView reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
[self goToMarker:self.data[indexPath.row]];
}
Note that I changed your int property to an NSIndexPath * self.selectedIndexPath
In a specific row I apply the default UITableViewCell class with a text label and an accessory button. When the accessory button is clicked, the cell is expanded and I want to change the UITableViewCell to a custom subclass I have created. However even if the cell is expanded, it won't switch to the custom subclass. Any ideas what to fix?
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat result;
switch ([indexPath row])
{
case 2:
{
if ([indexPath isEqual:expandedRow]) {
return 100;
} else {
return 42;
}
}
return result;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *CellIdentifier;
NSString *CellIdentifierexp;
UITableViewCell *cell;
if (cell == nil) {
if (indexPath.row == 2) {
if ([indexPath isEqual:expandedRow]) {
cell = [[ExpandedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifierexp];
}else{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
switch ([indexPath row])
{
case 2:
{
if ([indexPath isEqual:expandedRow]) {
NSLog(#"bike");
ExpandedCell *expandedcell = (ExpandedCell *)cell;
[expandedcell.text setText:self.descr];
NSArray *indexPathArray=[NSArray arrayWithObject:indexPath];
[self.tableview reloadRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone];
} else {
cell.textLabel.text = #"Description";
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.textColor = UIColorFromRGB(0x2d5b3b);
// accessory type image
UIImage *image = [UIImage imageNamed:#"greenarrow.jpg"]; //or wherever you take your image from
UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(0,0, image.size.width, image.size.height)];
[button setBackgroundImage:image forState:UIControlStateNormal];
[button addTarget:self action:#selector(accessoryButtonTapped:withEvent:) forControlEvents:UIControlEventTouchDown];
button.userInteractionEnabled = YES;
cell.accessoryView = button;
}
break;
}
return cell;
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
switch ([indexPath row])
{
case 2:
{
NSLog(#"Detail Disclosure Tapped");
expandedRow = indexPath;
[tableview beginUpdates];
[tableview endUpdates];
}
}
}
- (void) accessoryButtonTapped: (UIControl *) button withEvent: (UIEvent *) event
{
NSIndexPath * indexPath = [self.tableview indexPathForRowAtPoint: [[[event touchesForView: button] anyObject] locationInView: tableview]];
if ( indexPath == nil )
return;
[self.tableview.delegate tableView: self.tableview accessoryButtonTappedForRowWithIndexPath: indexPath];
}
Wow, you're doing something terribly wrong in here:
ExpandedCell *expandedcell = (ExpandedCell *)cell;
[expandedcell.text setText:self.descr];
NSArray *indexPathArray=[NSArray arrayWithObject:indexPath];
[self.tableview reloadRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone];
What you should do is slightly different. On a button tap you just call the [self.tableview reloadRowsAtIndexPaths:indexPathArray withRowAnimation:UITableViewRowAnimationNone]; then in the cellForRowAtIndexPath you should return the custom cell of your subclass in the method if the indexPath matches. No need to update the tableView there.
And need I say that this looks like a very very strange switch statement to me:
switch ([indexPath row])
{
case 2:
{
NSLog(#"Detail Disclosure Tapped");
expandedRow = indexPath;
[tableview beginUpdates];
[tableview endUpdates];
}
}
I would simply put it like this:
if (indexPath.row == 2){
NSLog(#"Detail Disclosure Tapped");
expandedRow = indexPath;
[tableview beginUpdates];
[self.tableview reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableview endUpdates];
}
Running the code
expandedRow = indexPath;
[tableview beginUpdates];
[tableview endUpdates];
when the accessory button is tapped will only ask the table view to update the row heights, it won't actually request a new cell for any row, so no cell type change will happen (until you scroll the row off screen and then back on again).
So, you need to actually reload the selected row, not just begin and end updates.
this here...
UITableViewCell *cell;
if (cell == nil) {
Local vars dont init to nil , they start as garbage, maybe nil , maybe not.
Either way you probably are not making it into the branch.
You are going to want to dequeue a cell from the table depending on whether its an expanded row or not
UITableViewCell *cell = nil;
if(thisIsAnExpandedRow) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierexp];
if(!cell) {
cell = [[ExpandedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifierexp];
}
}
else {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
}
I have a simple tableView in which i change the cell whenever i click it in the function like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[self shuftleCellExpansion:indexPath.row];
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath,nil] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}
Second Functions:
-(BOOL) shuftleCellExpansion:(int) indexP
{
if([opendedCells containsIndex:indexP])
{
[opendedCells removeIndex:indexP];
return YES;
}
else
{
[opendedCells addIndex:indexP];
return YES;
}
}
Cell for index Function:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([self isCellInExpandedList:indexPath.row])
{
NSLog(#"Cell is in Expanded State %hhd for Index Path %ld",[self isCellInExpandedList:indexPath.row], (long)indexPath.row);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"poCell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"poCell"];
cell.backgroundColor = [UIColor blueColor];
}
[[cell textLabel] setText:[searchResults objectAtIndex:indexPath.row]];
[cell imageView].image = [UIImage imageNamed:#"someicon.png"];
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"poCell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"poCell"];
cell.backgroundColor = [UIColor redColor];
}
[[cell textLabel] setText:[searchResults objectAtIndex:indexPath.row]];
return cell;
}
No the issue is that when ever i change a specific cell and then scroll it the cell at after the specific cells (i.e. after total visible cells) i see the same changed cell .
What is the efficient way to get Rid of this?
Every cell which go off the screen goes to reuse pool (mostly all data are erase) and if you scroll back the cell is populated in cellForRowAtIndexPath: method again.
You need to change the data source if you want to keep the changes.
In your didSelectRowAtIndexPath: you need to also make the changes in the array (I assume you use array to read the data from in cellForRowAtIndexPath:) you use in cellForRowAtIndexPath: to populate cell.
Hope this help.
// EXTENDED
To make it work you need to amend shuftleCellExpansion: method. In opendedCells you swap the indexes, I believe it reflect the changes in table view. In the same method you have to reflect the changes for searchResults array as well (swap the object as you did for opendedCells).
I am having issues with my tableView not firing the didSelectRowAtIndexPath method. I have implemented the delegates as such:
#interface ViewController : UIViewController<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate>
And in my storyboard the tableView's data source and delegate are both pointed at the base View Controller. I have User Interactions enabled as well as Selection set to Single Selection, and it is not the TapGesture problem since my tap gestures are not bound to the view and I have checked and they do not fire.
This is the code for setting up the table:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return menuArray.count;
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
NSDictionary *menuItem = [menuArray objectAtIndex:indexPath.row];
cell.textLabel.text = menuItem[#"Title"];
cell.detailTextLabel.text = menuItem[#"Subtitle"];
return cell;
}
-(void)showMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(menuTable.frame.size.width, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(void)hideMenu{
[UIView animateWithDuration:.25 animations:^{
[content setFrame:CGRectMake(0, content.frame.origin.y, content.frame.size.width, content.frame.size.height)];
}];
}
-(IBAction)showMenuDown:(id)sender {
if(content.frame.origin.x == 0)
[self showMenu];
else
[self hideMenu];
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//whatever
}
The table is initially out of view on the storyboard (origin.x is set to -150), then when the user clicks on a button in the navigationBar, the view slides over to reveal it, which is what might be causing the problem I think.
Is there anything wrong with my code or implementation that would be causing this to not work?
If you already see your table populated with values from your dictionary then you can rule out data source and delegate as being the problem. i.e. your storyboard connections are working.
Your code looks fine to me. the only difference I see is I usually define my table like this. Try this and see if it helps.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSLog(#"Inside cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Your code here
// ....
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"menuCell"];
This will return nil in case there was never a cell created.
so checking if cell is nil is mandatory and if so, you need to create a cell.
static NSString *CellIdentifier = #"menuCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
}
as you are using storyboard you can alternatively use
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
for prototype cells. Make sure you use the same identifier in the storyboard and that you registered your the cell's class
- (void) viewDidLoad {
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"menuCell"];
}
I have a list which I have using as a check boxes. I have enable or disable Check mark on row on select. But when I scroll the list its make mark row after every 10 rows.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:indexPath];
if (oldCell.accessoryType == UITableViewCellAccessoryCheckmark)
{
oldCell.accessoryType = UITableViewCellAccessoryNone;
}
else
{
oldCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
}
UItableView reuses the cell in every scroll so using condition as per accessory type is not a good practice. You can Create an NSMutableArray with selected items and Check as per the Condition below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if ([selected containsIndex:indexPath.row]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
// Do the rest of your code
return cell;
}
in didSelectrowAtindexpath method you can Add and remove the Selected items.
Its because UITableView reuses the cell.
So, in the method cellForRowAtIndexPath, you will have to check for a particular cell (of a particular section and row), if it needs to be checked on, provide the accessory type.
If not needed for that cell, provide accessory type as none.
You need to put your logic to set accessory type for cell in cellForRowAtIndexPath, and to identify the cell to mark with check mark you can mark the object in the list in didSelectRowAtIndexPath: or manage an array of selected/unselected objects of the list here.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectedCell accessoryType] == UITableViewCellAccessoryNone) {
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
[NSMutableArray addObject:[AnotherMutableArray objectAtIndex:indexPath.row]];
} else {
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
[NSMutableArray removeObject:[AnotherMutableArray objectAtIndex:indexPath.row]];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
Also in your viewDidLoad, instantiate you both mutable arrays-
yourmutableArray1 = [[NSMutableArray alloc]init];
yourmutableArray2 = [[NSMutableArray alloc]init];