UIButton中图像下的标签

Posted

技术标签:

【中文标题】UIButton中图像下的标签【英文标题】:Label under image in UIButton 【发布时间】:2011-05-11 06:13:19 【问题描述】:

我正在尝试创建一个在图标下方有一些文本的按钮(有点像应用程序按钮),但它似乎很难实现。有什么想法可以让文本显示在 下面 带有UIButton 的图像?

【问题讨论】:

创建一个包含 UIImage 和 UILabel 的 UIbutton 的自定义子类是相当容易和可行的,就像你需要的那样定位...... 或者只使用 UIButton 和 UILabel。 要精确控制大小和自动布局,你可以试试这个:https://github.com/albert-zhang/AZCenterLabelButton (Link) 适用于这个解决方案***.com/a/59666154/1576134 Xcode 13 情节提要中有一个选项只需将按钮 Placement 选项更改为 top 对我来说很好 【参考方案1】:

子类UIButton。覆盖 -layoutSubviews 将内置 subviews 移动到新位置:

- (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;

【讨论】:

我个人必须将 titleLabel 的 y 值设置为 0 并将高度设置为框架高度,以显示带有图像的文本。这对我来说没有意义,但它有效......虽然我仍在学习设置控件的“Apple”方式。 其实更好的办法是覆盖titleRectForContentRectimageRectForContentRect【参考方案2】:

在 Xcode 中,您可以简单地将 Edge Title Left Inset 设置为图像宽度的负值。这将在图像的中心显示标签。

要让标签显示在图像下方(类似于应用程序按钮),您可能需要将 Edge Title Top Inset 设置为某个正数。

编辑:这里有一些代码可以在不使用 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)

请注意,如果您使用自动布局并且按钮尚未通过约束在屏幕中布局,则此方法将不起作用。

【讨论】:

这是这样做的方法...除非您使用多个按钮(各种大小)重复执行此操作...在这种情况下,我使用调整版本的Erik W 的解决方案 只是为了确保人们意识到这一点。该值应该是图像的负宽度,即使按钮比图像的宽度更宽。 这对我不起作用。我的文字仍然出现在图像的右侧,即不换行。 @Cindeselia 太令人惊讶了。您为顶部插图使用了多大的值?也许尝试将其增加到更大的值? ios7中,好像不行。标签仅移动到图像底部并隐藏,不再显示。【参考方案3】:

如果您将UIButtonoverride layoutSubviews 子类化,则可以使用下面的方法将图像居中并将标题置于其下方:

kTextTopPadding 是一个常量,您必须引入它来确定图像与其下方文本之间的空间。

-(void)layoutSubviews 
    [super layoutSubviews];

    // Move the image to the top and center it horizontally
    CGRect imageFrame = self.imageView.frame;
    imageFrame.origin.y = 0;
    imageFrame.origin.x = (self.frame.size.width / 2) - (imageFrame.size.width / 2);
    self.imageView.frame = imageFrame;

    // Adjust the label size to fit the text, and move it below the image
    CGRect titleLabelFrame = self.titleLabel.frame;
    CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font
                                        constrainedToSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX)
                                        lineBreakMode:NSLineBreakByWordWrapping];
    titleLabelFrame.size.width = labelSize.width;
    titleLabelFrame.size.height = labelSize.height;
    titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
    titleLabelFrame.origin.y = self.imageView.frame.origin.y + self.imageView.frame.size.height + kTextTopPadding;
    self.titleLabel.frame = titleLabelFrame;


【讨论】:

【参考方案4】:

这是 Erik W 出色答案的修改版本。但是不是将图像放在视图的顶部居中,而是将图像和标签放在视图的中心作为一个组。

区别在于:

+-----------+
|    ( )    |
|   Hello   |     // Erik W's code
|           |
|           |
+-----------+

+-----------+
|           |
|    ( )    |     // My modified version
|   Hello   |
|           |
+-----------+

来源如下:

-(void)layoutSubviews 
    [super layoutSubviews];

    CGRect titleLabelFrame = self.titleLabel.frame;
    CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font constrainedToSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];

    CGRect imageFrame = self.imageView.frame;

    CGSize fitBoxSize = (CGSize).height = labelSize.height + kTextTopPadding +  imageFrame.size.height, .width = MAX(imageFrame.size.width, labelSize.width);

    CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);

    imageFrame.origin.y = fitBoxRect.origin.y;
    imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
    self.imageView.frame = imageFrame;

    // Adjust the label size to fit the text, and move it below the image

    titleLabelFrame.size.width = labelSize.width;
    titleLabelFrame.size.height = labelSize.height;
    titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
    titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
    self.titleLabel.frame = titleLabelFrame;

仅供参考:当与 UIView 动画结合使用时,这可能会中断,因为在其中调用 layoutSubviews。

【讨论】:

计算labelSize的行不应该使用self.bounds.size.width而不是self.frame.size.width吗?【参考方案5】:

或者你可以只使用这个类别:

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 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
        )
    
    


建议: 如果按钮高度小于totalHeight,则图像将绘制到外边框。

imageEdgeInset.top 应该是:

max(0, -(totalHeight - imageViewSize.height))

【讨论】:

我认为这是最好的答案,因为它使用 edgeInsets 而不是手动调整框架。当从按钮的超级视图中的 layoutSubviews 调用时,它也适用于自动布局。唯一的建议是在获取 imageView 和 titleLabel 的高度和宽度时使用 CGRectGetHeight()CGRectGetWidth() 当我使用它时,图像会弹出按钮视图上方,我应该CGFloat inset = (self.frame.size.height - totalHeight)/2; self.contentEdgeInsets = UIEdgeInsetsMake(inset, 0.0f, inset, 0.0f); Swift 扩展没有为我正确布局。 如果 Image 设置为 setImage,而不是 setBackgroundImage,则它可以工作。 适用于这个解决方案***.com/a/59666154/1576134【参考方案6】:

这是我的UIButton 的子类,它解决了这个问题:

@implementation MyVerticalButton

@synthesize titleAtBottom; // BOOL property

- (id)initWithFrame:(CGRect)frame

  self = [super initWithFrame:frame];
  if (self) 
    self.titleAtBottom = YES;
  
  return self;


- (CGSize)sizeThatFits:(CGSize)size 
  self.titleLabel.text = [self titleForState: self.state];

  UIEdgeInsets imageInsets = self.imageEdgeInsets;
  UIEdgeInsets titleInsets = self.titleEdgeInsets;

  CGSize imageSize = [self imageForState: self.state].size;
  if (!CGSizeEqualToSize(imageSize, CGSizeZero)) 
    imageSize.width += imageInsets.left + imageInsets.right;
    imageSize.height += imageInsets.top + imageInsets.bottom;

  

  CGSize textSize = [self.titleLabel sizeThatFits: CGSizeMake(size.width - titleInsets.left - titleInsets.right,
                                                              size.height -(imageSize.width +
                                                                            titleInsets.top+titleInsets.bottom))];
  if (!CGSizeEqualToSize(textSize, CGSizeZero)) 
    textSize.width += titleInsets.left + titleInsets.right;
    textSize.height += titleInsets.top + titleInsets.bottom;
  

  CGSize result = CGSizeMake(MAX(textSize.width, imageSize.width),
                             textSize.height + imageSize.height);
  return result;


- (void)layoutSubviews 
  // needed to update all properities of child views:
  [super layoutSubviews];

  CGRect bounds = self.bounds;

  CGRect titleFrame = UIEdgeInsetsInsetRect(bounds, self.titleEdgeInsets);
  CGRect imageFrame = UIEdgeInsetsInsetRect(bounds, self.imageEdgeInsets);
  if (self.titleAtBottom) 
    CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
    titleFrame.origin.y = CGRectGetMaxY(titleFrame)-titleHeight;
    titleFrame.size.height = titleHeight;
    titleFrame = CGRectStandardize(titleFrame);
    self.titleLabel.frame = titleFrame;

    CGFloat imageBottom = CGRectGetMinY(titleFrame)-(self.titleEdgeInsets.top+self.imageEdgeInsets.bottom);
    imageFrame.size.height = imageBottom - CGRectGetMinY(imageFrame);
    self.imageView.frame = CGRectStandardize(imageFrame);
   else 
    CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
    titleFrame.size.height = titleHeight;
    titleFrame = CGRectStandardize(titleFrame);
    self.titleLabel.frame = titleFrame;

    CGFloat imageTop = CGRectGetMaxY(titleFrame)+(self.titleEdgeInsets.bottom+self.imageEdgeInsets.top);
    imageFrame.size.height = CGRectGetMaxY(imageFrame) - imageTop;
    self.imageView.frame = CGRectStandardize(imageFrame);
  


- (void)setTitleAtBottom:(BOOL)newTitleAtBottom 
  if (titleAtBottom!=newTitleAtBottom) 
    titleAtBottom=newTitleAtBottom;
    [self setNeedsLayout];
  


@end

就是这样。像魅力一样工作。如果按钮太小而无法容纳标题和文本,则可能会出现问题。

【讨论】:

它确实像一个魅力!并带有自动布局。非常感谢分享这个解决方案。我对此发疯了,并求助于创建自己的 UIControl 子类。【参考方案7】:

Dave 在 Swift 中的解决方案:

override func layoutSubviews() 
    super.layoutSubviews()
    if let imageView = self.imageView 
        imageView.frame.origin.x = (self.bounds.size.width - imageView.frame.size.width) / 2.0
        imageView.frame.origin.y = 0.0
    
    if let titleLabel = self.titleLabel 
        titleLabel.frame.origin.x = (self.bounds.size.width - titleLabel.frame.size.width) / 2.0
        titleLabel.frame.origin.y = self.bounds.size.height - titleLabel.frame.size.height
    

【讨论】:

好答案。将@IBDesignable 添加到您的子类并在情节提要中查看。【参考方案8】:

我认为最好的方法之一是继承 UIButton 并覆盖一些渲染方法:

- (void)awakeFromNib

    [super awakeFromNib];
    [self setupSubViews];


- (instancetype)init

    if (self = [super init])
    
        [self setupSubViews];
    
    return self;


- (void)setupSubViews

    [self.imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
    [self.titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView][titleLabel]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:@@"imageView": self.imageView, @"titleLabel": self.titleLabel]];


- (CGSize)intrinsicContentSize

    CGSize imageSize = self.imageView.image.size;
    CGSize titleSize = [self.titleLabel.text sizeWithAttributes:@NSFontAttributeName: self.titleLabel.font];
    return CGSizeMake(MAX(imageSize.width, titleSize.width), imageSize.height + titleSize.height);

【讨论】:

【参考方案9】:

您只需根据图像和标题标签的大小调整所有三个边缘插图:

button.contentEdgeInsets = UIEdgeInsetsMake(0, 0, titleLabelBounds.height + 4, 0)
button.titleEdgeInsets = UIEdgeInsetsMake(image.size.height + 8, -image.size.width, 0, 0)
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -titleLabelBounds.width)

您可以在设置其文本后通过调用 sizeToFit 来获取标题标签边界。无论文本、字体和图像的大小如何,水平间距都应该起作用,但我不知道有一个单一的解决方案可以使垂直间距和底部内容边缘插图保持一致。

【讨论】:

【参考方案10】:

更新了 Kenny Winker 的答案,因为 sizeWithFont 在 iOS 7 中已被弃用。

-(void)layoutSubviews 
[super layoutSubviews];

int kTextTopPadding = 3;

CGRect titleLabelFrame = self.titleLabel.frame;

CGRect labelSize = [self.titleLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGRectGetHeight(self.bounds)) options:NSStringDrawingUsesLineFragmentOrigin attributes:@NSFontAttributeName:self.titleLabel.font context:nil];

CGRect imageFrame = self.imageView.frame;

CGSize fitBoxSize = (CGSize).height = labelSize.size.height + kTextTopPadding +  imageFrame.size.height, .width = MAX(imageFrame.size.width, labelSize.size.width);

CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);

imageFrame.origin.y = fitBoxRect.origin.y;
imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;

// Adjust the label size to fit the text, and move it below the image

titleLabelFrame.size.width = labelSize.size.width;
titleLabelFrame.size.height = labelSize.size.height;
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.size.width / 2);
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame;

【讨论】:

由于pre iOS 7越来越过时,这应该是新的接受答案。【参考方案11】:

这是一个简单的居中标题按钮,在 Swift 中通过覆盖 titleRect(forContentRect:)imageRect(forContentRect:) 实现。它还实现了 intrinsicContentSize 以与 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
    

【讨论】:

这是最正确的解决方案。但是内在内容大小需要一些修改。它应该返回图像和标签之间的最大宽度: return CGSizeMake(MAX(labelSize.width, self.imageView.image.size.width), self.imageView.image.size.height + labelHeight)【参考方案12】:

使用 Kenny Winker 和 simeon 的代码,我制作了适合我的快速代码。

import UIKit

@IBDesignable
class TopIconButton: UIButton 
    override func layoutSubviews() 
        super.layoutSubviews()

        let kTextTopPadding:CGFloat = 3.0;

        var titleLabelFrame = self.titleLabel!.frame;


        let labelSize = titleLabel!.sizeThatFits(CGSizeMake(CGRectGetWidth(self.contentRectForBounds(self.bounds)), CGFloat.max))

        var imageFrame = self.imageView!.frame;

        let fitBoxSize = CGSizeMake(max(imageFrame.size.width, labelSize.width), labelSize.height + kTextTopPadding + imageFrame.size.    height)

        let fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.    height)/2);

        imageFrame.origin.y = fitBoxRect.origin.y;
        imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
        self.imageView!.frame = imageFrame;

        // Adjust the label size to fit the text, and move it below the image

        titleLabelFrame.size.width = labelSize.width;
        titleLabelFrame.size.height = labelSize.height;
        titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
        titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
        self.titleLabel!.frame = titleLabelFrame;
        self.titleLabel!.textAlignment = .Center
    


【讨论】:

【参考方案13】:

在 Swift 中查看 great answer。

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
        )
    


【讨论】:

如果您还希望图像垂直居中,请将imageEdgeInsets 中的left 替换为(self.frame.size.width - imageSize.width) / 2 如果您使用自动布局,请在您的超级视图的layoutSubviews() 中调用此方法。 谁在设置imageView 的框架?用imageView?.image.size不是更好吗?【参考方案14】:

这是 Swift 2.0 中作为子类的“Bear With Me”的答案。要使用它,只需将Interface Builder 中的按钮类更改为VerticalButton,它就会神奇地更新预览。

我还更新了它以计算自动布局的正确内在内容大小。

import UIKit

@IBDesignable

class VerticalButton: UIButton 
    @IBInspectable var padding: CGFloat = 8

    override func prepareForInterfaceBuilder() 
        super.prepareForInterfaceBuilder()

        update()
    

    override func layoutSubviews() 
        super.layoutSubviews()

        update()
    

    func update() 
        let imageBounds = self.imageView!.bounds
        let titleBounds = self.titleLabel!.bounds
        let totalHeight = CGRectGetHeight(imageBounds) + padding + CGRectGetHeight(titleBounds)

        self.imageEdgeInsets = UIEdgeInsets(
            top: -(totalHeight - CGRectGetHeight(imageBounds)),
            left: 0,
            bottom: 0,
            right: -CGRectGetWidth(titleBounds)
        )

        self.titleEdgeInsets = UIEdgeInsets(
            top: 0,
            left: -CGRectGetWidth(imageBounds),
            bottom: -(totalHeight - CGRectGetHeight(titleBounds)),
            right: 0
        )
    

    override func intrinsicContentSize() -> CGSize 
        let imageBounds = self.imageView!.bounds
        let titleBounds = self.titleLabel!.bounds

        let width = CGRectGetWidth(imageBounds) > CGRectGetWidth(titleBounds) ? CGRectGetWidth(imageBounds) : CGRectGetWidth(titleBounds)
        let height = CGRectGetHeight(imageBounds) + padding + CGRectGetHeight(titleBounds)

        return CGSizeMake(width, height)
    

【讨论】:

以无限循环结束,在我的情况下,layoutSubviews() 被重复调用:intrinsicContentSize 访问 imageView,这使得 layoutSubviews 被调用,从而访问 imageView 等。【参考方案15】:

UIButton 子类里面有这样的东西

public override func layoutSubviews() 
    super.layoutSubviews()

    imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, 0)
    titleEdgeInsets = UIEdgeInsetsMake(0, -bounds.size.width/2 - 10, -30, 0)

【讨论】:

【参考方案16】:

@Tiago 我这样改变你的答案。它适用于所有尺寸

func alignImageAndTitleVertically(padding: CGFloat = 5.0) 
        self.sizeToFit()
        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: 0,
            bottom: -(totalHeight - titleSize.height),
            right: titleSize.height
        )
    

【讨论】:

【参考方案17】:

它非常简单。

而不是这个:

   button.setImage(UIImage(named: "image"), forState: .Normal)

使用这个:

   button.setBackgroundImage(UIImage(named: "image", forState: .Normal)

然后您可以使用以下方法轻松地在按钮上添加文本:

// button.titleLabel!.font = UIFont(name: "FontName", size: 30)

 button.setTitle("TitleText", forState: UIControlState.Normal)

【讨论】:

【参考方案18】:

我综合了这里的答案,并在 Swift 中提出了一个似乎对我有用的答案。我不喜欢我如何覆盖插图,但它确实有效。我愿意接受对 cme​​ts 提出的改进建议。它似乎与sizeToFit() 和自动布局一起正常工作。

import UIKit

/// A button that displays an image centered above the title.  This implementation 
/// only works when both an image and title are set, and ignores
/// any changes you make to edge insets.
class CenteredButton: UIButton

    let padding: CGFloat = 0.0

    override func layoutSubviews() 
        if imageView?.image != nil && titleLabel?.text != nil 
            let imageSize: CGSize = imageView!.image!.size
            titleEdgeInsets = UIEdgeInsetsMake(0.0, -imageSize.width, -(imageSize.height + padding), 0.0)
            let labelString = NSString(string: titleLabel!.text!)
            let titleSize = labelString.sizeWithAttributes([NSFontAttributeName: titleLabel!.font])
            imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + padding), 0.0, 0.0, -titleSize.width)
            let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
            contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0)
        
        super.layoutSubviews()
    

    override func sizeThatFits(size: CGSize) -> CGSize 
        let defaultSize = super.sizeThatFits(size)
        if let titleSize = titleLabel?.sizeThatFits(size),
        let imageSize = imageView?.sizeThatFits(size) 
            return CGSize(width: ceil(max(imageSize.width, titleSize.width)), height: ceil(imageSize.height + titleSize.height + padding))
        
        return defaultSize
    

    override func intrinsicContentSize() -> CGSize 
        let size = sizeThatFits(CGSize(width: CGFloat.max, height: CGFloat.max))
        return size
    

【讨论】:

【参考方案19】:

在这里更正了一个答案:

斯威夫特 3:

class CenteredButton: UIButton

    override func titleRect(forContentRect contentRect: CGRect) -> CGRect 
        let rect = super.titleRect(forContentRect: contentRect)
        let imageRect = super.imageRect(forContentRect: contentRect)

        return CGRect(x: 0, y: imageRect.maxY + 10,
                      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 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
    

【讨论】:

【参考方案20】:

@simeon 在 Objective-C 中的 solution

#import "CenteredButton.h"

@implementation CenteredButton

- (CGRect)titleRectForContentRect:(CGRect)contentRect

    CGRect rect = [super titleRectForContentRect: contentRect];
    return CGRectMake(0,
                      contentRect.size.height - rect.size.height - 5,
                      contentRect.size.width,
                      rect.size.height);


- (CGRect)imageRectForContentRect:(CGRect)contentRect

    CGRect rect = [super imageRectForContentRect: contentRect];
    CGRect titleRect = [self titleRectForContentRect: contentRect];

    return CGRectMake(contentRect.size.width / 2.0 - rect.size.width / 2.0,
                      (contentRect.size.height - titleRect.size.height)/2.0 - rect.size.height/2.0,
                      rect.size.width,
                      rect.size.height);


- (CGSize)intrinsicContentSize 
    CGSize imageSize = [super intrinsicContentSize];

    if (self.imageView.image) 
        UIImage* image = self.imageView.image;
        CGFloat labelHeight = 0.0;

        CGSize labelSize = [self.titleLabel sizeThatFits: CGSizeMake([self contentRectForBounds: self.bounds].size.width, CGFLOAT_MAX)];
        if (CGSizeEqualToSize(imageSize, labelSize)) 
            labelHeight = imageSize.height;
        

        return CGSizeMake(MAX(labelSize.width, imageSize.width), image.size.height + labelHeight + 5);
    

    return imageSize;


- (id) initWithFrame:(CGRect)frame 
    self = [super initWithFrame:frame];
     if (self) 
         [self centerTitleLabel];
     
    return self;



- (id)initWithCoder:(NSCoder *)aDecoder 
    self = [super initWithCoder:aDecoder];
    if (self) 
        [self centerTitleLabel];
    
    return self;


- (void)centerTitleLabel 
    self.titleLabel.textAlignment = NSTextAlignmentCenter;


@end

【讨论】:

我认为intrinsicContentSize 在这里不正确。我不明白 CGSizeEqualToSize 的作用是什么,但如果标签大小与 UILabel 的 intrinsicContentSize 匹配,则标签大小只有 > 0。在 if-case 中只返回 CGSizeMake(MAX(labelSize.width, image.size.width), image.size.height + labelSize.height + 5.0) 就足够了【参考方案21】:

重构 icecrystal23 的答案。

Swift 3,支持自动布局、xib、故事板,可以动画化。

原始 icecrystal23 的答案中的按钮的框架计算错误。我想我解决了这个问题。

编辑:更新到 Swift 5 并在 Interface Builder / Storyboards 中工作

编辑:根据 Alexsander Akers 对以下答案的评论,更新以支持本地化 (ltr/rtl)

import UIKit

@IBDesignable
class VerticalButton: UIButton 

    @IBInspectable public var padding: CGFloat = 20.0 
        didSet 
            setNeedsLayout()
        
    
    
    override var intrinsicContentSize: CGSize 
        let maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
        
        if let titleSize = titleLabel?.sizeThatFits(maxSize), let imageSize = imageView?.sizeThatFits(maxSize) 
            let width = ceil(max(imageSize.width, titleSize.width))
            let height = ceil(imageSize.height + titleSize.height + padding)
            
            return CGSize(width: width, height: height)
        
        
        return super.intrinsicContentSize
    
    
    override func layoutSubviews() 
        if let image = imageView?.image, let title = titleLabel?.attributedText 
            let imageSize = image.size
            let titleSize = title.size()
            
            if effectiveUserInterfaceLayoutDirection == .leftToRight 
                titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: -(imageSize.height + padding), right: 0.0)
                imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + padding), left: 0.0, bottom: 0.0, right: -titleSize.width)
            
            else 
                titleEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: -(imageSize.height + padding), right: -imageSize.width)
                imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + padding), left: -titleSize.width, bottom: 0.0, right: 0.0)
            
        
        
        super.layoutSubviews()
    


【讨论】:

删除图像时会出现此问题。我将图像用于选定状态,而没有图像用于默认状态。当状态从选中状态变为默认状态时,标签就搞砸了。因此需要进行一些修复:不要检查图像视图,而是使用“图像(用于:状态)”。当layoutSubviews的else语句中没有图像时设置零边缘插入。 这里唯一可行的解​​决方案。其他答案似乎有效,但实际上按钮边界不会根据标签和图像大小调整大小。设置背景颜色以查看此内容。 这个解决方案不起作用,我认为它导致了某种无限循环并最终导致 Xcode 崩溃。我删除了 intrinsicContentSize 部分,它工作正常(Xcode 11.5) 您可以通过如下计算 CGSize 来支持您的contentEdgeInsetsCGSize(width: width + contentEdgeInsets.left + contentEdgeInsets.right, height: height + contentEdgeInsets.top + contentEdgeInsets.bottom)【参考方案22】:

iOS 11 - Objective-C

-(void)layoutSubviews 
[super layoutSubviews];

CGRect titleLabelFrame = self.titleLabel.frame;
CGSize labelSize = [self.titleLabel.text sizeWithAttributes:@NSFontAttributeName: [UIFont systemFontOfSize:12.0f]];
CGSize adjustedLabelSize = CGSizeMake(ceilf(labelSize.width), ceilf(labelSize.height));

CGRect imageFrame = self.imageView.frame;

CGSize fitBoxSize = (CGSize).height = adjustedLabelSize.height + kTextTopPadding +  imageFrame.size.height, .width = MAX(imageFrame.size.width, adjustedLabelSize.width);

CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);

imageFrame.origin.y = fitBoxRect.origin.y;
imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;

// Adjust the label size to fit the text, and move it below the image

titleLabelFrame.size.width = adjustedLabelSize.width;
titleLabelFrame.size.height = adjustedLabelSize.height;
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (adjustedLabelSize.width / 2);
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame; 

【讨论】:

【参考方案23】:

我发现Simeon's answer 可能是最好的,但它在某些按钮上给了我奇怪的结果,我就是不知道为什么。因此,以他的回答为基础,我按照以下方式实现了我的按钮:

#define PADDING 2.0f

@implementation OOButtonVerticalImageText

-(CGSize) intrinsicContentSize 
  CGSize size = [super intrinsicContentSize];
  CGFloat labelHeight = 0.0f;
  CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake([self contentRectForBounds:self.bounds].size.width, CGFLOAT_MAX)];
  labelHeight = titleSize.height;
  return CGSizeMake(MAX(titleSize.width, self.imageView.image.size.width), self.imageView.image.size.height + labelHeight + PADDING);


-(void) layoutSubviews 
  [super layoutSubviews];

  CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake([self contentRectForBounds:self.bounds].size.width, CGFLOAT_MAX)];
  self.titleLabel.frame = CGRectMake((self.bounds.size.width - titleSize.width)/2.0f,
                                     self.bounds.size.height - titleSize.height - PADDING,
                                     titleSize.width,
                                     titleSize.height);

  CGSize ivSize = self.imageView.frame.size;
  self.imageView.frame = CGRectMake((self.bounds.size.width - ivSize.width)/2.0f,
                                    self.titleLabel.frame.origin.y - ivSize.height - PADDING,
                                    ivSize.width,
                                    ivSize.height);


@end

【讨论】:

【参考方案24】:

在 iOS 11/Swift 4 上,以上答案都没有真正适合我。我找到了一些例子并对此进行了说明:

extension UIButton 

    func centerImageAndButton(_ gap: CGFloat, imageOnTop: Bool) 

      guard let imageView = self.currentImage,
      let titleLabel = self.titleLabel?.text else  return 

      let sign: CGFloat = imageOnTop ? 1 : -1
      self.titleEdgeInsets = UIEdgeInsetsMake((imageView.size.height + gap) * sign, -imageView.size.width, 0, 0);

      let titleSize = titleLabel.size(withAttributes:[NSAttributedStringKey.font: self.titleLabel!.font!])
      self.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + gap) * sign, 0, 0, -titleSize.width)
    

希望这对某人有所帮助。

【讨论】:

感谢 Roman,尽管存在 contentEdgeInsets 不完全包含标题和图像的问题。【参考方案25】:

如果您使用自定义字体,titleLabel 大小的计算将无法正常工作,您应该将其替换为

let titleLabelSize = self.titleLabel?.text?.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font!])

【讨论】:

【参考方案26】:

使用这两种方法:

func titleRect(forContentRect contentRect: CGRect) -> CGRect
func imageRect(forContentRect contentRect: CGRect) -> CGRect

例子:

class VerticalButton: UIButton 

  override func titleRect(forContentRect contentRect: CGRect) -> CGRect 
    let titleRect = super.titleRect(forContentRect: contentRect)
    let imageRect = super.imageRect(forContentRect: contentRect)

    return CGRect(x: 0,
                  y: contentRect.height - (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2 - titleRect.size.height,
                  width: contentRect.width,
                  height: titleRect.height)
  

  override func imageRect(forContentRect contentRect: CGRect) -> CGRect 
    let imageRect = super.imageRect(forContentRect: contentRect)
    let titleRect = self.titleRect(forContentRect: contentRect)

    return CGRect(x: contentRect.width/2.0 - imageRect.width/2.0,
                  y: (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2,
                  width: imageRect.width,
                  height: imageRect.height)
  

  private let padding: CGFloat
  init(padding: CGFloat) 
    self.padding = padding

    super.init(frame: .zero)
    self.titleLabel?.textAlignment = .center
  

  required init?(coder aDecoder: NSCoder)  fatalError() 


extension UIButton 

  static func vertical(padding: CGFloat) -> UIButton 
    return VerticalButton(padding: padding)
  

你可以使用:

let myButton = UIButton.vertical(padding: 6)

【讨论】:

【参考方案27】:

您可以创建一个带有图像作为附件的 NSAttributedString 并将其设置为按钮的属性标题,而不是通过地狱尝试定位图标和带有边缘插入的文本:

let titleText = NSAttributedString(string: yourTitle, attributes: attributes)
let imageAttachment = NSTextAttachment()
imageAttachment.image = yourImage

let title = NSMutableAttributedString(string: "")
title.append(NSAttributedString(attachment: imageAttachment))
title.append(titleText)

button.setAttributedTitle(title, for: .normal)

【讨论】:

不适用于 OPs 问题,其中文本应居中 在图像下方UIButton 的文本字段布局仅显示 1 行,因此即使在属性字符串中使用换行符也不起作用。否则将是一个不错的解决方案。 设置button.titleLabel?.numberOfLines 以获得所需的行数也很重要【参考方案28】:

本地化友好解决方案:

这么多优秀的解决方案,但我想在这里为那些使用本地化的人添加一个注释。

如果语言方向从 LtR 更改为 RtL,您需要反转左右 EdgeInstets 值以正确布局按钮。

使用类似的解决方案,我将按如下方式实现:

extension UIButton 

    func alignVertical(spacing: CGFloat, lang: String) 
        guard let imageSize = self.imageView?.image?.size,
            let text = self.titleLabel?.text,
            let font = self.titleLabel?.font
        else  return 

        let labelString = NSString(string: text)
        let titleSize = labelString.size(
            withAttributes: [NSAttributedString.Key.font: font]
        )

        var titleLeftInset: CGFloat = -imageSize.width
        var titleRigtInset: CGFloat = 0.0

        var imageLeftInset: CGFloat = 0.0
        var imageRightInset: CGFloat = -titleSize.width

        if Locale.current.languageCode! != "en"  // If not Left to Right language
            titleLeftInset = 0.0
            titleRigtInset = -imageSize.width

            imageLeftInset = -titleSize.width
            imageRightInset = 0.0
        

        self.titleEdgeInsets = UIEdgeInsets(
            top: 0.0,
            left: titleLeftInset,
            bottom: -(imageSize.height + spacing),
            right: titleRigtInset
        )
        self.imageEdgeInsets = UIEdgeInsets(
            top: -(titleSize.height + spacing),
            left: imageLeftInset,
            bottom: 0.0,
            right: imageRightInset
        )
        let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
        self.contentEdgeInsets = UIEdgeInsets(
            top: edgeOffset,
            left: 0.0,
            bottom: edgeOffset,
            right: 0.0
        )
    


【讨论】:

有很多非英语的 LTR 语言。您最好检查按钮上的 EffectiveUserInterfaceLayoutDirection。 标题很大,会造成问题。有什么解决办法吗?【参考方案29】:

斯威夫特 5 - 下面的方法对我有用

func centerVerticallyWithPadding(padding : CGFloat) 
        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: max(0, -(totalHeight - imageViewSize.height)),
            left: 0.0,
            bottom: 0.0,
            right: -titleLabelSize.width
        )

        self.titleEdgeInsets = UIEdgeInsets(
            top: (totalHeight - imageViewSize.height),
            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
        )
    

确保您的按钮标题不会在故事板/xib 中被截断,否则请使用解决方案 2

class SVVerticalButton: UIButton 

    override func layoutSubviews() 
        super.layoutSubviews()
        let padding : CGFloat = 2.0
        if let imageView = self.imageView 
            imageView.frame.origin.x = (self.bounds.size.width - imageView.frame.size.width) / 2.0
            imageView.frame.origin.y = max(0,(self.bounds.size.height - (imageView.frame.size.height + (titleLabel?.frame.size.height ?? 0.0) + padding)) / 2.0)
        
        if let titleLabel = self.titleLabel 
            titleLabel.frame.origin.x = 0
            titleLabel.frame.origin.y = self.bounds.size.height - titleLabel.frame.size.height
            titleLabel.frame.size.width = self.bounds.size.width
            titleLabel.textAlignment = .center
        
    


【讨论】:

顶部还是被剪掉了?这个方案,尺寸比想象的要小。【参考方案30】:

带有子类 UIButton 的顶部图像和底部标题按钮

class VerticalButton: UIButton 
  override func layoutSubviews() 
    super.layoutSubviews()
    let padding: CGFloat = 8
    let iH = imageView?.frame.height ?? 0
    let tH = titleLabel?.frame.height ?? 0
    let v: CGFloat = (frame.height - iH - tH - padding) / 2
    if let iv = imageView 
      let x = (frame.width - iv.frame.width) / 2
      iv.frame.origin.y = v
      iv.frame.origin.x = x
    

    if let tl = titleLabel 
      let x = (frame.width - tl.frame.width) / 2
      tl.frame.origin.y = frame.height - tl.frame.height - v
      tl.frame.origin.x = x
    
  

【讨论】:

以上是关于UIButton中图像下的标签的主要内容,如果未能解决你的问题,请参考以下文章

WPF:按钮模板中图像下的文本

如何在 OBJECTIVE C 的 UITableViewCell 中更改先前按下的 UIButton 标签?

在 TableViewCell 内单击时 UIButton 标签重置

如何将自定义 UIControl 中按下的 UIButton 传递给我的视图控制器?

从 UIButton 中获取 UIButton 的标签,该 UIButton 位于此 UIButton 呈现的弹出框中

带有左+右大写和中间图案的 UIButton