Search code examples
iosswiftxcodeuibuttonlayoutsubviews

UIButton align image left and center text


Introduction:

I have a class, which inherits from UIButton. In this class I want to update properties, like titleEdgeInsets, imageEdgeInsets, contentHorizontalAlignment.

My first approach was to use layoutSubviews:

override func layoutSubviews() {
    super.layoutSubviews()

    // update properties 
}

The layoutSubviews creates an infinity loop, so that I've searched for an alternative method.

My Question:

Is it a common way, to use the willMove method for updating UIButton properties?

override func willMove(toWindow newWindow: UIWindow?) {
    super.willMove(toWindow: newWindow)

    // update properties
}

If not, why?

My goal is to align the imageView of the button left (with padding) and center the text.

UPDATE:

I need the button frame.size and the bounds.width to calculate the position of the text and the image view


Solution

  • All the properties you mentioned above can be set in the init of the UIButton there is absolutely no need to set them in layoutSubviews or willMove(toWindow. layoutSubviews will be called multiple times so setting these properties again n agin in here makes no sense. willMove(toWindow will be called when button is added to some view and button is loaded but you dont have to wait till then to set these properties. Because you already have a subclass of button, so I would suggest doing

    class SomeButton: UIButton {
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            self.contentHorizontalAlignment = .center
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    }
    

    By the way creating a subclass of UIButton is not recommended, so if you wanna simply assign these properties to your button you can rather have a extension to UIButton

    extension UIButton {
        func applyStyle() {
            self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            self.contentHorizontalAlignment = .center
        }
    }
    

    EDIT:

    Is this what you want??

    enter image description here

    enter image description here

    No matter what the text is, text is always in centre and image is to its left with 10 pixel padding

    EDIT 2:

    As OP has confirmed that, he wants the button to be styled as showed in images above, posting the code to achieve the same

    class SomeButton: UIButton {
        var titleFont: UIFont! = nil
        var textSize: CGFloat = 0
        let imageWidth: CGFloat = 20
        let buttonHeight: CGFloat = 30
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            self.titleFont = titleLabel!.font
            self.setTitle("here", for: .normal)
            self.setTitleColor(UIColor.red, for: .normal)
            self.setImage(UIImage(named: "hand"), for: .normal)
            self.backgroundColor = UIColor.green
        }
    
        override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
            if let string = self.title(for: .normal) {
                textSize = string.widthOfString(usingFont: self.titleFont)
                //30 because imageWidth + 10 padding
                return CGRect(origin: CGPoint(x: 30, y: 0), size: CGSize(width: textSize + 30, height: buttonHeight))
            }
            return CGRect.zero
        }
    
        override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
            return CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: imageWidth, height: buttonHeight))
        }
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override var intrinsicContentSize: CGSize {
            //60 because you need eauql padding on both side 30 + 30 = 60
            return CGSize(width: textSize + 60, height: buttonHeight)
        }
    }
    
    extension String {
    
        func widthOfString(usingFont font: UIFont) -> CGFloat {
            let fontAttributes = [NSAttributedString.Key.font: font]
            let size = self.size(withAttributes: fontAttributes)
            return size.width
        }
    }
    

    Hope it helps