Swift 3 - Programmatically build a button with an image and label - ios

I am currently trying to build an interface (programmatically) with 5 x buttons, containing an image and a label.
I have done this successfully for ONE button using a UIStackView (holding the UIButton and a UIlabel).
I have two questions for this forum…
A UIButton can be built to display a title OR an image, can it have both?
Can a ‘for in’ loop be used to generate 5 x individual buttons? i.e: a way to re-use code instead of typing out code for 5 x buttons, 5 x labels, 5 x stack views.
My working UIStackView button code is as follows:
// Button
let btnSettings = UIButton()
// btnSettings.setTitle("Settings", for: .normal)
btnSettings.setImage(#imageLiteral(resourceName: "star-in-circle"), for: .normal)
btnSettings.heightAnchor.constraint(equalToConstant: 100.0).isActive = true
btnSettings.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
btnSettings.contentMode = .scaleAspectFit
btnSettings.addTarget(self, action: #selector(openSettings), for: .touchUpInside)
btnSettings.translatesAutoresizingMaskIntoConstraints = false
// Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.clear
textLabel.widthAnchor.constraint(equalToConstant: 100.0).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
textLabel.font = UIFont.boldSystemFont(ofSize: 18)
textLabel.text = "Settings"
textLabel.textAlignment = .center
// Stack View
let stackView = UIStackView()
stackView.axis = UILayoutConstraintAxis.vertical
stackView.distribution = UIStackViewDistribution.equalSpacing
stackView.alignment = UIStackViewAlignment.center
stackView.spacing = 1.0
stackView.addArrangedSubview(btnSettings)
stackView.addArrangedSubview(textLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(stackView)
// Constraints
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
Update / Solved Question 1
I needed to use the button.setBackgroundImage() in order for the button title to show up with the image.
btnSettings.setBackgroundImage(#imageLiteral(resourceName: "star-in-circle"), for: .normal)
btnSettings.setTitle("Button Title", for: .normal)
btnSettings.backgroundColor = UIColor.white

Of course, UIButton can have both an image or/and text. You can use:
button.setImage(image: UIImage?, for: UIControlState)
button.setTitle(title: String?, for: UIControlState)
Create a function that will return UIButton and do something like that:
let button = generateButton()
stackView.addArrangedSubview(button)

Related

2 UIButton with dynamic text and font size

I am using 2 UIButton in same y position. The one on the left is shorter in text than the one on the right:
example image
I am adding
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.minimumScaleFactor = 0.5
for each button. The problem is that one button font size get smaller than the other. For example, they are both with font size 18, one might get 12 and the other will stay 18. I want them both to be 15 (the larger possible keeping both texts visible)
The text inserted to the buttons changes so i can't use aspect ratio.
Also, i don't want to use the font of the button with the smaller text because the difference is sometimes to high.
You can do it programmatically with stack view, declare your buttons under your controller class:
let myButton: UIButton = {
let b = UIButton()
b.backgroundColor = .white
b.setTitle("Short button", for: .normal)
b.setTitleColor(.black, for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
b.layer.cornerRadius = 12
b.clipsToBounds = true
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.titleLabel?.minimumScaleFactor = 0.5
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let myButton2: UIButton = {
let b = UIButton()
b.backgroundColor = .white
b.setTitle("Much Much Much longer button", for: .normal)
b.setTitleColor(.black, for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 17, weight: .regular)
b.layer.cornerRadius = 12
b.clipsToBounds = true
b.titleLabel?.adjustsFontSizeToFitWidth = true
b.titleLabel?.minimumScaleFactor = 0.5
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
Now in viewDidLoad set stackView and constraints (I set 6 for space, but you can set your prefer space, look at the comment to do it):
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .darkGray
let stackView = UIStackView(arrangedSubviews: [myButton, myButton2])
stackView.distribution = .fillProportionally
stackView.spacing = 6 // change space betwenn buttons
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
This is the result shortButton/shortButton and shortButton/longerButton, the width of the buttons changes dynamically according to the text:

Constrain Button with its own width

Really struggling to constrain my buttons.
All I want is do setup my 2 buttons to have a height of 35 and their width should be whatever they need. Right now it looks like this (left & right buttons):
This is how I set them up:
let communityButton: UIButton = {
let v = UIButton()
v.setImage(UIImage(systemName: "person.3.fill"), for: .normal)
v.tintColor = UIColor.darkCustom
v.imageView?.contentMode = .scaleAspectFill
v.contentHorizontalAlignment = .fill
v.contentVerticalAlignment = .fill
v.translatesAutoresizingMaskIntoConstraints = false
v.addTarget(self, action: #selector(communityButtonTapped), for: .touchUpInside)
return v
}()
let profileButton: UIButton = {
let v = UIButton()
v.setImage(UIImage(systemName: "person.fill"), for: .normal)
v.tintColor = UIColor.darkCustom
v.imageView?.contentMode = .scaleAspectFill
v.contentHorizontalAlignment = .fill
v.contentVerticalAlignment = .fill
v.translatesAutoresizingMaskIntoConstraints = false
v.addTarget(self, action: #selector(profileButtonTapped), for: .touchUpInside)
return v
}()
Constraints:
//contrain communityButton
communityButton.centerYAnchor.constraint(equalTo: bottomBar.centerYAnchor),
communityButton.centerXAnchor.constraint(equalTo: bottomBar.centerXAnchor, constant: -view.frame.width/3.5),
communityButton.heightAnchor.constraint(equalToConstant: 35),
// constrain profileButton
profileButton.centerYAnchor.constraint(equalTo: bottomBar.centerYAnchor),
profileButton.centerXAnchor.constraint(equalTo: bottomBar.centerXAnchor, constant: view.frame.width/3.5),
profileButton.heightAnchor.constraint(equalToConstant: 35),
What is the right way to constrain here?
You might need to let autoLayout know that you want width to be flexible. You can do that by adding this line on both of your button definitions: -
// Replace "myButton" with your buttons name in your case "v"
myButton.autoresizingMask = [.flexibleWidth]
Edits: -
Since you are using system images, if you want your button to look bigger then you have to increase the font size on your image configuration. Edit your images by adding configuration with your preferred font sizes.
For example: -
let communityButton: UIButton = {
let v = UIButton()
//----
// NB: - Change the font size for bigger icons and viceversa
let imageSymbolConfiguration = UIImage.SymbolConfiguration(pointSize: 50, weight: .regular, scale: .large)
v.setImage(UIImage(systemName: "person.3.fill", withConfiguration: imageSymbolConfiguration), for: .normal)
// -----
return v
}()
let profileButton: UIButton = {
let v = UIButton()
// ----
// NB: - Change the font size for bigger icons and viceversa
let imageSymbolConfiguration = UIImage.SymbolConfiguration(pointSize: 50, weight: .regular, scale: .large)
v.setImage(UIImage(systemName: "person.fill", withConfiguration: imageSymbolConfiguration), for: .normal)
// ----
return v
}()
Explanation:-
From the official documentation
"Symbol image configuration objects include details such as the point size, scale, text style, weight, and font to apply to your symbol image. The system uses these details to determine which variant of the image to use and how to scale or style the image."

Set contentMode for Image inside Button in Swift

I am trying to setImage inside my UIButton but the image appears smaller inside the button although there is "free-space" and I also set the contentMode.
Button:
let noteButton: UIButton = {
let v = UIButton()
v.setImage(UIImage(systemName: "pencil"), for: .normal)
v.tintColor = UIColor.white
v.imageView?.contentMode = .scaleAspectFit
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
Constraints:
noteButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
noteButton.widthAnchor.constraint(equalToConstant: 30).isActive = true
noteButton.centerYAnchor.constraint(equalTo: itemView.centerYAnchor).isActive = true
noteButton.leadingAnchor.constraint(equalTo: linkButton.leadingAnchor, constant: 50).isActive = true
Result:
I fixed the issue. I just had to add these to lines:
v.contentHorizontalAlignment = .fill
v.contentVerticalAlignment = .fill

Setting background color for customView has no effect

I have a customView which I display by after a button-tap. My problem is that setting .backgroundColor has no effect and it is just a clear background.
CustomView:
let wishWishView: WishStackView = {
let v = WishStackView()
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
AddViewButton:
#objc func addWishButtonTapped(){
self.view.addSubview(self.wishWishView)
wishWishView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
wishWishView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
}
WishWishView is just a simple UIView with a StackView inside of it.
My guess would be that it is a UIStackView, which does not support having a background color.
You can read more about why this happens, and possible workarounds here: https://stackoverflow.com/a/34868367/3992237
Your code is Totally wrong, first you set your view like this:
let wishWishView: UIView = {
let v = UIView()
v.backgroundColor = .cyan
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
now set your stackView, in my example I put in 2 label in vertical axis:
let stackView: UIStackView = {
let label1 = UILabel()
label1.text = "Label 1"
label1.textColor = .red
label1.font = UIFont.systemFont(ofSize: 16)
let label2 = UILabel()
label2.text = "Label 2"
label2.textColor = .black
label2.font = UIFont.systemFont(ofSize: 16)
let sV = UIStackView(arrangedSubviews: [label1, label2])
sV.axis = .vertical
sV.distribution = .fillEqually
sV.translatesAutoresizingMaskIntoConstraints = false
return sV
}()
after that set your button:
let buttonAddView: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add View", for: .normal)
button.setTitleColor(.white, for: .normal)
button.backgroundColor = .red
button.addTarget(self, action: #selector(addWishButtonTapped), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
in viewDidLoad add button and set constraints
view.addSubview(buttonAddView)
buttonAddView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
buttonAddView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
buttonAddView.heightAnchor.constraint(equalToConstant: 50).isActive = true
buttonAddView.widthAnchor.constraint(equalToConstant: 120).isActive = true
now write the function that add the view with stack view inside when the button is tapped with right constraints, in your function you call only the position of the view but not the size of it. Write your function like this:
#objc func addWishButtonTapped(){
view.addSubview(self.wishWishView)
wishWishView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
wishWishView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
wishWishView.heightAnchor.constraint(equalToConstant: 100).isActive = true
wishWishView.widthAnchor.constraint(equalToConstant: 200).isActive = true
wishWishView.addSubview(stackView)
stackView.topAnchor.constraint(equalTo: wishWishView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: wishWishView.bottomAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: wishWishView.leadingAnchor, constant: 10).isActive = true
stackView.trailingAnchor.constraint(equalTo: wishWishView.trailingAnchor, constant: -10).isActive = true
}
Now simply change background of the wishWishView to automatically set stackView background, and that's it...

Swift - UIButton setting constraints programmatically

I am having problems with setting my height and width constraints for my UIButton. I have no idea why this code is not working:
let wishButton: UIButton = {
let v = UIButton()
v.setImage(UIImage(named: "wishButton"), for: .normal)
v.translatesAutoresizingMaskIntoConstraints = false
v.addTarget(self, action: #selector(wishButtonTapped), for: .touchUpInside)
return v
}()
These are my constraints:
wishButton.centerXAnchor.constraint(equalTo: popUpView.centerXAnchor).isActive = true
wishButton.centerYAnchor.constraint(equalTo: popUpView.centerYAnchor, constant: 150).isActive = true
wishButton.heightAnchor.constraint(equalToConstant: 100).isActive = true
wishButton.widthAnchor.constraint(equalToConstant: 100).isActive = true
This is how it looks at the moment:
It's weird because the other two constraints are working just fine. Probably just a stupid mistake but I am quite new and grateful for every help :)
Constraints have no issues. The issue is with the image. Your image is not big enough to fill the frame. You can set the .setBackgroundImage of the button to make it automatically scalable.
v.setBackgroundImage(UIImage(named: "wishButton"), for: .normal)
or you can make the content Alignment equal to .fill on both directions.
One thing I noticed is it doesn't look like you have set the frame of the button.
Try adding this line after you initialize the button:
v.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
I'm not sure if it is required but if your button isn't showing up at all, this may fix the issue
Add this to your UIButton initialisation, it will work
v.contentVerticalAlignment = .fill
v.contentHorizontalAlignment = .fill
It will scale your image.
let wishButton: UIButton = {
let v = UIButton()
v.setImage(UIImage(named: "wishButton"), for: .normal)
v.translatesAutoresizingMaskIntoConstraints = false
v.contentVerticalAlignment = .fill
v.contentHorizontalAlignment = .fill
v.addTarget(self, action: #selector(wishButtonTapped), for: .touchUpInside)
return v
}()

Resources