1

I have a UIButton. And finally, I should get this:

button with image and title

No matter how I try, I can't get the desired result. I have read many questions regarding this topic. I tried to experiment with titleEdgeInsets and imageEdgeInsets, but it did not help. The fact is that when I set the image for the button, it takes up the entire content of the button and the text remains behind. Below how i set the title

let button = UIButton()

button.frame = CGRect(x: 150, y: 300, width: 100, height: 20)
button.backgroundColor = .blue
button.layer.borderColor = UIColor.green.cgColor
button.layer.borderWidth = 2

button.setImage(UIImage(named: "baseline_play_arrow_black_48.png"), for: .normal)
button.imageView?.backgroundColor = .red
button.imageView?.contentMode = .scaleAspectFit

button.setTitle("Play", for: .normal)
button.setTitleColor(.green, for: .normal)
button.titleLabel?.backgroundColor = .yellow

As an experiment I had tried in the playground. And code above gave the following result:

image.

How can i get the desired result ?

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220

1 Answers1

0

I believe there are a few ways you can achieve this Ahmet, I will go over one idea.

The interesting challenge from your question is that you don't only want to left align the image.

You want to:

  • Left align the image with respect to the button title only
  • However, the image along with the text should be center aligned as a whole within the UIButton

I created the below extension which gets you close to your desired result with some comments to explain my thought process:

extension UIButton
{
    func configureWithLeftImage(_ imageName: String)
    {
        // Retrieve the desired image and set it as the button's image
        let buttonImage = UIImage(named: imageName)?.withRenderingMode(.alwaysOriginal)
        
        // Resize the image to the appropriate size if required
        let resizedButtonImage = resizeImage(buttonImage)
        
        setImage(resizedButtonImage, for: .normal)
        
        // Set the content mode
        contentMode = .scaleAspectFit
        
        // Align the content inside the UIButton to be left so that
        // image can be left and the text can be besides that on it's right
        contentHorizontalAlignment = .left
        
        // Set or compute the width of the UIImageView within the UIButton
        let imageWidth: CGFloat = imageView!.frame.width
        
        // Specify the padding you want between UIButton and the text
        let contentPadding: CGFloat = 10.0
        
        // Get the width required for your text in the button
        let titleFrame = titleLabel!.intrinsicContentSize.width
        
        // Keep a hold of the button width to make calculations easier
        let buttonWidth = frame.width
        
        // The UIImage and the Text combined should be centered so we need to calculate
        // the x position of the image first.
        let imageXPos = (buttonWidth - (imageWidth + contentPadding + titleFrame)) / 2
        
        // Adjust the content to be centered
        contentEdgeInsets = UIEdgeInsets(top: 0.0,
                                         left: imageXPos,
                                         bottom: 0.0,
                                         right: 0.0)
    }
    
    // Make sure the image is sized properly, I have just given 50 x 50 as random
    // Code taken from: https://www.createwithswift.com/uiimage-resize-resizing-an-uiimage/
    private func resizeImage(_ image: UIImage?,
                                   toSize size: CGSize = CGSize(width: 50, height: 50)) -> UIImage?
    {
        if let image = image
        {
            UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
            image.draw(in: CGRect(origin: CGPoint.zero, size: size))
            
            let resizedImage = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()
            return resizedImage
        }
        
        return nil
    }
}

Then when you want to use it:

        // Initialize a UIButton and set its frame
        let customButton: UIButton = UIButton(type: .custom)
        customButton.frame = CGRect(x: 100, y: 200, width: 150, height: 40)
        
        // Customize the button as you wish
        customButton.backgroundColor = .white
        customButton.layer.cornerRadius = 5.0
        customButton.setTitleColor(.black, for: .normal)
        customButton.setTitle("смотрите", for: .normal)
        
        // Call the function we just created to configure the button
        customButton.configureWithLeftImage("play_icon")
        
        // Finally add the button to your view
        view.addSubview(customButton)

This is the end result produced which I believe is close to your desired goal:

Customize UIButton with left aligned UIImage

This is the image I used as well inside the UIButton if you want to test it out

Play button icon

Update

The issue with your scenario was correctly identified by you in the comments, the size of your image was too large.

I have updated the above UIButton extension to include an image resize function which should solve the issue.

I have given random sizing for the UIImage inside the button, however you need to size it appropriate to your situation or dynamically calculate size of the image based on available space based on button size.

I also added some code for title insets to make final result better.

Now result works in PlayGround and iOS app:

App using your image

Custom UIButton with a left aligned UIImage

Playground using your image

Custom UIButton in Swift PlayGrounds

Update 2.0

As Ahmet pointed out, there were issues calculating the right positions for the image and the title when the length of the string changes using titleInsets and imageInsets, sometimes leading to overlapping of the button and image.

Instead, Adjust the content inset instead as follows (updated in the extension above as well)

// The UIImage and the Text combined should be centered so we need to calculate
// the x position of the image first.
let imageXPos = (buttonWidth - (imageWidth + contentPadding + titleFrame)) / 2

// Adjust the content to be centered
contentEdgeInsets = UIEdgeInsets(top: 0.0,
                                 left: imageXPos,
                                 bottom: 0.0,
                                 right: 0.0)

This will work if the text length of the button title is 1 or several characters

Shawn Frank
  • 4,381
  • 2
  • 19
  • 29
  • you're very correctly described my problem. but when i use your solution my titleLabel in the Button out of bounds. one difference i hasn't specify the size and position of button with hard coded values such as your example. it's landed on my custom uiview and it size's calculated automatically – Ahmet Hudayberdyyev Jan 15 '22 at 19:50
  • @AhmetHudayberdyyev Maybe update your question with how the view and button's size get's calculated and then I can try to update my answer. For now I only see this in your code `button.frame = CGRect(x: 150, y: 300, width: 100, height: 20)` - so maybe update the code with how the view is created and how the size is allocated for the view and the button. – Shawn Frank Jan 16 '22 at 02:54
  • Sorry for late response. I implement your solution in playground. But all the same, imageView filled Button frame. accordingly imageXPos become a negative value. i'm confused, why image filled of button frame ? – Ahmet Hudayberdyyev Jan 17 '22 at 18:02
  • But i understand your solution, and I would get the desired result, if the image didn't fill the button. if you can help me figure this out, your code should work correctly. – Ahmet Hudayberdyyev Jan 17 '22 at 18:09
  • @AhmetHudayberdyyev - no worries. Will try to help. It also depends on your image size. What would be helpful for me to debug if you share the image along with the latest code you tried. Maybe put the code that you tried in playground on some git repo and I will take a look. – Shawn Frank Jan 18 '22 at 03:07
  • [There](https://github.com/Hudayberdyyev/playground_swift.git) i put my code in playground which also contains the image in resources – Ahmet Hudayberdyyev Jan 19 '22 at 00:36
  • @AhmetHudayberdyyev - it seems the issue is PlayGround specific. Maybe the way frames are calculated has some difference. If you need to use this in an iOS app, I suggest to try this code in an iOS app. I added an update to my answer from findings. – Shawn Frank Jan 19 '22 at 04:48
  • [there](https://github.com/Hudayberdyyev/app_test.git) is implementation in app. Result same as playground. how can this be ? could it be because the size of the image is too big. to check I have included the image that I use. pls help me )). – Ahmet Hudayberdyyev Jan 19 '22 at 08:31
  • @AhmetHudayberdyyev - please see updated answer – Shawn Frank Jan 19 '22 at 10:00
  • 1
    thank you for detailed explanation. i'm got desired result. but your solution has one more issue, which should be fixed. after fixing that i set it's as an answer. when you specify titleEdgeInsets, for left inset we should provide imageXPos. `titleEdgeInsets = UIEdgeInsets(top: 0.0, left: imageXPos, bottom: 0.0, right: 0.0)` In your solution, if title's text length less than 5, title overlay the image surface. after analyzing your code i guessed, leftInset of both elements should be imageXPos. and it's worked perfectly. – Ahmet Hudayberdyyev Jan 19 '22 at 12:25
  • @AhmetHudayberdyyev - good catch and you are right. However, I found best result by adjusting `contentEdgeInsets` and removing adjustment of `titleEdgeInset` and `imageEdgeInset` – Shawn Frank Jan 19 '22 at 12:57
  • I get this warning when use your code in Xcode 14.2 : " 'contentEdgeInsets' was deprecated in iOS 15.0: This property is ignored when using UIButtonConfiguration " Can you try it please ? – Ghiggz Pikkoro Mar 24 '23 at 13:27