我正在尝试创建一个在图标下方有一些文本的按钮(类似于应用程序按钮),但是似乎很难实现。有什么想法可以使文本显示在图像下方并带有UIButton?
我正在尝试创建一个在图标下方有一些文本的按钮(类似于应用程序按钮),但是似乎很难实现。有什么想法可以使文本显示在图像下方并带有UIButton?
Answers:
或者,您可以只使用以下类别:
@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
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))CGRectGetHeight()和CGRectGetWidth()获得的ImageView和titleLabel高度和宽度的时候。
                    CGFloat inset = (self.frame.size.height - totalHeight)/2; self.contentEdgeInsets = UIEdgeInsetsMake(inset, 0.0f, inset, 0.0f);
                    在Xcode中,您可以简单地将Edge Title Left Inset设置为负的图像宽度。这将在图像的中心显示标签。
为了使标签显示在图像下方(类似于应用程序按钮),您可能需要将“边缘标题顶部插入”设置为某个正数。
这是一个简单的居中标题按钮,在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
    }
}
在Swift中看看这个好答案。
extension UIButton {
    func alignImageAndTitleVertically(padding: CGFloat = 6.0) {
        let imageSize = self.imageView!.frame.size
        let titleSize = self.titleLabel!.frame.size
        let totalHeight = imageSize.height + titleSize.height + padding
        self.imageEdgeInsets = UIEdgeInsets(
            top: -(totalHeight - imageSize.height),
            left: 0,
            bottom: 0,
            right: -titleSize.width
        )
        self.titleEdgeInsets = UIEdgeInsets(
            top: 0,
            left: -imageSize.width,
            bottom: -(totalHeight - titleSize.height),
            right: 0
        )
    }
}left在imageEdgeInsets用(self.frame.size.width - imageSize.width) / 2
                    layoutSubviews()您的超级视图中调用此方法。
                    子类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;
}titleRectForContentRect和imageRectForContentRect
                    重构了icecrystal23的答案。
Swift 3,可以与自动布局,XIB,情节提要一起使用,可以进行动画处理。
原始的icecrystal23答案中的Button的框架计算错误。我想我已经解决了。
编辑:更新为Swift 5,并在Interface Builder / Storyboards内部进行工作
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()
            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)
        }
        super.layoutSubviews()
    }
}在这里更正了答案之一:
斯威夫特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
    }
}这是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。
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
    }
}如果您是UIButton和的子类override 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;
}由于iOS 7中不推荐使用sizeWithFont,因此更新了Kenny Winker的答案。
-(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;
}在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)
    }
}希望这对某人有帮助。
使用肯尼·温克(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
    }
}您只需要根据图像和标题标签的大小调整所有三个边缘插图:
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获得标题标签范围。无论文本,字体和图像的大小如何,水平间距都应该起作用,但是我不知道有一种解决方案可以使垂直间距和底部内容边缘插图保持一致。
这是“与我一起负担”作为的子类的答案Swift 2.0。要使用它,只需将您的按钮类更改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等等
                    @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
        )
    }我在这里综合考虑了这些答案,并在Swift中想出了一个对我有用的答案。我不喜欢我如何覆盖插图,但它确实有效。我愿意在评论中提出改进建议。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
    }
}使用以下两种方法:
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)Swift 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
        }
    }
}我发现Simeon的答案可能是最好的,但这在某些按钮上给了我奇怪的结果,而我却无法弄清为什么。因此,以他的答案为基础,我按如下方式实现了我的按钮:
#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这是我的子类,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而已。像魅力一样工作。如果按钮太小以适合标题和文本,则可能会出现问题。
我认为做到这一点的最好方法之一是将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);
}@simeon 在Objective-C中的解决方案
#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;
}
@endCGSizeMake(MAX(labelSize.width, image.size.width), image.size.height + labelSize.height + 5.0)在if情况下返回就足够了
                    如果您使用的是自定义字体,则titleLabel大小的计算将无法正常进行,应将其替换为
let titleLabelSize = self.titleLabel?.text?.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font!]) 
您可以创建一个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)UIButton的文本字段的布局,只显示1行,所以使用属性串在断线时,它甚至不工作。否则将是一个很好的解决方案。
                    button.titleLabel?.numberOfLines以获取所需的行数也很重要
                    本地化友好解决方案:
有很多很棒的解决方案专家,但是我想在这里为使用本地化的人添加注释。
如果将语言方向从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
        )
    }
}
带有子类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
    }
  }
}对于这个问题,这绝对是一个矫kill过正的问题,但是...在我的一个项目中,我首先必须实现一个按钮,该按钮的图标最左对齐。然后我们在图像下得到了另一个带有标题的按钮。我搜索了一个现有的解决方案,但是没有运气,因此,这里是可对齐的按钮:
@IBDesignable
class AlignableButton: UIButton {
override class var requiresConstraintBasedLayout: Bool {
    return true
}
@objc enum IconAlignment: Int {
    case top, left, right, bottom
}
// MARK: - Designables
@IBInspectable var iconAlignmentValue: Int {
    set {
        iconAlignment = IconAlignment(rawValue: newValue) ?? .left
    }
    get {
        return iconAlignment.rawValue
    }
}
var iconAlignment: IconAlignment = .left
@IBInspectable var titleAlignmentValue: Int {
    set {
        titleAlignment = NSTextAlignment(rawValue: newValue) ?? .left
    }
    get {
        return titleAlignment.rawValue
    }
}
var titleAlignment: NSTextAlignment = .left
// MARK: - Corner Radius
@IBInspectable
var cornerRadius: CGFloat {
    get {
        return layer.cornerRadius
    }
    set {
        layer.masksToBounds = (newValue != 0)
        layer.cornerRadius = newValue
    }
}
// MARK: - Content size
override var intrinsicContentSize: CGSize {
    
    switch iconAlignment {
    case .top, .bottom:
        return verticalAlignedIntrinsicContentSize
    
    default:
        return super.intrinsicContentSize
    }
}
private var verticalAlignedIntrinsicContentSize: CGSize {
    
    if let imageSize = imageView?.intrinsicContentSize,
        let labelSize = titleLabel?.intrinsicContentSize {
        
        let width = max(imageSize.width, labelSize.width) + contentEdgeInsets.left + contentEdgeInsets.right
        let height = imageSize.height + labelSize.height + contentEdgeInsets.top + contentEdgeInsets.bottom
        
        return CGSize(
            width: ceil(width),
            height: ceil(height)
        )
    }
    
    return super.intrinsicContentSize
}
// MARK: - Image Rect
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
    
    switch iconAlignment {
    case .top:
        return topAlignedImageRect(forContentRect: contentRect)
    case .bottom:
        return bottomAlignedImageRect(forContentRect: contentRect)
    case .left:
        return leftAlignedImageRect(forContentRect: contentRect)
    case .right:
        return rightAlignedImageRect(forContentRect: contentRect)
    }
}
func topAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
    let rect = super.imageRect(forContentRect: contentRect)
    
    let x = (contentRect.width - rect.width) / 2.0 + contentRect.minX
    let y = contentRect.minY
    let w = rect.width
    let h = rect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: imageEdgeInsets)
}
func bottomAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
    let rect = super.imageRect(forContentRect: contentRect)
    
    let x = (contentRect.width - rect.width) / 2.0 + contentRect.minX
    let y = contentRect.height - rect.height + contentRect.minY
    let w = rect.width
    let h = rect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: imageEdgeInsets)
}
func leftAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
    let rect = super.imageRect(forContentRect: contentRect)
    
    let x = contentRect.minX
    let y = (contentRect.height - rect.height) / 2 + contentRect.minY
    let w = rect.width
    let h = rect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: imageEdgeInsets)
}
func rightAlignedImageRect(forContentRect contentRect: CGRect) -> CGRect {
    let rect = super.imageRect(forContentRect: contentRect)
    
    let x = (contentRect.width - rect.width) + contentRect.minX
    let y = (contentRect.height - rect.height) / 2 + contentRect.minY
    let w = rect.width
    let h = rect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: imageEdgeInsets)
}
// MARK: - Title Rect
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
    
    switch iconAlignment {
    case .top:
        return topAlignedTitleRect(forContentRect: contentRect)
    case .bottom:
        return bottomAlignedTitleRect(forContentRect: contentRect)
    case .left:
        return leftAlignedTitleRect(forContentRect: contentRect)
    case .right:
        return rightAlignedTitleRect(forContentRect: contentRect)
    }
}
func topAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
    
    let rect = super.titleRect(forContentRect: contentRect)
    let x = contentRect.minX
    let y = contentRect.height - rect.height + contentRect.minY
    let w = contentRect.width
    let h = rect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: titleEdgeInsets)
}
func bottomAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
    
    let rect = super.titleRect(forContentRect: contentRect)
    
    let x = contentRect.minX
    let y = contentRect.minY
    let w = contentRect.width
    let h = rect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: titleEdgeInsets)
}
func leftAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
    
    let titleRect = super.titleRect(forContentRect: contentRect)
    let imageRect = self.imageRect(forContentRect: contentRect)
    
    let x = imageRect.width + imageRect.minX
    let y = (contentRect.height - titleRect.height) / 2.0 + contentRect.minY
    let w = contentRect.width - imageRect.width * 2.0
    let h = titleRect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: titleEdgeInsets)
}
func rightAlignedTitleRect(forContentRect contentRect: CGRect) -> CGRect {
    
    let titleRect = super.titleRect(forContentRect: contentRect)
    let imageRect = self.imageRect(forContentRect: contentRect)
    let x = contentRect.minX + imageRect.width
    let y = (contentRect.height - titleRect.height) / 2.0 + contentRect.minY
    let w = contentRect.width - imageRect.width * 2.0
    let h = titleRect.height
    
    return CGRect(
        x: x,
        y: y,
        width: w,
        height: h
    ).inset(by: titleEdgeInsets)
}
// MARK: - Lifecycle
override func awakeFromNib() {
    super.awakeFromNib()
    
    titleLabel?.textAlignment = titleAlignment
}
override func prepareForInterfaceBuilder() {
    super.prepareForInterfaceBuilder()
    
    titleLabel?.textAlignment = titleAlignment
}
}希望你觉得它有用。
非常简单。
代替这个:
   button.setImage(UIImage(named: "image"), forState: .Normal)用这个:
   button.setBackgroundImage(UIImage(named: "image", forState: .Normal)然后,您可以使用以下命令轻松在按钮上添加文本:
// button.titleLabel!.font = UIFont(name:“ FontName”,大小:30)
 button.setTitle("TitleText", forState: UIControlState.Normal)