How to change iOS button size based on titleLabel length - ios

My buttons don't resize based on the text in their titleLabel. All of the titleLabel text is displayed, but it goes outside the bounds of the button
My buttons are in a stackview in a xib cell. The xib cell also has a label above the stackview.
I have two classes: ViewController (tableview and datasource), ViewCell (button outlets and actions)
ViewController code:
In my cellForRowAt method:
cell.choice1Button.titleLabel?.lineBreakMode = .byWordWrapping
cell.choice2Button.titleLabel?.lineBreakMode = .byWordWrapping
cell.choice3Button.titleLabel?.lineBreakMode = .byWordWrapping
cell.choice1Button.titleLabel?.numberOfLines = 0
cell.choice2Button.titleLabel?.numberOfLines = 0
cell.choice3Button.titleLabel?.numberOfLines = 0
cell.conceptLabel.text = question["concept"]!
cell.choice1Button.setTitle(question["choice1"]!, for: .normal)
cell.choice2Button.setTitle(question["choice2"]!, for: .normal)
cell.choice3Button.setTitle(question["choice3"]!, for: .normal)
cell.mcTableCellDelegate = self
I want the buttons to resize in height and for the text to wrap based on how long the titleLabel text is. Is this a constraints issue?

Related

UITableViewCell doesn't change height when some UIStackView's subviews are unhidded

As the title says, I have a custom UITableCell in which I have some UIStackViews. Each of those stacks contains many subviews but I just want to show three of them when the cell is displayed for the first time. If a user wants to see more, there is a [+] button that calls a method that adds the remaining.
The custom cell height is determined via UITableViewAutomaticDimension and it works perfectly for the first display of the cell but when I try to add and remove subviews to the stack, there are views that shouldn't be modified that lose they constraints and the ones that should be displayed doesn't do it in some cases. What I'd like is to show all the UILabels and the height of the cell to be updated.
The method that is called when the button [+] is pressed is the following:
#objc private func changeImage(sender: UIButton) {
let index = (Int(sender.accessibilityValue!)!)
let open : Bool = openItem[index]
let plateStack : UIStackView = plateStacks[index]
let plates : [UILabel] = platesViews[index]
if !open {
sender.setImage(UIImage(named: "less")?.withRenderingMode(.alwaysTemplate), for: .normal)
let nPlatesToAdd = max(platesViews[index].count - 3, 0)
for i in 0..<nPlatesToAdd {
let plate = plates[i + 3]
plateStack.addArrangedSubview(plate)
plate.leadingAnchor.constraint(equalTo: plateStack.leadingAnchor, constant: 0).isActive = true
plate.trailingAnchor.constraint(equalTo: plateStack.trailingAnchor, constant: 0).isActive = true
}
}
else {
sender.setImage(UIImage(named: "more")?.withRenderingMode(.alwaysTemplate), for: .normal)
var i = plateStack.arrangedSubviews.count - 1
while i > 2 {
let view = plateStack.arrangedSubviews[i]
plateStack.removeArrangedSubview(view)
view.removeFromSuperview()
i = i - 1
}
}
openItem[index] = !open
}
The first display of the cell (everything's ok) and after click on the [+] button:
It happened because tableView is already rendered its layout.
You might need to check some causes :
make sure the stackView constraint is properly put to contentView
stackView's distribution must be fill
After you change something that affects tableView height, you can use these code to update cell height without reloading the table:
tableView.beginUpdates()
tableView.endUpdates()

How to add padding to searchbar swift ios

I'm trying to shift down my searchbar to align with the bottom of its parent cell in a collection view.
if(searchBarCellId == widgets[indexPath.item]){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: searchBarCellId, for: indexPath) as! SearchBarCell
cell.backgroundColor = UIColor.white
cell.searchBar.backgroundImage = UIColor.white.image()
cell.searchBar.tintColor = UIColor.gray
let textFieldInsideSearchBar = cell.searchBar.value(forKey: "searchField") as? UITextField
textFieldInsideSearchBar?.backgroundColor = UIColor.lightGray
return cell
}
Is there a way to align the searchbar with the bottom cell of the cell programatically?
The custom xib editor does not allow me add constraints.
Updated with the full solution
Added constraint using editor
Edited codes:
if(searchBarCellId == widgets[indexPath.item]){
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: searchBarCellId, for: indexPath) as! SearchBarCell
cell.backgroundColor = UIColor.white
let frameWidth = UIScreen.main.bounds.width
let frameHeight = UIScreen.main.bounds.width*0.2
cell.searchBar.frame=CGRect(x: 0, y: frameHeight*0.47, width: frameWidth*0.7, height: frameHeight*0.5)
cell.searchBar.backgroundImage = UIColor.white.image()
cell.searchBar.tintColor = UIColor.gray
let textFieldInsideSearchBar = cell.searchBar.value(forKey: "searchField") as? UITextField
textFieldInsideSearchBar?.backgroundColor = UIColor.lightGray
return cell
}
InterfaceBuilder's .xib editor does indeed let you specify constraints. You need to turn on "Use Auto Layout" in the File Inspector (first tab in the right side pane).
Alternatively, you may set up constraints programmatically.
Edited for more info
How to add a bottom constraint to the search bar:
Select just the search bar. Then tap on the Add New Constraints button at bottom right of the .xib window (it looks like a tie fighter icon). Tap on the thin red line below the center box in the graphic, set the value to something appropriate (probably close to 0) and verify the view you're binding it to by pulling down the popup next to that value. Then tap the Add 1 Constraint button. That should bind the bottom of the search bar to the bottom of its parent view.
So the point is you just want to set up constraints for the search box relative to its immediate parent, which will be the view of the cell in your case, regardless that the xib doesn't know it's going to be for a cell.

How to make one UIButton, UILabel, & UITextField control all input

Currently I am working on my first iOS app, a ToDo Planner. Here is the layout for it.
Right now I have it set up in that each line has its own textField, button (checkmarked circle), and label (strike line). Like this:
if(textField == textField1) // if the first text field is pressed
{
let image = UIImage(named: "To Be Completed Circle.png")
button1 = UIButton(frame: CGRect(x: 340, y: 56, width: 30, height: 30));
button1.setBackgroundImage(image, for: UIControlState.normal)
self.view.addSubview(button1)
button1.addTarget(self, action:#selector(self.pressCheck), for: .touchUpInside)
}
And this:
if(button == button1) // if first button was pressed
{
button1.setBackgroundImage(image, for: UIControlState.normal) //set image to be of circle with checkmark
button1.removeTarget(nil, action: nil, for: .allEvents) //remove old target action
button1.addTarget(self, action:#selector(self.pressUnCheck), for: .touchUpInside) //add new target action for removing checkmark
self.view.addSubview(button1)
textField1.textColor = UIColor.gray //change textfield to a gray color
label1 = UILabel(frame: CGRect(x : 34, y : 70, width: 200, height: 2)) //add the strike line through textfield
label1.backgroundColor = UIColor(patternImage: labelImage!)
self.view.addSubview(label1)
}
Button1 Label1 textfield1 all correspond to the first line of input.
Button2 Label2 textfield2 etc...
The textfields were made using interface builder, the buttons/labels were made programmatically. I was wondering if there is any way (I'm sure there is) to use just one button one label and one textfield to control all user input? The buttons and labels all essentially to the same thing.
Let me know if I'm not too clear.
You can use a TableView with custom cells for your each line. A single cell can be designed with your required Label, TextField and Button. You can make it through the storyboard itself using prototype cell in tableView.
This should really be using a TableView.
That would allow you to set up one row of the table with the elements you want and reuse them the way you are asking about.
You should create a nib file of UITableViewCell class where you can design a single cell with Textfield, label, button. And you should use UITableview on storyboard and after that you should load that nib.

Can't interact with anything inside a UITableViewCell, I'm being blocked by a bool somewhere, where is it?

I'm constantly running into this issue, I don't know how to treat UITableViewCells as UIViews.
I added a button to my UITableViewCell:
let btnWidth = self.contentView.frame.size.width * 1.1
let btnHeight = self.contentView.frame.size.height * 1.6
btnJoinChannel = UIButton(frame: CGRect(x:0,y:0,width:btnWidth,height:btnHeight))
btnJoinChannel.setTitle("Join Channel", for: .normal)
btnJoinChannel.setTitleColor(.white, for: .normal)
btnJoinChannel.backgroundColor = .clear
btnJoinChannel.addTarget(self, action: #selector(JRegionCell.loadRegion), for: .touchUpInside)
self.contentView.addSubview(btnJoinChannel)
Buttons never work on UITableViewCells by default because some kind of touch gesture value overrides the new button.
What do I configure to prevent this override? I would like users to touch buttons inside UITableViewCells. Basically, my cells need to behave like UIViews.
Your button is bigger than content view. Buttons don't work if they are out of superview.

How to automatically adapt height of UITableViewCell containing a multiline UIButton?

A subclass of UITableViewCell contains a UIButton with multi-line text, i.e. property numberOfLines = 0.
The table view cells vary in height, so the cell height is set to UITableViewAutomaticDimension.
The cell height adapts when adding a UILabel with multiple text lines. However it does not adapt with a UIButton, in fact also the frame of the button does not adapt to the frame of its titleLabel.
What can I do to make the table view cell and its content view adapt to the button height?
class MyButtonCell: UITableViewCell {
var button: UIButton!
var buttonText: String?
convenience init(buttonText: String?) {
self.init()
self.buttonText = buttonText
button = UIButton(type: UIButtonType.System)
button.titleLabel?.numberOfLines = 0
button.titleLabel?.lineBreakMode = .ByWordWrapping
button.contentHorizontalAlignment = .Center
button.titleLabel?.textAlignment = .Center
button.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubView(button)
NSLayoutConstraint.activateConstraints([
button.topAnchor.constraintEqualToAnchor(self.contentView.topAnchor),
button.bottomAnchor.constraintEqualToAnchor(self.contentView.bottomAnchor),
button.rightAnchor.constraintEqualToAnchor(self.contentView.rightAnchor),
button.leftAnchor.constraintEqualToAnchor(self.contentView.leftAnchor)
])
button.setTitle(buttonText, forState: .Normal)
button.setTitleColor(buttonTextColor, forState: UIControlState.Normal)
button.titleLabel?.font = buttonFont
}
}
The cell height is calculated automatically with:
tableView.estimatedRowHeight = 100
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
UPDATE:
Example project on https://github.com/mtrezza/ButtonCellHeightBug
Filed Apple Bug Report #26170971.
The bug results in this:
Fully dynamic height for table view cell is achievable by 1) using estimated row height, 2) setting rowHeight to AutoDimension, 3) and most importantly using constraints in your xib/storyboard. The cell can contain buttons/labels or whatever UI components you'd like to have, as long as you constrain them properly, particularly to make sure things are constrained vertically so table view can figure out the cell height. And in this way you don't have to calculate height for dynamic text, no need for sizeToFit/sizeThatFit, and it works for different screen sizes.
You should use estimatedHeightForRowAtIndexPath. On your button, you can call sizeToFit(), which will resize it to contain the text.
Also, if you set the estimated size on the tableView (as you did), you usually don't need to call the heightForRowAtIndexPath, or estimatedHeightForRowAtIndexPath, and the tableView will set it for you.
EDIT:
I created a test project, and you seem to be correct. Using a UIButton setTitle does not resize the cell.
A workaround, is to do the calculation using a label in heightForRowAtIndexPath, and return that value + any padding. Then in cellForRowAtIndexPath, you can still set the title on the button and it will appear.
//paragraphs is just a string array.
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let label = UILabel(frame: CGRectMake(0,0,tableView.frame.width, <your prototype height>))
label.numberOfLines = 0
label.text = paragraphs[indexPath.row]
label.sizeToFit()
print(label.frame.height)
return label.frame.height
}
Bug in iOS?
The problem is that the internal UIButtonLabel resizes correctly, but the actual UIButton does not.
I've worked around this by extending UIButton and overriding a couple of things:
override func layoutSubviews() {
super.layoutSubviews()
self.titleLabel?.preferredMaxLayoutWidth = self.titleLabel?.frame.size.width ?? 0
}
override var intrinsicContentSize: CGSize {
return self.titleLabel?.intrinsicContentSize ?? CGSize.zero
}
You'll also need to make sure that titleLabel?.numberOfLines = 0 and titleLabel?.lineBreakMode = .byWordWrapping.

Resources