SetImage() method removes titleLabel for UIButton - ios

My wish is to make centered image(left) and next to it(right) the label.
Without setting an image, there was a perfectly centered titleLabel:
btnWhatsapp.titleLabel?.adjustsFontSizeToFitWidth = true
btnWhatsapp.setTitle("WhatsApp", for: .normal)
Then I added this code to add an image:
btnWhatsapp.setImage(UIImage(named: "phoneIcon"), for: .normal)
btnWhatsapp.imageView?.layer.transform = CATransform3DMakeScale(0.5, 0.6, 0.5)
btnWhatsapp.imageView?.contentMode = .scaleAspectFit
, and this iswhat I got then:
, so the title disappeared.
Maybe the problem is that image uses more space than its actual size(the size shouldnt take more widht and height than the icon size). I saw this when changed images background(should be this much grey color):
btnWhatsapp.imageView?.backgroundColor = .gray
I tried to use the imageEdgeInsets but it is very hard to calculate it to fit perfectly on every iPhone.
This is the Attributes inspector of the button:

You can't set title and image at once by default, nor position them as you describe.
If you need to have a UIButton, I'd recommend to make a UIView (or possibly horizontal UIStackView) with UIImage and UILabel inside, position them with autolayout, then you can add this view to the UIButton as a subview.
let button = UIButton(type: .custom)
button.frame = viewFrame // This is the desired frame of your custom UIView or UIStackView
button.addSubview(customView)
You will be able to position the views easily for all sizes with this approach, but you will probably want to use autolayout in real word app, instead of hardcoded frames.
Example:
override func viewDidLoad() {
super.viewDidLoad()
let image = UIImageView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
label.text = "text"
let stack = UIStackView(arrangedSubviews: [image, label])
stack.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
stack.distribution = .fillEqually
let button = UIButton()
button.frame = CGRect(x: 0, y: 0, width: 100, height: 50)
button.addSubview(stack)
view.addSubview(button)
self.view.addSubview(button)
}

Set your button under your controller class like this:
let imageButton: UIButton = {
let b = UIButton(type: .custom)
b.backgroundColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
b.layer.cornerRadius = 12
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
let imageV = UIImageView()
imageV.image = UIImage(named: "yourImage")?.withRenderingMode(.alwaysTemplate)
imageV.tintColor = .white
imageV.contentMode = .scaleAspectFill
imageV.translatesAutoresizingMaskIntoConstraints = false
imageV.widthAnchor.constraint(equalToConstant: 30).isActive = true
let label = UILabel()
label.text = "WhatsApp"
label.textColor = .white
label.font = .systemFont(ofSize: 16, weight: .regular)
let stack = UIStackView(arrangedSubviews: [imageV, label])
stack.distribution = .fill
stack.spacing = 4
stack.axis = .horizontal
stack.translatesAutoresizingMaskIntoConstraints = false
b.addSubview(stack)
stack.heightAnchor.constraint(equalToConstant: 30).isActive = true
stack.widthAnchor.constraint(equalToConstant: 120).isActive = true
stack.centerXAnchor.constraint(equalTo: b.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: b.centerYAnchor).isActive = true
return b
}()
Now in viewDidLoad add button and set constraints in your view (in my case on top)
view.addSubview(imageButton)
imageButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
imageButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
imageButton.widthAnchor.constraint(equalToConstant: 200).isActive = true
imageButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
This is the result:

Related

Swift - UIView moves when changing tab controller tabs

Inside the viewDidAppear I have a function that contains this code, in order to make a UIView:
let contentView = UIView()
func addSleepingView() {
contentView.backgroundColor = .systemYellow.withAlphaComponent(0.25)
view.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
// Anchor your view right above the tabBar
contentView.bottomAnchor.constraint(equalTo: (tabBarController?.tabBar.topAnchor)!).isActive = true
contentView.heightAnchor.constraint(equalToConstant: 50).isActive = true
let label = UILabel()
label.text = "Test"
label.frame = CGRect(x: 0, y: 0, width: 25, height: 34.0)
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
let button = UIButton()
button.setImage(UIImage(systemName: "arrow.clockwise", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: UIControl.State.normal)
button.tintColor = .systemGray
button.frame = CGRect(x: self.view.bounds.width-42, y: 8, width: 34, height: 34.0)
button.isUserInteractionEnabled = true
button.addTarget(self, action: #selector(wakeupFunction), for: .touchUpInside)
contentView.addSubview(button)
let button2 = UIButton()
button2.setImage(UIImage(systemName: "exclamationmark.triangle", withConfiguration: UIImage.SymbolConfiguration(scale: .large)), for: UIControl.State.normal)
button2.tintColor = .label
button2.frame = CGRect(x: 8, y: 8, width: 34, height: 34.0)
button2.isUserInteractionEnabled = false
contentView.addSubview(button2)
contentView.bringSubviewToFront(button)
}
This is what it looks like:
Now this is exactly how I want it. The problem comes when I change tab. For example go to the last tab, and back to the first tab again. Then it looks like this:
What am I doing wrong here?
You can just add your code of activating constraints inside the DispatchQueue.main block
Updated Line you can change and it will start working as you're expectation.
// Anchor your view right above the tabBar
DispatchQueue.main.async
{
self.contentView.bottomAnchor.constraint(equalTo: (self.tabBarController?.tabBar.topAnchor)!).isActive = true
self.contentView.heightAnchor.constraint(equalToConstant: 50).isActive = true
}

Swift: Programmatic Autolayout broken by UNavigationController

I have inherited a UIKit app where my predecessor has written all the interface code by hand. I have a view controller, a simple login screen that works just fine, but when I add it to a UINavigationController it is oddly stretched. This doesn't make a lot of sense to me.
I feel like I must be missing some simple flag, or what do I need to do to make the Navcontroller play nice with this programmatic autolayout (which is hopefully my last ever)
// this a view controller extension
func apply(constraints: [NSLayoutConstraint]) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(constraints)
}
There's my handy dandy, seemingly sane utility for applying constraints. As an example, the nice little oval thing making my password textfield looks nice has these constraints set up.
// these constraints are being setup in the view controller
emailCapsuleView.apply(constraints: [
emailCapsuleView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24.0),
emailCapsuleView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
emailCapsuleView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8),
emailCapsuleView.heightAnchor.constraint(equalToConstant: Dimensions.inputFieldHeight)
])
Outside of a UINavigationController everything is fine,
but in a UINavigationController everythhing is super wide and broken (simulator shot)
The width is not the problem here - if I use a constant for the width, my subview is still off center despite having it's centerX sets to the view controller's view's center X.
Configure your navigation controller and after that try like this:
set your objects:
let emailTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Email address", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let passTextfield: UITextField = {
let tf = UITextField()
tf.backgroundColor = .white
tf.layer.borderWidth = 1
tf.layer.borderColor = UIColor.lightGray.cgColor
tf.setPadding(left: 10, right: 10)// use my extension below
tf.attributedPlaceholder = NSAttributedString(string: "Password", attributes: [.foregroundColor: UIColor.lightGray])
tf.layer.cornerRadius = 14
tf.clipsToBounds = true
tf.translatesAutoresizingMaskIntoConstraints = false
return tf
}()
let loginButton: UIButton = {
let b = UIButton(type: .system)
b.backgroundColor = .black
b.setTitle("Save Image", for: .normal)
b.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
b.setTitleColor(.white, for: .normal)
b.layer.cornerRadius = 14
b.clipsToBounds = true
b.translatesAutoresizingMaskIntoConstraints = false
return b
}()
let subBigTitle: UILabel = {
let l = UILabel()
l.text = "Your Big Title"
l.font = .systemFont(ofSize: 30, weight: .regular)
l.textColor = .black
l.textAlignment = .center
l.translatesAutoresizingMaskIntoConstraints = false
return l
}()
Now in viewDidLoad set stackView, bigTitle label and constraints:
view.addSubview(subBigTitle)
subBigTitle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
subBigTitle.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
subBigTitle.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
subBigTitle.heightAnchor.constraint(equalToConstant: 60).isActive = true
let stackView = UIStackView(arrangedSubviews: [emailTextfield, passTextfield, loginButton])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 10
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
stackView.heightAnchor.constraint(equalToConstant: 170).isActive = true
stackView.topAnchor.constraint(equalTo: subBigTitle.bottomAnchor, constant: 24).isActive = true
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
to add text padding to your textfield use my extension:
extension UITextField {
func setPadding(left: CGFloat, right: CGFloat){
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: left, height: self.frame.size.height))
self.leftView = paddingView
self.leftViewMode = .always
let paddingViewRight = UIView(frame: CGRect(x: 0, y: 0, width: right, height: self.frame.size.height))
self.rightView = paddingViewRight
self.rightViewMode = .always
}
}
and this is the result:

Why is space added between UIButtons?

I have the following code
let mostRead = UIButton(frame: CGRect(x: 0, y: tableView.frame.origin.y + tableView.frame.height, width: self.view.bounds.size.width, height: 160))
mostRead.setImage(UIImage.init(named: "mostread"), for: .normal)
mostRead.addTarget(self, action:#selector(self.goToMostRead(sender:)), for: .touchUpInside)
innerView.addSubview(mostRead)
let videoTitles = UIButton(frame: CGRect(x: 0, y: mostRead.frame.maxY, width: self.view.bounds.size.width, height: 160))
videoTitles.setImage(UIImage.init(named: "videotitles"), for: .normal)
videoTitles.addTarget(self, action:#selector(self.goToVideoTitles(sender:)), for: .touchUpInside)
innerView.addSubview(videoTitles)
let audioTitles = UIButton(frame: CGRect(x: 0, y: videoTitles.frame.maxY, width: self.view.bounds.size.width, height: 160))
audioTitles.setImage(UIImage.init(named: "audiotitles"), for: .normal)
audioTitles.addTarget(self, action:#selector(self.goToAudioTitles(sender:)), for: .touchUpInside)
innerView.addSubview(audioTitles)
Space gets added between the UI Buttons. If you notice in the picture, there's a lot of space between each image even though I specified no space between images. Does anyone know why that's happening?
You try to add it with stackView and auto layout like this:
first set your object under your controller class:
let buttonmostRead: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setBackgroundImage(UIImage(named: "mostRead"), for: .normal)
button.layer.cornerRadius = 8
button.clipsToBounds = true
return button
}()
let buttonvideoTitles: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setBackgroundImage(UIImage(named: "videoTitles"), for: .normal)
button.layer.cornerRadius = 8
button.clipsToBounds = true
return button
}()
let buttonaudiotitles: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.setBackgroundImage(UIImage(named: "audiotitles"), for: .normal)
button.layer.cornerRadius = 8
button.clipsToBounds = true
return button
}()
after that in viewDiLoad set staclView an constraints:
let stackView = UIStackView(arrangedSubviews: [buttonmostRead, buttonvideoTitles, buttonaudiotitles])
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 8
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
stackView.heightAnchor.constraint(equalToConstant: 466).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
stackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = true
to set space between button, change stackView spacing...
and this is the result
I put stackView in the center of the view but you can place them wherever you want by changing the constraints...

Trying to make a UIButton a circle

Im currently trying to make a button into a circle shape and here is my code:
var raffleButton:UIButton = {
var button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.titleLabel?.font = UIFont(name: "AvenirNext-Bold", size: 19)
button.setTitle("Tap To Enter Raffle!", for: .normal)
button.setTitleColor(UIColor.red, for: .normal)
button.backgroundColor = .white
button.layer.masksToBounds = true
button.frame = CGRect(x: 1600, y: 160, width: 160, height: 160)
button.layer.cornerRadius = button.frame.width/2
return button
}()
However when I run the code the button is disfigured and looks like the following... round and football like.
Is there any reason why the button looks like it does instead of being a circle?
You have some confusion as to whether you are using autolayout or not.
You have set translatesAutoresizingMaskIntoConstraints to false, but then you are trying to manipulate the frame.
If you add constraints to set the width & height, you will get a result closer to what you are after, but you will still need to adjust your label or font to get it to fit:
var raffleButton:UIButton = {
var button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.titleLabel?.font = UIFont(name: "AvenirNext-Bold", size: 19)
button.setTitle("Tap To Enter Raffle!!", for: .normal)
button.setTitleColor(UIColor.red, for: .normal)
button.backgroundColor = .white
button.layer.masksToBounds = true
button.heightAnchor.constraint(equalToConstant: 160).isActive = true
button.widthAnchor.constraint(equalToConstant: 160).isActive = true
button.layer.cornerRadius = 80
return button
}()
Since you set button.translatesAutoresizingMaskIntoConstraints = false, this code won't work button.frame = CGRect(x: 1600, y: 160, width: 160, height: 160). The button's size will be changed during the layout process(won't be (160, 160) anymore). Therefore, it's not a circle.
you can either change button.translatesAutoresizingMaskIntoConstraints = false or try setting button's cornerRadius in viewDidLayoutSubviews method.
your button Height same as button width and then set this code..
anyButton.backgroundColor = .clear
anyButton.layer.cornerRadius = anyButton.frame.height / 2
anyButton.layer.borderWidth = 1
anyButton.layer.borderColor = UIColor.black.cgColor
One semi-obvious thing to note, is if the button frame's height and width is not equal, it will not be a circle :).

Initialize a button with a title from a Label in swift

I want to initialize a button's title from a Label. I have this code:
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: CGPointZero, size: smallSquare))
But, I do not know how I can initialize a title with my label:
let label = UILabel(frame: CGRectMake(0, 0, 200, 21))
label.center = CGPointMake(160, 284)
label.textAlignment = NSTextAlignment.Center
label.text = "I'am a test label"
Normally, I use this property to add a title with a string:
button.setTitle("Button Title",for: .normal)
Is it possible to simply put my Label in my button's title?
Thanks in advance
You can add you custom label as subview to your button like Mike Alter mentioned in the comments like this (Note: Code is in Swift 3, but should be easy to adopt to Swift 2.*. Hints are in the code comments):
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: .zero, size: smallSquare))
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .red
// add the button to your view
view.addSubview(button)
// set constraints of your button
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 21))
label.translatesAutoresizingMaskIntoConstraints = false
label.center = CGPoint(x: 160, y: 284)
label.textAlignment = .center
label.text = "I'm a test label"
// add the label to your button
button.addSubview(label)
// set constraints of your label
label.centerXAnchor.constraint(equalTo: button.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
}
}
Result with your custom values looks like this (Just added the red background of the button so you see the frame of the button compared to the label frame):
Your label has a text property. You used it to set a value and you can also use it to get the value. The text is optional, so you need to unwrap.
let labelText = label.text ?? ""
button.setTitle(labelText, for: .normal)
if let textFromLabel = yourCustomLabel.text {
yourButton.setTitle(textFromLabel, .normal)
}
is what I'll suggest you to do

Resources