I have a button within a tableView that doesn't behave as I would like.
I've placed my button initialisation within my cell==nil block in the tableView cellForRowAtIndexPath. When I move it out of this block the button is drawn multiple times with all the other buttons on top so I'm unable to move it (unless this is set up wrong).
My button is a simple green tick or red cross and is set up with a selector. Each row height within the tableView differs depending on the amount of text within it. My button however, when in the cell==nil part, doesn't take into consideration cell.frame.height. It reuses the height of the cells from the first time the tableView is initialised.
This is the code for the button:
button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button =[[UIButton alloc]init];
float framePos_x = cell.frame.size.width/1.30;
float framePos_y = cell.frame.size.height - 55;
NSLog(#"%f",cell.frame.size.height);
button.frame = CGRectMake(framePos_x, framePos_y, 35, 35);
buttonImage = [UIImage imageNamed:#"Notification_Accepted"];
buttonImage_Pressed = [UIImage imageNamed:#"Notification_Reject"];
[button setBackgroundImage:buttonImage forState:UIControlStateNormal];
[button setImage:buttonImage_Pressed forState:UIControlStateSelected];
button.tag = [UserID intValue];
[button addTarget:self action:#selector(ResponseFlagButton:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:button];
The output of my NSLog is:
2014-08-03 17:03:49.724 TableApp[5540:60b] 120.000000
2014-08-03 17:03:49.774 TableApp[5540:60b] 100.000000
2014-08-03 17:03:49.853 TableApp[5540:60b] 100.000000
2014-08-03 17:03:49.936 TableApp[5540:60b] 100.000000
2014-08-03 17:03:51.287 TableApp[5540:60b] 100.000000
When this log is within the cell=nil block that's all it outputs, even when scrolling up and down. Out of the cell=nil block and it works fine - same as the button (except for it's redraw issue).
How would I get the button to be drawn once each time without it being place on top of the old button. As well as taking into consideration the variable cell height?
I would do a combination of both Michal's and Lev's answers.
Subclass UITableViewCell and make the button a property.
Instantiate the button during the cell's own instantiation. This way you will only be adding the button once.
Override the prepareForReuse method of your UITableViewCell subclass and change only the appearance of the button as needed.
Override the layoutSubviews method of your UITableViewCell subclass and calculate and set the new height for the cell. (You are implementing your own custom intrinsic size here. Cool!)
Subclass UITableViewCell and calculate the button's frame in cell's layoutSubviews method.
I believe you need to subclass UITableViewCell and implement the prepareForReuse method. In prepareForReuse you would remove the button. Not a 100% certain that will solve the issue you are describing.
Subclassing UITableViewCell might also make it easier to calculate the button size.
Related
I have a UITableView in Grouped format with a few cells in it. In the cells is a UIView to which I am adding another UIView. When I update the frame of the first UIView, it doesn't change the position of it nor the size.
The UIViews realign themselves correctly if the cell goes offscreen and onscreen again, so the actual transformation is being applied, it's just the re-rendering that's not happening properly.
The code for this is just in my cellForRowAtIndexPath:
[cell.cellMoney setBackgroundColor:[UIColor clearColor]];
[cell.cellMoney.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
UIView *sellMoneySubView = [UIMoneyDisplay createViewWithAmount:item.min_sale_unit_price fontSize:17.0];
[cell.cellMoney setFrame:CGRectMake(cell.frame.size.width-sellMoneySubView.frame.size.width-20, 7, sellMoneySubView.frame.size.width, sellMoneySubView.frame.size.height)];
[cell.cellMoney addSubview:sellMoneySubView];
[cell setNeedsLayout];
The sub UIView that I'm adding to the first UIView gets added and rendered properly, it's just the first's positioning that's gone weird.
Oh, I'm also using AutoLayout with the storyboard.
Addition: I fixed part of my problem, but the title is still valid. I also have a cell with an image that needs to be downloaded through the network, but that cell doesn't respond to any form of layout call either.
I fixed the problem with the position of the UIView by removing the middle view and adding straight to the cell's contentView.
instead of this
[cell setNeedsLayout]
use [cell layoutIfNeeded]
[cell.cellMoney setBackgroundColor:[UIColor clearColor]];
[cell.cellMoney.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
UIView *sellMoneySubView = [UIMoneyDisplay createViewWithAmount:item.min_sale_unit_price fontSize:17.0];
[cell.cellMoney setFrame:CGRectMake(cell.frame.size.width-sellMoneySubView.frame.size.width-20, 7, sellMoneySubView.frame.size.width, sellMoneySubView.frame.size.height)];
[cell.cellMoney addSubview:sellMoneySubView];
[cell layoutIfNeeded]; //use this
return cell;
This might helps you :)
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.
What I tried was this :-
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
[self.view addSubview:btn];
btn.translatesAutoresizingMaskIntoConstraints = NO;
[btn addTarget:self action:#selector(bringUpNextViewController:)
forControlEvents:UIControlEventTouchUpInside];
btn.titleLabel.font = [UIFont fontWithName:#"HelveticaNeue" size:14];
[btn setTitle:#"8" forState:UIControlStateNormal];
[self.view layoutIfNeeded];
NSLog(#"button size : %#", NSStringFromCGSize(btn.frame.size));
As output, I get this :
button size : {30, 29}
Then I gave setTitle string as nothing. The button width was still 30.
So why is this the case always?
I also tried giving a high compression resistance priority and high content hugging priority. Doesn't shrink to nothing.
The problem is also the fact that I want to reduce the width of the button simply based on its content, without giving any fixed width.
I could take the width of text and give the button the width, but I shouldn't be needing to do that either if the button was taking up the content width.
EDIT:
Its not the insets either which is causing the width to be 30. Ghost value.
A button is made of several subviews. It's very likely that the internal layout of a button has some default padding between the label and the button view itself.
Making a button like yours and examining the constraints shows the following:
button constraints (
"<NSContentSizeLayoutConstraint:0x8c40a60 H:[UIButton:0x8f29840(30)] Hug:250 CompressionResistance:750>",
"<NSContentSizeLayoutConstraint:0x8c55280 V:[UIButton:0x8f29840(29)] Hug:250 CompressionResistance:750>"
)
The 30 and 29 tie up with the size values you are seeing. The intrinsic content size property of the button also returns 30,29. Basically this is the minimum size for a button, in the absence of anything else.
It's not quite clear what you want or why you are bothered by this. Anything smaller will be a poor touch target, and a button with no label or image will be invisible anyway. If you add a longer title, the button will get bigger. If you add other constraints to force particular sizes, then these will override the intrinsic content size.
If you want the button to become invisible when it has no title, then you should explicitly hide it. This makes your intentions in the code much clearer and will prevent the user from accidentally hitting a button they can't really see.
I'm wondering if there is a minimum intrinsic content size for a uibutton?
Anyway, try doing...
[button invalidateIntrinsicContentSize];
Did you try [button sizeToFit];?
For custom buttons, I think that you will need to override:
- (CGSize)sizeThatFits:(CGSize)size;
Finally, if nothing other works, you can always try giving the button width from the text size like so
CGSize textsize = [yourText sizeWithFont:[UIFont fontWithName:#"HelveticaNeue" size:14]];
[button setFrame:CGRectMake(0,0,textsize.width, textsize.height)];
First define a constraint for button size in storyboard.
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *buttonSizeConst;
After that you can set it's size to whatever you want like this.
self.buttonSizeConst.constant = 65.0;
Edit: With this method you need to calculate your button width but I think you don't want to do that. You need to autoresize UIButton for it's content. For this you should give constraints like image below. It will expand to right when you change your title.
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.
I have a fairly simple TableViewController listing items that can be checked as they are collected. I have successfully implemented a word wrapped label in each cell, and updated heightForRowAtIndexPath such that each row is a suitable height. This is working well:
note: I have set a garish background color on the cell's contentView for testing purposes.
The problem comes when I try to add a checkmark button as the accessoryView for each cell:
UIImage *image = [UIImage imageNamed:#"checked.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, 44.0, 44.0);
button.frame = frame;
[button setImage:image forState:UIControlStateNormal]
button.backgroundColor = [UIColor redColor];
cell.accessoryView = button;
For some reason, the alignment is not quite right for my cells that span more than one line once the button size exceeds about 26x26 pixels.
The example above uses 44x44 as the button width.
Can anyone explain what is going on here? Why would there be a different alignment when the contentView is 2-lines versus one? Infact, for each extra line that the text uses, the alignment is increasingly off. I can add the code for the row text if it will help.
I found that this can be resolved by setting the height of the button in the accessory view to match what will be calculated for the row.
So, the code used in heightForRow:atIndexPath: should be copied in to the cellForRowAtIndexPath and used to set the button's height.
I had the same issue and unfortunately I neither can explain the odd behaviour, nor did I solve it. But I made some observations that may be help to others struggling with this issue.
I am using a UIImageView as an accessory view for my cells, and ended up fiddling around with the image dimensions. What finally worked were images to a size of about 16x16 pixels or lower.
As I just wanted a rather tiny image, this worked for me.