I have UICollectionView with custom flow layout, which purpose is availability to delete items. In order to retrieve index i use function:
-(void)aMethod:(UIButton*)sender{
[self.viewModel deleteAt:[sender tag]];
[self.myCollectionView reloadData];
}
Sometimes (in rare cases) i got crash. When i dig into it, i found that sometimes [sender tag] was incorrect, in fact, higher then array of items count. Why is that happen? I found that it send 8, when array only had 5 items.
Button is simple 40x40 width/height image, placed above UITableViewCell like this:
UIButton *button = [UIButton new];
[button setImage:[UIImage imageNamed:#"m_delete"] forState:UIControlStateNormal];
if (self.shouldEdit){
self.layout.longPressGestureRecognizer.minimumPressDuration = 0.3f;
NSLog(#"1 blk called");
[button addTarget:self
action:#selector(aMethod:)
forControlEvents:UIControlEventTouchUpInside];
[button setTag:indexPath.row];
[cell addSubview:button];
[button mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(cell.mas_left).with.offset(2);
make.top.equalTo(cell.mas_top).with.offset(2);
}];
}
I suspect that it's reusing old cell without updating the button's tag. Since you have conditions if (self.shouldEdit) which means the [button setTag:indexPath.row]; is not always called. You mentioned that the purpose of the buttons is to delete items so it might be that the tableView reused old / deleted cell (the one with tag 8) as one of the 5 cells and it doesn't call the setTag
You can take the setTag line outside the conditional so it will always update the button's tag every time the cell is created / reused.
Alternatively you can make sure the old button are removed from cell before reuse in UITableViewCell's prepareForReuse or inside cellForRowAtIndexPath right after dequeueReusableCellWithReuseIdentifier(...). Doing this you must always add new button because the old ones are removed from the cell
button tag is override every time when cellForRowAtIndexPath callled so dont set tag in that method
remove this line
[button setTag:indexPath.row];
use this one
-(void)aMethod:(UIButton*)sender
{
CGPoint point=[sender convertPoint:CGPointZero toView:collectiewname];
NSIndexPath *indexPath=[collectiewname indexPathForItemAtPoint:point];
NSLog(#"row :%ld",(long)indexPath.row)
}
Related
Having issues updating uibutton in specific uitableviewcell. When I change the button color it updates every button in uitableview. Attached my code below:
PFObject *post = [self.objectArray objectAtIndex:indexPath.section];
cell.likeBtn.layer.cornerRadius = 5.0f;
//To access button methods
[cell.likeBtn setTag:indexPath.section];
NSMutableDictionary *attributes = [NSMutableDictionary
dictionaryWithDictionary:[[TMMemoryCache sharedCache] objectForKey:post.objectId]];
BOOL likedByCurrentUser = [[attributes objectForKey:#"likedByCurrentUser"] boolValue];
if (likedByCurrentUser) {
cell.likeBtn.backgroundColor = [UIColor flatRedColor];
[cell.likeBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[cell.likeBtn setTitle:#"Liked" forState:UIControlStateNormal];
}else{
//NSLog(#"Did not like %#", post.objectId);
cell.likeBtn.backgroundColor = [UIColor flatBlueColor];
[cell.likeBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[cell.likeBtn setTitle:#"Like" forState:UIControlStateNormal];
}
is their a better way to update uibutton in just one specific cell?
There are two ways to do what you want, depending on how the updated information comes in:
When configuring the cell in cellForRowAtIndexPath:.
Let the cell manage its own contents.
More often than not, #1 is the way to go. When new information comes into your UITableViewController, do 1 of the following:
Call reloadData to force a refresh of the entire table.
Figure out which cells need to be updated and call reloadRowsAtIndexPaths:withRowAnimation: passing in the correct index path values.
In either of those scenarios, you'll set up the button correctly somewhere in your tableView:cellForRowAtIndexPath: method. Generally, I create my UITableViewCell subclasses to accept an object and configure themselves with the data in that object. That way, there's less code in my UITableViewController that deals with cell configuration.
A typical implementation of tableView:cellForRowAtIndexPath: looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Cell *cell = [tableView dequeueReusableCellWithIdentifier:#"item" forIndexPath:indexPath];
[cell configureForItem:self.dataSource[indexPath.row]];
return cell;
}
Each of your UITableViewCell instances will have their own button that will be configured by the data passed by the object in self.dataSource[indexPath.row]. That way, as cells are recycled, the button isn't just passed around, but a new one is recycled and configured each time a new cell is needed.
At a guess, without seeing more of your code I would say your issue is with your use of indexPath.section instead of indexPath.row
If you have multiple buttons per section then they will all be covered by this code.
I got the following code to show a button on first UITableViewCellin case the NSArray with the search results is empty. Now, my numberOfRowsInSection has NSArray + 1, this way, my table will always have, at least, one cell.
This is in my cellForRowAtIndexPath:
if ([cell.contentView viewWithTag:1]) {
[[cell.contentView viewWithTag:1] removeFromSuperview];
}
if ((self.searchDisplayController.isActive && indexPath.row == [self.searchResults count]) || (!self.searchDisplayController.isActive && indexPath.row == [[self pegarObjetosUnicos] count])) {
// Create UIButton
UIButton *botaoProcurar = [UIButton buttonWithType:UIButtonTypeRoundedRect];
botaoProcurar.frame = CGRectMake(0, 0, 320, 50);
botaoProcurar.showsTouchWhenHighlighted = YES;
[botaoProcurar setTitle:#"Procurar no Banco de Dados" forState:UIControlStateNormal];
botaoProcurar.tag = 1;
[botaoProcurar addTarget:self action:#selector(botaoProcurar) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:botaoProcurar];
cell.textLabel.text = #"";
return cell;
}
The code first removes a subview, in case it finds one, and then create and add the button as subviewin case the search array is empty.
It works perfectly, I don't have any problems with this code, but since I need to do something similar now, I'm just wondering if this is the best approach. I'm just not comfortable with running this code for every cell my tableview has to render. I thought about adding the button to the NSArray when the database connection returned empty, but still, it wouldn't be a nice code.
Any suggestions for a more objective and clean code would be great. If not, a "Go for it! you are doing fine", will work as well. :)
You should place the button in the header or footer of the tableview. If the array has results, Remove the footer or header of the tableview. If the array has no results add the view to the footer or header. You should place this code in your function that decides if the array has data or not. This way the placement of the UIBUtton is not cell dependent, so now you won't have to run it for each and every cell you will have in the tableview.
I try to display a tableView with a button on each cell. The title of each button is a variable (an ID) in order to keep this value.
NSString *valuebutton = [NSString stringWithFormat:#"%#", [contactInfoDict objectForKey:#"idparse"]];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(cell.frame.origin.x, cell.frame.origin.y + 12, 300,35);
[button addTarget:self action:#selector(customActionPressed:) forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor= [UIColor clearColor];
[button setTitle:valuebutton forState:UIControlStateNormal];
[cell.contentView addSubview:button];
return cell;
When I try to scroll the tableview on the device the value of the button change and I can see it be modified in live.
Did you see this issue before ?
Thanks for your help !
Alex.
Without more details it is difficult to debug. However, if you are using dynamic cells then any off screen cells are candidates for being recycled. If you use static cells, then any off screen cells are retained (not recycled). It is possible that off screen cells are being reused (assuming you are using dynamic cells) producing the behavior you described. If you are having issues with your cellForRowAtIndexPath method then that could be the issue too. Please share that code.
Also, looking at your initial code, you might want consider a dynamic prototype cell that includes the button in IB. Then all you need to do is reference the cell within cellForRowAtIndexPath, grab the button of the cell and set its title via the cell indexPath (i.e. cell 0 gets title 0, cell 1 gets title 1, etc.) -- would save you a bit of code.
I have a table, and based on a simple boolean, I add a subview containing an image to the right side of the cell using this code in cellForRowAtIndexPath:
if(myArray[indexPath.row]==YES){
int xpos=self.mainTableView.frame.size.width+self.mainTableView.frame.origin.x-24;
int ypos=(cell.frame.size.height/2)-(18/2);
UIImageView *imv=[[UIImageView alloc]initWithFrame:CGRectMake(xpos,ypos, 18, 18)];
imv.image=[UIImage imageNamed:#"transition-30"];
[cell.contentView addSubview:imv];
}
If when the value of "myArray[x]" is NO, how can I remove the previously added subview?
I've tried using: [cell.contentView removeFromSuperview] but that removes the entire contents of the cell - text included.
You have to call removeFromSuperview on imv --> [imv removeFromSuperview];
To get the previously added imv, you could set the tag property of your imv to a constant and get it at the next call with viewWithTag (see here)
So before adding the subview:
imv.tag = 1;
[cell.contentView addSubview:imv];
When deleting the subview you first have to trigger a reload of the desired cell with
reloadRowsAtIndexPaths:(NSArray *)indexPaths
withRowAnimation:(UITableViewRowAnimation)animation;
and then in cellForRowAtIndexPath you do the following if myArray[indexPath.row] == NO
UIImageView *imv = (UIImageView*)[cell.contentView viewWithTag:1];
[imv removeFromSuperview];
Using the tag property is bad practice so consider creating your own UITableViewCell
See here for a description how to do that.
I'm trying to create an friend request function. Where all the friend request would show up in a table and a player will get to click accept or decline. What I'm trying to do is create an accept button beside this UITableView that contains all the player's friend requests.
Here's my code.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *notificationCell = [tableView dequeuREusableCellWithIdentifier#"notificationCell" for IndexPath:indexPath];
NSArray *friendRequests = [self fetchAllFriendRequestsInArray];
NSManagedObject *friendRequestingRelationship = [friendRequests objectAtIndex:indexPath.row];
notificationCell.textLabel.text = [friendRequestingRelationship valueForKey:#"name"];
UIButton *acceptButton = [UiButton buttonWithType:UIButtonTypeSystem];
[acceptButton.frame = CGRectMake(notificationCell.frame.origin.x + 150, notificationcell.frame.origin.y -20, 80, 40);
[acceptButton setTitle:#"Accept" forState:UIControlStateNormal];
acceptButton.backgroundColor = [UIColor clearColor];
[acceptButton addTarget:self action:#selector(acceptButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[notificationCell.contentView addSubview:acceptButton];
return notificationCell;
}
Only the first notificationCell showed the friendrequester's name and Accept Button. Other notificationCells only showed other friendrequesters' names without the button. May I know what is wrong with my code such that I can allow the button to be shown on every single cell?
Thank you in advance!
The buttons are there, but they are clipped from the view. This line is the culprit:
acceptButton.frame = CGRectMake(notificationCell.frame.origin.x + 150, notificationcell.frame.origin.y -20, 80, 40);
You shouldn't add the origin of notificationCell to the button, because subview positions are relative to positions of their superviews.
This should give you the right look, but your code has other potential problems.
The line where you fetch all friend requests is probably too slow to be executed for each cell in the view. Make sure that the results are cached
Table view cells are recycled. When one of such recycled cells makes it to your code, it looks like your code adds a second button on top of the first one
Similarly, if a recycled cell with a button is returned for the cell that does not need a button, the old button would remain visible.
You may be better off using a prototype cell that already has a button on it. Instead of adding and removing that button, you could make the existing one visible or invisible, depending on the context.
This is a bad approach to solving this problem to begin with. You should go to the storyboard, and drop a button in a prototype cell. Then select that button, go to the attributes inspector, and set the "tag" to a number of your choice (for this example we will say 1). You can then get the button for each cell like so after grabbing the cell:
UIButton * acceptButton = (UIButton *)[cell viewWithTag: 1];
[acceptButton addTarget:self action:#selector(acceptButtonPressed) forControlEvents:UIControlEventTouchUpInside];
This is cleaner and will give you the results you want.