Label under image in UIButton
Solution 1
Or you can just use this category:
ObjC
@interface UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding;
- (void)centerVertically;
@end
@implementation UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding {
CGSize imageSize = self.imageView.frame.size;
CGSize titleSize = self.titleLabel.frame.size;
CGFloat totalHeight = (imageSize.height + titleSize.height + padding);
self.imageEdgeInsets = UIEdgeInsetsMake(- (totalHeight - imageSize.height),
0.0f,
0.0f,
- titleSize.width);
self.titleEdgeInsets = UIEdgeInsetsMake(0.0f,
- imageSize.width,
- (totalHeight - titleSize.height),
0.0f);
self.contentEdgeInsets = UIEdgeInsetsMake(0.0f,
0.0f,
titleSize.height,
0.0f);
}
- (void)centerVertically {
const CGFloat kDefaultPadding = 6.0f;
[self centerVerticallyWithPadding:kDefaultPadding];
}
@end
Swift extension
extension UIButton {
func centerVertically(padding: CGFloat = 6.0) {
guard
let imageViewSize = self.imageView?.frame.size,
let titleLabelSize = self.titleLabel?.frame.size else {
return
}
let totalHeight = imageViewSize.height + titleLabelSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageViewSize.height),
left: 0.0,
bottom: 0.0,
right: -titleLabelSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0.0,
left: -imageViewSize.width,
bottom: -(totalHeight - titleLabelSize.height),
right: 0.0
)
self.contentEdgeInsets = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: titleLabelSize.height,
right: 0.0
)
}
}
Suggestion:
If button height is less than totalHeight
, then image will draw outside borders.
imageEdgeInset.top
should be:
max(0, -(totalHeight - imageViewSize.height))
Solution 2
In Xcode, you can simply set the Edge Title Left Inset to negative the width of the image. This will display the label in the center of the image.
To get the label to display below the image (sorta like the app buttons), you may need to set the Edge Title Top Inset to some positive number.
Edit: Here is some code to achieve that without using Interface Builder:
/// This will move the TitleLabel text of a UIButton to below it's Image and Centered.
/// Note: No point in calling this function before autolayout lays things out.
/// - Parameter padding: Some extra padding to be applied
func centerVertically(padding: CGFloat = 18.0) {
// No point in doing anything if we don't have an imageView size
guard let imageFrame = imageView?.frame else { return }
titleLabel?.numberOfLines = 0
titleEdgeInsets.left = -(imageFrame.width + padding)
titleEdgeInsets.top = (imageFrame.height + padding)
}
Please note this won't work if you're using autolayout and the button didn't get layed out in the screen yet via constraints.
Solution 3
This is a simple centered title button implemented in Swift by overriding titleRect(forContentRect:)
and imageRect(forContentRect:)
. It also implements intrinsicContentSize
for use with AutoLayout.
import UIKit
class CenteredButton: UIButton
{
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.titleRect(forContentRect: contentRect)
return CGRect(x: 0, y: contentRect.height - rect.height + 5,
width: contentRect.width, height: rect.height)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - rect.width/2.0,
y: (contentRect.height - titleRect.height)/2.0 - rect.height/2.0,
width: rect.width, height: rect.height)
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
if let image = imageView?.image {
var labelHeight: CGFloat = 0.0
if let size = titleLabel?.sizeThatFits(CGSize(width: self.contentRect(forBounds: self.bounds).width, height: CGFloat.greatestFiniteMagnitude)) {
labelHeight = size.height
}
return CGSize(width: size.width, height: image.size.height + labelHeight + 5)
}
return size
}
override init(frame: CGRect) {
super.init(frame: frame)
centerTitleLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
centerTitleLabel()
}
private func centerTitleLabel() {
self.titleLabel?.textAlignment = .center
}
}
Solution 4
Look at this great answer in Swift.
extension UIButton {
func alignImageAndTitleVertically(padding: CGFloat = 6.0) {
let imageSize = self.imageView!.frame.size
let titleSize = self.titleLabel!.frame.size
let totalHeight = imageSize.height + titleSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageSize.height),
left: 0,
bottom: 0,
right: -titleSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: -imageSize.width,
bottom: -(totalHeight - titleSize.height),
right: 0
)
}
}
Solution 5
Subclass UIButton
. Override -layoutSubviews
to move the built-in subviews
into new positions:
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect frame = self.imageView.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), 0.0f, frame.size.width, frame.size.height);
self.imageView.frame = frame;
frame = self.titleLabel.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), self.bounds.size.height - frame.size.height, frame.size.width, frame.size.height);
self.titleLabel.frame = frame;
}
Related videos on Youtube
NRaf
Updated on November 28, 2021Comments
-
NRaf over 2 years
I'm trying to create a button which has some text beneath the icon (sorta like the app buttons) however it seems to be quite difficult to achieve. Any ideas how can I go about get the text to display below the image with a
UIButton
?-
NP Compete over 13 yearsIt is fairly easy and doable to make a custom subclass of UIbutton containing a UIImage and UILabel, positioned like you would need...
-
raidfive over 13 yearsOr just use a UIButton and UILabel.
-
Albert Zhang over 9 yearsTo precisely control with the size and auto layout, you can try this:
https://github.com/albert-zhang/AZCenterLabelButton
(Link) -
Shreyank over 4 yearsworks fine with this solution stackoverflow.com/a/59666154/1576134
-
Reema over 2 yearsFrom Xcode 13 there is option in storyboard just change the button Placement option to top that work fine for me
-
-
Russ about 12 yearsI personally had to set the titleLabel y value to 0 and the height to the frame height for it to display the text with the image. It doesn't make sense to me but it works... though I'm still learning the 'Apple' way of setting up controls.
-
Kenny Winker almost 11 yearsThis is the way to do it... unless you're doing this repeatedly with a number of buttons (of various sizes)... in which case I had good results with a tweaked version of Erik W's solution
-
Liron over 10 yearsJust to make sure people realize this. The value should be the negative width of the image, even if the button is wider than the width of the image.
-
Erika Electra over 10 yearsThis did not work for me. My text still appears to the right of the image, i.e. does not wrap below it.
-
Chris over 10 years@Cindeselia Thats surprising. How big of a value did you use for the Top Inset? Maybe try increasing it to an even larger value?
-
Radu Simionescu about 10 yearsunfortunately, there are alignment differences between ios versions with this method
-
Jeremy Wiebe about 10 yearsShouldn't the line calculating the labelSize use self.bounds.size.width instead of self.frame.size.width?
-
Brave almost 10 yearsIn iOS7, it seems not work. Label only moves to bottom of image and hidden, not show anymore.
-
Jesse over 9 yearsI think this is the best answer since it uses edgeInsets instead of manually adjusting the frame. It works great with auto layout too when called from layoutSubviews in the button's superview. Only suggestion is to use
CGRectGetHeight()
andCGRectGetWidth()
when getting the imageView and titleLabel height and width. -
Shaked Sayag almost 9 yearsI used the edge insets method proposed by Chris, but since the button width was smaller than the total width of both the image and the title of the button, the title showed as 3 dots (...). I couldn't increase the width of the button due to layout reasons. I solved this by changing the Line Break attribute to: "Clip". That makes the title go down beneath the image. Then I corrected its location using the insets. This works only when the above problem exists.
-
Mazyod almost 9 yearsActually, the better way is to override
titleRectForContentRect
andimageRectForContentRect
-
Mehlyfication over 8 yearsSince pre iOS 7 is getting more and more outdated, this should be the new accepted answer.
-
kirander over 8 yearsThat is the most correct solution. But some modification needed for intrinsic content size. It should return MAX width between image and label: return CGSizeMake(MAX(labelSize.width, self.imageView.image.size.width), self.imageView.image.size.height + labelHeight)
-
ctietze about 8 yearsEnds up as an infinite loop where
layoutSubviews()
is called repeatedly in my case:intrinsicContentSize
accessesimageView
which makeslayoutSubviews
being called which accessesimageView
etc. -
Joel Teply almost 8 yearsGood answer. Add @IBDesignable to your subclass and see it in the storyboard.
-
elsurudo over 7 yearsIf you also want the image centered vertically, replace
left
inimageEdgeInsets
with(self.frame.size.width - imageSize.width) / 2
-
Oliver almost 7 yearsI think the intrinsicContentSize is not correct here. I don't understand what the part with the CGSizeEqualToSize is for but you only have a label size > 0 if the label size matches the intrinsicContentSize of UILabel. It should be sufficient to just return
CGSizeMake(MAX(labelSize.width, image.size.width), image.size.height + labelSize.height + 5.0)
in the if-case -
Chucky almost 7 yearsI had to use negative the button's frame width, rather than negative the button's image frame width.
-
Alex Hedley almost 7 yearsWhen I use this the image pops above the button view, to center it should I
CGFloat inset = (self.frame.size.height - totalHeight)/2; self.contentEdgeInsets = UIEdgeInsetsMake(inset, 0.0f, inset, 0.0f);
-
Hogdotmac over 6 yearsthis doesn't work. with the insets the placement is totally different on the device/simulator and don't match up to that in IB
-
Matic Oblak over 6 yearsThere is an issue with this when the image is removed. I am using an image for selected state and no image for default state. When the state is changed from selected to default the label is messed up. So a few fixes are needed: Do not check image view but use 'image(for: state)'. Set zero edge insets when there is no image in else statement of layoutSubviews.
-
valeCocoa over 6 yearsIt does work like a charm indeed! And with auto layout too. Thanks a lot for sharing this solution. I was going nuts with this and resorting to create my own UIControl subclass.
-
Nicolas Miari about 6 yearsThe problem I have is, the image ends up left-aligned. This is bad when the button ends up being wider than the image...
-
Patrick almost 6 yearsThanks for suggesting that Roman, though there is an issue where the contentEdgeInsets don't include the title and image entirely.
-
Patrick almost 6 yearsThe Swift extension did not layout it out correctly for me.
-
Argus over 5 yearsIt work if Image was set as setImage, not as setBackgroundImage.
-
Manuel over 5 yearsDoesn't work for the OPs question where the text should be centered below the image. A
UIButton
's text field layouts to display only 1 line, hence it doesn't work even when using a line break in the attributed string. Would be a nice solution otherwise. -
Manuel over 5 yearsOnly solution here that works. Other answers seem to work, but actually the button bounds don't resize according to label and image size. Set a background color to see this.
-
swearwolf over 5 yearsIt is also important to set
button.titleLabel?.numberOfLines
in order to get the needed number of lines -
Alexsander Akers almost 5 yearsThere are many non-English LTR languages. You are better off checking the effectiveUserInterfaceLayoutDirection on the button.
-
AlexVogel almost 5 yearsIf you are using autolayout call this method in
layoutSubviews()
of your superview. -
Spasitel over 4 yearsThe solution is not working for me. The following values are set into imageViewSize and TitleLabelSize: mageViewSize = (CGSize) (width = 0, height = 0), titleLabelSize (CGSize) (width = 0, height = 18)
-
vikzilla over 4 yearsDo you mean center "horizontally"? OP described they want the title label below the image.. which would mean they are centered horizontally, and not vertically.
-
Shreyank over 4 yearsworks fine with this solution stackoverflow.com/a/59666154/1576134
-
Oscar about 4 yearsI was able to force this to do what I needed, by commenting out the part that modified contentEdgeInsets. You do need to call this method in an override of didLayoutSubviews, or the position will be wonky (off to the right and down a bit in my case).
-
mojuba almost 4 yearsThis solution didn't work, I think it caused some sort of an infinite loop and eventually Xcode crash. I removed the intrinsicContentSize part and it worked fine (Xcode 11.5)
-
Zorayr almost 4 yearsWho is setting the
imageView
's frame? Wouldn't it be better if you usedimageView?.image.size
? -
Zorayr over 3 yearsThe top is still cut off 😢 with this solution, the size is smaller than expected.
-
zionpi over 3 yearswhy
negative the width of the image
will make the text to the middle ? -
C0D3 over 3 years@zionpi pulling the label on the x-axis in negative direction will bring the label towards left.
-
C0D3 over 3 yearsI think this should be the selected answer as other answers with a UIButton extension centerVertically() function didn't work for me. I briefly tried subclassing but that seems like a lot of work for something simple I wanted to achieve. Changing the titleEdgeInsets.left and top seems to work!
-
androidguy over 3 yearsAnd you can support your
contentEdgeInsets
by computing CGSize as follows:CGSize(width: width + contentEdgeInsets.left + contentEdgeInsets.right, height: height + contentEdgeInsets.top + contentEdgeInsets.bottom)
-
Peter Suwara over 3 yearsThink you're missing the style == .postEditorTypeOption extension from somewhere.
-
Robert Dresler over 3 years@PeterSuwara thank you, you are right. It just shouldn't be there
-
Maulik Pandya over 3 yearsWith a large title, it is creating an issue. Any solution for that?
-
horseshoe7 about 2 yearsThanks, this was great. Note, I added the inline var spacing to Constants, as this was also relevant for iOS 15 code: myConfiguration.imagePadding = Constants.spacing
-
Cory about 2 yearsIn my opinion, best answer so far