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

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

【问题讨论】:

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

标签: ios image text uibutton label


【解决方案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,则它可以工作。
    • 适用于这个解决方案stackoverflow.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
                                }
                              }
                            }
                            

                            【讨论】:

                              猜你喜欢
                              • 2012-09-28
                              • 1970-01-01
                              • 2015-08-13
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多