skip to Main Content

I have a UIButton with both a title and an image. The button’s size changes dynamically, but the title text and image inside the button do not resize proportionally to match the button’s size. I want to make sure that both the title and the image adjust their sizes automatically based on the button’s frame to maintain a balanced appearance.

Storyboard button configuration:

Storyboard button configuration

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var myBtn: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

Button on the simulator:

button on the simulator

I set the title and image for the UIButton, hoping they would resize automatically when the button’s frame changes. However, the title and image remained the same size, not scaling proportionally with the button. I expected both to adjust dynamically to fit the button’s new size, but this didn’t happen.

2

Answers


  1. Here’s a simple solution to ensure your UIButton’s title and image resize dynamically based on the button’s frame:

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var myBtn: UIButton!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            myBtn.imageView?.contentMode = .scaleAspectFit
    
            myBtn.titleLabel?.adjustsFontSizeToFitWidth = true
            myBtn.titleLabel?.minimumScaleFactor = 0.5
            
            myBtn.contentEdgeInsets = UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)
            myBtn.titleEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 0)
            myBtn.imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
        }
    }
    

    Explanation:

    1. Image Resizing: The contentMode property of imageView ensures the image scales proportionally to fit within the button.
    2. Text Resizing: adjustsFontSizeToFitWidth and minimumScaleFactor allow the text to resize to fit the button’s width.
    3. Edge Insets: Adjust the spacing between the button content (image and text) and the edges for better alignment.

    This approach ensures both the title and image dynamically adjust as the button size changes.

    Login or Signup to reply.
  2. UI components – including controls such as UIButton – are rarely a "one size fits all" solution.

    Buttons do not auto-adjust the font size.

    Options are to:

    • subclass UIButton and add customization code
    • set the font and image size on layout of the view
    • create a custom control of your own

    Here is a sample custom button control that you may find helpful:

    @IBDesignable
    class MyCustomButton: UIControl {
        
        @IBInspectable
        public var image: UIImage? {
            didSet {
                imageView.image = image
                setNeedsLayout()
            }
        }
    
        @IBInspectable
        public var title: String = "Button" {
            didSet {
                titleLabel.text = title
                setNeedsLayout()
            }
        }
        
        private let imageView = UIImageView()
        private let titleLabel = UILabel()
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        override func prepareForInterfaceBuilder() {
            self.backgroundColor = .systemBlue
        }
        
        private func commonInit() {
            
            backgroundColor = .systemBlue
            layer.cornerRadius = 8
            
            // Configure internal UI elements
            imageView.tintColor = .white
            imageView.contentMode = .scaleAspectFill
            
            titleLabel.textColor = .white
            titleLabel.textAlignment = .center
            
            titleLabel.text = title
            
            // let's put the imageView and titleLabel in a stack view
            let stackView = UIStackView(arrangedSubviews: [imageView, titleLabel])
            // adjust spacing as desired
            stackView.spacing = 0.0
            
            stackView.translatesAutoresizingMaskIntoConstraints = false
            addSubview(stackView)
    
            // Set up layout constraints
            NSLayoutConstraint.activate([
                stackView.centerXAnchor.constraint(equalTo: centerXAnchor),
                stackView.centerYAnchor.constraint(equalTo: centerYAnchor),
                
                // default imageView is ~75% of the button height
                //  based on your screen shots, you want it to be ~60%
                //  adjust as desired
                imageView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.60),
                // use 1:1 ratio for the image view
                imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor),
            ])
            
            // make sure all subviews are not interactive
            //  so they don't consume touches
            for v in [imageView, titleLabel, stackView] {
                v.isUserInteractionEnabled = false
            }
            
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
    
            // update font size based on button height
            
            // default filledButton uses
            //  17-pt font
            //  has height of 35-pts
            let fSize: CGFloat = 17.0 * (bounds.height / 35.0)
            titleLabel.font = .systemFont(ofSize: fSize, weight: .regular)
            
            imageView.isHidden = imageView.image == nil
        }
        
        // MARK: - Touch Handling
        override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
            // Highlight the button on touch down
            alpha = 0.7
            return true
        }
        
        override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
            // Reset appearance on touch up
            alpha = 1.0
        }
        
        override func cancelTracking(with event: UIEvent?) {
            // Reset appearance if the touch is canceled
            alpha = 1.0
        }
    }
    

    First, we add a UIImageView and a UILabel in a horizontal UIStackView.

    Next, we constrain the size of the image view to 60% of the height of the control view (based on your screen-shots).

    On layoutSubviews() we calculate the font size for the label. Again, based on you screen-shots, it looks like you want the default font size of 17 for a button height of 45. The UIButton default is 17-pt for a 35-pt height, so we set the new font size to 17.0 * (bounds.height / 35.0).

    We also define this class as @IBDesignable with @IBInspectable title and image so we can lay it out and configure it in Storyboard.

    Add a UIView and set its Custom Class:

    class

    Set the Image and Title properties:

    properties

    and we get this at run-time:

    runtimeP

    runtimeL

    This is an example to get you started. See the inline // comments for ways you can customize it as desired.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search