I'm trying to build a radio-style like button (a circle with a dot on the left, and text next to it on the right), pretty much like this:
I have 2 PDFs (link to one of them) containing images for selected and unselected radios. My code for the button is as follows:
let radioBtn = UIButton()
radioBtn.setImage(UIImage(named: "radio", in: .module, compatibleWith: nil), for: .normal)
radioBtn.setImage(UIImage(named: "radio_ticked", in: .module, compatibleWith: nil), for: .selected)
radioBtn.contentHorizontalAlignment = .leading
radioBtn.titleLabel?.adjustsFontSizeToFitWidth = true
radioBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
radioBtn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
radioBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
radioBtn.contentVerticalAlignment = .center
radioBtn.imageView?.contentMode = .scaleAspectFit
The problem is that the UIImage stretches over the whole width (the blue part) and there is no space for thee text to show:
What I want to accomplish is the radio completely on the left, and then the text next to it with some inset. How can this be achieved?
First, your code is not setting a Width for your button. In that case, the button will set the width of its imageView to the size of the image -- with your pdf
, that ends up being 510
pts wide.
So, couple options...
Use some scaling code to resize your image. If you're setting the button Height to 40, with 8-pts top and bottom insets, you need a 24x24
image.
Give your button a Width constraint, and calculate the imageView insets "on-the-fly."
Probably the easiest way to do that is with a UIButton
subclass, such as this:
class RadioButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentHorizontalAlignment = .leading
// 8-pts inset "padding" on all 4 sides
contentEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
// set title inset Left to content inset Left
var insets: UIEdgeInsets = titleEdgeInsets
insets.left = contentEdgeInsets.left
titleEdgeInsets = insets
// set images for .normal and .selected
if let img = UIImage(named: "radio") {
setImage(img, for: .normal)
}
if let img = UIImage(named: "radio_ticked") {
setImage(img, for: .selected)
}
}
override func layoutSubviews() {
super.layoutSubviews()
// make sure an image was set (otherwise, there is no imageView)
if let imgView = imageView {
// get height of imageView
let h = imgView.frame.height
// get current (default) image edge insets
var insets: UIEdgeInsets = imageEdgeInsets
// set inset Right to width of self minus imageView Height + Left and Right content insets
insets.right = bounds.width - (h + contentEdgeInsets.left + contentEdgeInsets.right)
// update image edge insets
imageEdgeInsets = insets
}
}
}
example in use:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let radioBtn = RadioButton()
// background color so we can see its frame
radioBtn.backgroundColor = .red
radioBtn.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(radioBtn)
// give it a 22-pt bold font
radioBtn.titleLabel?.font = .boldSystemFont(ofSize: 22.0)
// set the Title
radioBtn.setTitle("Testing", for: [])
// Height: 40
radioBtn.heightAnchor.constraint(equalToConstant: 40).isActive = true
// Width: 240
radioBtn.widthAnchor.constraint(equalToConstant: 240.0).isActive = true
// centered in view
radioBtn.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
radioBtn.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
Result: