UIButton中图像下的标签
Posted
技术标签:
【中文标题】UIButton中图像下的标签【英文标题】:Label under image in UIButton 【发布时间】:2011-05-11 06:13:19 【问题描述】:我正在尝试创建一个在图标下方有一些文本的按钮(有点像应用程序按钮),但它似乎很难实现。有什么想法可以让文本显示在 下面 带有UIButton
的图像?
【问题讨论】:
创建一个包含 UIImage 和 UILabel 的 UIbutton 的自定义子类是相当容易和可行的,就像你需要的那样定位...... 或者只使用 UIButton 和 UILabel。 要精确控制大小和自动布局,你可以试试这个:https://github.com/albert-zhang/AZCenterLabelButton
(Link)
适用于这个解决方案***.com/a/59666154/1576134
从 Xcode 13 情节提要中有一个选项只需将按钮 Placement 选项更改为 top 对我来说很好
【参考方案1】:
子类UIButton
。覆盖 -layoutSubviews
将内置 subviews
移动到新位置:
- (void)layoutSubviews
[super layoutSubviews];
CGRect frame = self.imageView.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), 0.0f, frame.size.width, frame.size.height);
self.imageView.frame = frame;
frame = self.titleLabel.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), self.bounds.size.height - frame.size.height, frame.size.width, frame.size.height);
self.titleLabel.frame = frame;
【讨论】:
我个人必须将 titleLabel 的 y 值设置为 0 并将高度设置为框架高度,以显示带有图像的文本。这对我来说没有意义,但它有效......虽然我仍在学习设置控件的“Apple”方式。 其实更好的办法是覆盖titleRectForContentRect
和imageRectForContentRect
【参考方案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】:如果您将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;
【讨论】:
【参考方案4】:这是 Erik W 出色答案的修改版本。但是不是将图像放在视图的顶部居中,而是将图像和标签放在视图的中心作为一个组。
区别在于:
+-----------+
| ( ) |
| Hello | // Erik W's code
| |
| |
+-----------+
对
+-----------+
| |
| ( ) | // My modified version
| Hello |
| |
+-----------+
来源如下:
-(void)layoutSubviews
[super layoutSubviews];
CGRect titleLabelFrame = self.titleLabel.frame;
CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font constrainedToSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
CGRect imageFrame = self.imageView.frame;
CGSize fitBoxSize = (CGSize).height = labelSize.height + kTextTopPadding + imageFrame.size.height, .width = MAX(imageFrame.size.width, labelSize.width);
CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
imageFrame.origin.y = fitBoxRect.origin.y;
imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.width;
titleLabelFrame.size.height = labelSize.height;
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame;
仅供参考:当与 UIView 动画结合使用时,这可能会中断,因为在其中调用 layoutSubviews。
【讨论】:
计算labelSize的行不应该使用self.bounds.size.width而不是self.frame.size.width吗?【参考方案5】:或者你可以只使用这个类别:
ObjC
@interface UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding;
- (void)centerVertically;
@end
@implementation UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding
CGSize imageSize = self.imageView.frame.size;
CGSize titleSize = self.titleLabel.frame.size;
CGFloat totalHeight = (imageSize.height + titleSize.height + padding);
self.imageEdgeInsets = UIEdgeInsetsMake(- (totalHeight - imageSize.height),
0.0f,
0.0f,
- titleSize.width);
self.titleEdgeInsets = UIEdgeInsetsMake(0.0f,
- imageSize.width,
- (totalHeight - titleSize.height),
0.0f);
self.contentEdgeInsets = UIEdgeInsetsMake(0.0f,
0.0f,
titleSize.height,
0.0f);
- (void)centerVertically
const CGFloat kDefaultPadding = 6.0f;
[self centerVerticallyWithPadding:kDefaultPadding];
@end
Swift 扩展
extension UIButton
func centerVertically(padding: CGFloat = 6.0)
guard
let imageViewSize = self.imageView?.frame.size,
let titleLabelSize = self.titleLabel?.frame.size else
return
let totalHeight = imageViewSize.height + titleLabelSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageViewSize.height),
left: 0.0,
bottom: 0.0,
right: -titleLabelSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0.0,
left: -imageViewSize.width,
bottom: -(totalHeight - titleLabelSize.height),
right: 0.0
)
self.contentEdgeInsets = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: titleLabelSize.height,
right: 0.0
)
建议:
如果按钮高度小于totalHeight
,则图像将绘制到外边框。
imageEdgeInset.top
应该是:
max(0, -(totalHeight - imageViewSize.height))
【讨论】:
我认为这是最好的答案,因为它使用 edgeInsets 而不是手动调整框架。当从按钮的超级视图中的 layoutSubviews 调用时,它也适用于自动布局。唯一的建议是在获取 imageView 和 titleLabel 的高度和宽度时使用CGRectGetHeight()
和 CGRectGetWidth()
。
当我使用它时,图像会弹出按钮视图上方,我应该CGFloat inset = (self.frame.size.height - totalHeight)/2; self.contentEdgeInsets = UIEdgeInsetsMake(inset, 0.0f, inset, 0.0f);
Swift 扩展没有为我正确布局。
如果 Image 设置为 setImage,而不是 setBackgroundImage,则它可以工作。
适用于这个解决方案***.com/a/59666154/1576134【参考方案6】:
这是我的UIButton
的子类,它解决了这个问题:
@implementation MyVerticalButton
@synthesize titleAtBottom; // BOOL property
- (id)initWithFrame:(CGRect)frame
self = [super initWithFrame:frame];
if (self)
self.titleAtBottom = YES;
return self;
- (CGSize)sizeThatFits:(CGSize)size
self.titleLabel.text = [self titleForState: self.state];
UIEdgeInsets imageInsets = self.imageEdgeInsets;
UIEdgeInsets titleInsets = self.titleEdgeInsets;
CGSize imageSize = [self imageForState: self.state].size;
if (!CGSizeEqualToSize(imageSize, CGSizeZero))
imageSize.width += imageInsets.left + imageInsets.right;
imageSize.height += imageInsets.top + imageInsets.bottom;
CGSize textSize = [self.titleLabel sizeThatFits: CGSizeMake(size.width - titleInsets.left - titleInsets.right,
size.height -(imageSize.width +
titleInsets.top+titleInsets.bottom))];
if (!CGSizeEqualToSize(textSize, CGSizeZero))
textSize.width += titleInsets.left + titleInsets.right;
textSize.height += titleInsets.top + titleInsets.bottom;
CGSize result = CGSizeMake(MAX(textSize.width, imageSize.width),
textSize.height + imageSize.height);
return result;
- (void)layoutSubviews
// needed to update all properities of child views:
[super layoutSubviews];
CGRect bounds = self.bounds;
CGRect titleFrame = UIEdgeInsetsInsetRect(bounds, self.titleEdgeInsets);
CGRect imageFrame = UIEdgeInsetsInsetRect(bounds, self.imageEdgeInsets);
if (self.titleAtBottom)
CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
titleFrame.origin.y = CGRectGetMaxY(titleFrame)-titleHeight;
titleFrame.size.height = titleHeight;
titleFrame = CGRectStandardize(titleFrame);
self.titleLabel.frame = titleFrame;
CGFloat imageBottom = CGRectGetMinY(titleFrame)-(self.titleEdgeInsets.top+self.imageEdgeInsets.bottom);
imageFrame.size.height = imageBottom - CGRectGetMinY(imageFrame);
self.imageView.frame = CGRectStandardize(imageFrame);
else
CGFloat titleHeight = [self.titleLabel sizeThatFits: titleFrame.size].height;
titleFrame.size.height = titleHeight;
titleFrame = CGRectStandardize(titleFrame);
self.titleLabel.frame = titleFrame;
CGFloat imageTop = CGRectGetMaxY(titleFrame)+(self.titleEdgeInsets.bottom+self.imageEdgeInsets.top);
imageFrame.size.height = CGRectGetMaxY(imageFrame) - imageTop;
self.imageView.frame = CGRectStandardize(imageFrame);
- (void)setTitleAtBottom:(BOOL)newTitleAtBottom
if (titleAtBottom!=newTitleAtBottom)
titleAtBottom=newTitleAtBottom;
[self setNeedsLayout];
@end
就是这样。像魅力一样工作。如果按钮太小而无法容纳标题和文本,则可能会出现问题。
【讨论】:
它确实像一个魅力!并带有自动布局。非常感谢分享这个解决方案。我对此发疯了,并求助于创建自己的 UIControl 子类。【参考方案7】:Dave 在 Swift 中的解决方案:
override func layoutSubviews()
super.layoutSubviews()
if let imageView = self.imageView
imageView.frame.origin.x = (self.bounds.size.width - imageView.frame.size.width) / 2.0
imageView.frame.origin.y = 0.0
if let titleLabel = self.titleLabel
titleLabel.frame.origin.x = (self.bounds.size.width - titleLabel.frame.size.width) / 2.0
titleLabel.frame.origin.y = self.bounds.size.height - titleLabel.frame.size.height
【讨论】:
好答案。将@IBDesignable 添加到您的子类并在情节提要中查看。【参考方案8】:我认为最好的方法之一是继承 UIButton 并覆盖一些渲染方法:
- (void)awakeFromNib
[super awakeFromNib];
[self setupSubViews];
- (instancetype)init
if (self = [super init])
[self setupSubViews];
return self;
- (void)setupSubViews
[self.imageView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.imageView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.titleLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView][titleLabel]|" options:NSLayoutFormatAlignAllCenterX metrics:nil views:@@"imageView": self.imageView, @"titleLabel": self.titleLabel]];
- (CGSize)intrinsicContentSize
CGSize imageSize = self.imageView.image.size;
CGSize titleSize = [self.titleLabel.text sizeWithAttributes:@NSFontAttributeName: self.titleLabel.font];
return CGSizeMake(MAX(imageSize.width, titleSize.width), imageSize.height + titleSize.height);
【讨论】:
【参考方案9】:您只需根据图像和标题标签的大小调整所有三个边缘插图:
button.contentEdgeInsets = UIEdgeInsetsMake(0, 0, titleLabelBounds.height + 4, 0)
button.titleEdgeInsets = UIEdgeInsetsMake(image.size.height + 8, -image.size.width, 0, 0)
button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -titleLabelBounds.width)
您可以在设置其文本后通过调用 sizeToFit 来获取标题标签边界。无论文本、字体和图像的大小如何,水平间距都应该起作用,但我不知道有一个单一的解决方案可以使垂直间距和底部内容边缘插图保持一致。
【讨论】:
【参考方案10】:更新了 Kenny Winker 的答案,因为 sizeWithFont 在 iOS 7 中已被弃用。
-(void)layoutSubviews
[super layoutSubviews];
int kTextTopPadding = 3;
CGRect titleLabelFrame = self.titleLabel.frame;
CGRect labelSize = [self.titleLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGRectGetHeight(self.bounds)) options:NSStringDrawingUsesLineFragmentOrigin attributes:@NSFontAttributeName:self.titleLabel.font context:nil];
CGRect imageFrame = self.imageView.frame;
CGSize fitBoxSize = (CGSize).height = labelSize.size.height + kTextTopPadding + imageFrame.size.height, .width = MAX(imageFrame.size.width, labelSize.size.width);
CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
imageFrame.origin.y = fitBoxRect.origin.y;
imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.size.width;
titleLabelFrame.size.height = labelSize.size.height;
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.size.width / 2);
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame;
【讨论】:
由于pre iOS 7越来越过时,这应该是新的接受答案。【参考方案11】:这是一个简单的居中标题按钮,在 Swift 中通过覆盖 titleRect(forContentRect:)
和 imageRect(forContentRect:)
实现。它还实现了 intrinsicContentSize
以与 AutoLayout 一起使用。
import UIKit
class CenteredButton: UIButton
override func titleRect(forContentRect contentRect: CGRect) -> CGRect
let rect = super.titleRect(forContentRect: contentRect)
return CGRect(x: 0, y: contentRect.height - rect.height + 5,
width: contentRect.width, height: rect.height)
override func imageRect(forContentRect contentRect: CGRect) -> CGRect
let rect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - rect.width/2.0,
y: (contentRect.height - titleRect.height)/2.0 - rect.height/2.0,
width: rect.width, height: rect.height)
override var intrinsicContentSize: CGSize
let size = super.intrinsicContentSize
if let image = imageView?.image
var labelHeight: CGFloat = 0.0
if let size = titleLabel?.sizeThatFits(CGSize(width: self.contentRect(forBounds: self.bounds).width, height: CGFloat.greatestFiniteMagnitude))
labelHeight = size.height
return CGSize(width: size.width, height: image.size.height + labelHeight + 5)
return size
override init(frame: CGRect)
super.init(frame: frame)
centerTitleLabel()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
centerTitleLabel()
private func centerTitleLabel()
self.titleLabel?.textAlignment = .center
【讨论】:
这是最正确的解决方案。但是内在内容大小需要一些修改。它应该返回图像和标签之间的最大宽度: return CGSizeMake(MAX(labelSize.width, self.imageView.image.size.width), self.imageView.image.size.height + labelHeight)【参考方案12】:使用 Kenny Winker 和 simeon 的代码,我制作了适合我的快速代码。
import UIKit
@IBDesignable
class TopIconButton: UIButton
override func layoutSubviews()
super.layoutSubviews()
let kTextTopPadding:CGFloat = 3.0;
var titleLabelFrame = self.titleLabel!.frame;
let labelSize = titleLabel!.sizeThatFits(CGSizeMake(CGRectGetWidth(self.contentRectForBounds(self.bounds)), CGFloat.max))
var imageFrame = self.imageView!.frame;
let fitBoxSize = CGSizeMake(max(imageFrame.size.width, labelSize.width), labelSize.height + kTextTopPadding + imageFrame.size. height)
let fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize. height)/2);
imageFrame.origin.y = fitBoxRect.origin.y;
imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView!.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = labelSize.width;
titleLabelFrame.size.height = labelSize.height;
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (labelSize.width / 2);
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel!.frame = titleLabelFrame;
self.titleLabel!.textAlignment = .Center
【讨论】:
【参考方案13】:在 Swift 中查看 great answer。
extension UIButton
func alignImageAndTitleVertically(padding: CGFloat = 6.0)
let imageSize = self.imageView!.frame.size
let titleSize = self.titleLabel!.frame.size
let totalHeight = imageSize.height + titleSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageSize.height),
left: 0,
bottom: 0,
right: -titleSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: -imageSize.width,
bottom: -(totalHeight - titleSize.height),
right: 0
)
【讨论】:
如果您还希望图像垂直居中,请将imageEdgeInsets
中的left
替换为(self.frame.size.width - imageSize.width) / 2
如果您使用自动布局,请在您的超级视图的layoutSubviews()
中调用此方法。
谁在设置imageView
的框架?用imageView?.image.size
不是更好吗?【参考方案14】:
这是 Swift 2.0
中作为子类的“Bear With Me”的答案。要使用它,只需将Interface Builder
中的按钮类更改为VerticalButton
,它就会神奇地更新预览。
我还更新了它以计算自动布局的正确内在内容大小。
import UIKit
@IBDesignable
class VerticalButton: UIButton
@IBInspectable var padding: CGFloat = 8
override func prepareForInterfaceBuilder()
super.prepareForInterfaceBuilder()
update()
override func layoutSubviews()
super.layoutSubviews()
update()
func update()
let imageBounds = self.imageView!.bounds
let titleBounds = self.titleLabel!.bounds
let totalHeight = CGRectGetHeight(imageBounds) + padding + CGRectGetHeight(titleBounds)
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - CGRectGetHeight(imageBounds)),
left: 0,
bottom: 0,
right: -CGRectGetWidth(titleBounds)
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: -CGRectGetWidth(imageBounds),
bottom: -(totalHeight - CGRectGetHeight(titleBounds)),
right: 0
)
override func intrinsicContentSize() -> CGSize
let imageBounds = self.imageView!.bounds
let titleBounds = self.titleLabel!.bounds
let width = CGRectGetWidth(imageBounds) > CGRectGetWidth(titleBounds) ? CGRectGetWidth(imageBounds) : CGRectGetWidth(titleBounds)
let height = CGRectGetHeight(imageBounds) + padding + CGRectGetHeight(titleBounds)
return CGSizeMake(width, height)
【讨论】:
以无限循环结束,在我的情况下,layoutSubviews()
被重复调用:intrinsicContentSize
访问 imageView
,这使得 layoutSubviews
被调用,从而访问 imageView
等。【参考方案15】:
UIButton
子类里面有这样的东西
public override func layoutSubviews()
super.layoutSubviews()
imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, 0)
titleEdgeInsets = UIEdgeInsetsMake(0, -bounds.size.width/2 - 10, -30, 0)
【讨论】:
【参考方案16】:@Tiago 我这样改变你的答案。它适用于所有尺寸
func alignImageAndTitleVertically(padding: CGFloat = 5.0)
self.sizeToFit()
let imageSize = self.imageView!.frame.size
let titleSize = self.titleLabel!.frame.size
let totalHeight = imageSize.height + titleSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageSize.height),
left: 0,
bottom: 0,
right: -titleSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: 0,
bottom: -(totalHeight - titleSize.height),
right: titleSize.height
)
【讨论】:
【参考方案17】:它非常简单。
而不是这个:
button.setImage(UIImage(named: "image"), forState: .Normal)
使用这个:
button.setBackgroundImage(UIImage(named: "image", forState: .Normal)
然后您可以使用以下方法轻松地在按钮上添加文本:
// button.titleLabel!.font = UIFont(name: "FontName", size: 30)
button.setTitle("TitleText", forState: UIControlState.Normal)
【讨论】:
【参考方案18】:我综合了这里的答案,并在 Swift 中提出了一个似乎对我有用的答案。我不喜欢我如何覆盖插图,但它确实有效。我愿意接受对 cmets 提出的改进建议。它似乎与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 来支持您的contentEdgeInsets
:CGSize(width: width + contentEdgeInsets.left + contentEdgeInsets.right, height: height + contentEdgeInsets.top + contentEdgeInsets.bottom)
【参考方案22】:
iOS 11 - Objective-C
-(void)layoutSubviews
[super layoutSubviews];
CGRect titleLabelFrame = self.titleLabel.frame;
CGSize labelSize = [self.titleLabel.text sizeWithAttributes:@NSFontAttributeName: [UIFont systemFontOfSize:12.0f]];
CGSize adjustedLabelSize = CGSizeMake(ceilf(labelSize.width), ceilf(labelSize.height));
CGRect imageFrame = self.imageView.frame;
CGSize fitBoxSize = (CGSize).height = adjustedLabelSize.height + kTextTopPadding + imageFrame.size.height, .width = MAX(imageFrame.size.width, adjustedLabelSize.width);
CGRect fitBoxRect = CGRectInset(self.bounds, (self.bounds.size.width - fitBoxSize.width)/2, (self.bounds.size.height - fitBoxSize.height)/2);
imageFrame.origin.y = fitBoxRect.origin.y;
imageFrame.origin.x = CGRectGetMidX(fitBoxRect) - (imageFrame.size.width/2);
self.imageView.frame = imageFrame;
// Adjust the label size to fit the text, and move it below the image
titleLabelFrame.size.width = adjustedLabelSize.width;
titleLabelFrame.size.height = adjustedLabelSize.height;
titleLabelFrame.origin.x = (self.frame.size.width / 2) - (adjustedLabelSize.width / 2);
titleLabelFrame.origin.y = fitBoxRect.origin.y + imageFrame.size.height + kTextTopPadding;
self.titleLabel.frame = titleLabelFrame;
【讨论】:
【参考方案23】:我发现Simeon's answer 可能是最好的,但它在某些按钮上给了我奇怪的结果,我就是不知道为什么。因此,以他的回答为基础,我按照以下方式实现了我的按钮:
#define PADDING 2.0f
@implementation OOButtonVerticalImageText
-(CGSize) intrinsicContentSize
CGSize size = [super intrinsicContentSize];
CGFloat labelHeight = 0.0f;
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake([self contentRectForBounds:self.bounds].size.width, CGFLOAT_MAX)];
labelHeight = titleSize.height;
return CGSizeMake(MAX(titleSize.width, self.imageView.image.size.width), self.imageView.image.size.height + labelHeight + PADDING);
-(void) layoutSubviews
[super layoutSubviews];
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake([self contentRectForBounds:self.bounds].size.width, CGFLOAT_MAX)];
self.titleLabel.frame = CGRectMake((self.bounds.size.width - titleSize.width)/2.0f,
self.bounds.size.height - titleSize.height - PADDING,
titleSize.width,
titleSize.height);
CGSize ivSize = self.imageView.frame.size;
self.imageView.frame = CGRectMake((self.bounds.size.width - ivSize.width)/2.0f,
self.titleLabel.frame.origin.y - ivSize.height - PADDING,
ivSize.width,
ivSize.height);
@end
【讨论】:
【参考方案24】:在 iOS 11/Swift 4 上,以上答案都没有真正适合我。我找到了一些例子并对此进行了说明:
extension UIButton
func centerImageAndButton(_ gap: CGFloat, imageOnTop: Bool)
guard let imageView = self.currentImage,
let titleLabel = self.titleLabel?.text else return
let sign: CGFloat = imageOnTop ? 1 : -1
self.titleEdgeInsets = UIEdgeInsetsMake((imageView.size.height + gap) * sign, -imageView.size.width, 0, 0);
let titleSize = titleLabel.size(withAttributes:[NSAttributedStringKey.font: self.titleLabel!.font!])
self.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + gap) * sign, 0, 0, -titleSize.width)
希望这对某人有所帮助。
【讨论】:
感谢 Roman,尽管存在 contentEdgeInsets 不完全包含标题和图像的问题。【参考方案25】:如果您使用自定义字体,titleLabel 大小的计算将无法正常工作,您应该将其替换为
let titleLabelSize = self.titleLabel?.text?.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font!])
【讨论】:
【参考方案26】:使用这两种方法:
func titleRect(forContentRect contentRect: CGRect) -> CGRect
func imageRect(forContentRect contentRect: CGRect) -> CGRect
例子:
class VerticalButton: UIButton
override func titleRect(forContentRect contentRect: CGRect) -> CGRect
let titleRect = super.titleRect(forContentRect: contentRect)
let imageRect = super.imageRect(forContentRect: contentRect)
return CGRect(x: 0,
y: contentRect.height - (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2 - titleRect.size.height,
width: contentRect.width,
height: titleRect.height)
override func imageRect(forContentRect contentRect: CGRect) -> CGRect
let imageRect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - imageRect.width/2.0,
y: (contentRect.height - padding - imageRect.size.height - titleRect.size.height) / 2,
width: imageRect.width,
height: imageRect.height)
private let padding: CGFloat
init(padding: CGFloat)
self.padding = padding
super.init(frame: .zero)
self.titleLabel?.textAlignment = .center
required init?(coder aDecoder: NSCoder) fatalError()
extension UIButton
static func vertical(padding: CGFloat) -> UIButton
return VerticalButton(padding: padding)
你可以使用:
let myButton = UIButton.vertical(padding: 6)
【讨论】:
【参考方案27】:您可以创建一个带有图像作为附件的 NSAttributedString 并将其设置为按钮的属性标题,而不是通过地狱尝试定位图标和带有边缘插入的文本:
let titleText = NSAttributedString(string: yourTitle, attributes: attributes)
let imageAttachment = NSTextAttachment()
imageAttachment.image = yourImage
let title = NSMutableAttributedString(string: "")
title.append(NSAttributedString(attachment: imageAttachment))
title.append(titleText)
button.setAttributedTitle(title, for: .normal)
【讨论】:
不适用于 OPs 问题,其中文本应居中 在图像下方。UIButton
的文本字段布局仅显示 1 行,因此即使在属性字符串中使用换行符也不起作用。否则将是一个不错的解决方案。
设置button.titleLabel?.numberOfLines
以获得所需的行数也很重要【参考方案28】:
本地化友好解决方案:
这么多优秀的解决方案,但我想在这里为那些使用本地化的人添加一个注释。
如果语言方向从 LtR 更改为 RtL,您需要反转左右 EdgeInstets 值以正确布局按钮。
使用类似的解决方案,我将按如下方式实现:
extension UIButton
func alignVertical(spacing: CGFloat, lang: String)
guard let imageSize = self.imageView?.image?.size,
let text = self.titleLabel?.text,
let font = self.titleLabel?.font
else return
let labelString = NSString(string: text)
let titleSize = labelString.size(
withAttributes: [NSAttributedString.Key.font: font]
)
var titleLeftInset: CGFloat = -imageSize.width
var titleRigtInset: CGFloat = 0.0
var imageLeftInset: CGFloat = 0.0
var imageRightInset: CGFloat = -titleSize.width
if Locale.current.languageCode! != "en" // If not Left to Right language
titleLeftInset = 0.0
titleRigtInset = -imageSize.width
imageLeftInset = -titleSize.width
imageRightInset = 0.0
self.titleEdgeInsets = UIEdgeInsets(
top: 0.0,
left: titleLeftInset,
bottom: -(imageSize.height + spacing),
right: titleRigtInset
)
self.imageEdgeInsets = UIEdgeInsets(
top: -(titleSize.height + spacing),
left: imageLeftInset,
bottom: 0.0,
right: imageRightInset
)
let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
self.contentEdgeInsets = UIEdgeInsets(
top: edgeOffset,
left: 0.0,
bottom: edgeOffset,
right: 0.0
)
【讨论】:
有很多非英语的 LTR 语言。您最好检查按钮上的 EffectiveUserInterfaceLayoutDirection。 标题很大,会造成问题。有什么解决办法吗?【参考方案29】:斯威夫特 5 - 下面的方法对我有用
func centerVerticallyWithPadding(padding : CGFloat)
guard
let imageViewSize = self.imageView?.frame.size,
let titleLabelSize = self.titleLabel?.frame.size else
return
let totalHeight = imageViewSize.height + titleLabelSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: max(0, -(totalHeight - imageViewSize.height)),
left: 0.0,
bottom: 0.0,
right: -titleLabelSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: (totalHeight - imageViewSize.height),
left: -imageViewSize.width,
bottom: -(totalHeight - titleLabelSize.height),
right: 0.0
)
self.contentEdgeInsets = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: titleLabelSize.height,
right: 0.0
)
确保您的按钮标题不会在故事板/xib 中被截断,否则请使用解决方案 2
class SVVerticalButton: UIButton
override func layoutSubviews()
super.layoutSubviews()
let padding : CGFloat = 2.0
if let imageView = self.imageView
imageView.frame.origin.x = (self.bounds.size.width - imageView.frame.size.width) / 2.0
imageView.frame.origin.y = max(0,(self.bounds.size.height - (imageView.frame.size.height + (titleLabel?.frame.size.height ?? 0.0) + padding)) / 2.0)
if let titleLabel = self.titleLabel
titleLabel.frame.origin.x = 0
titleLabel.frame.origin.y = self.bounds.size.height - titleLabel.frame.size.height
titleLabel.frame.size.width = self.bounds.size.width
titleLabel.textAlignment = .center
【讨论】:
顶部还是被剪掉了?这个方案,尺寸比想象的要小。【参考方案30】:带有子类 UIButton 的顶部图像和底部标题按钮
class VerticalButton: UIButton
override func layoutSubviews()
super.layoutSubviews()
let padding: CGFloat = 8
let iH = imageView?.frame.height ?? 0
let tH = titleLabel?.frame.height ?? 0
let v: CGFloat = (frame.height - iH - tH - padding) / 2
if let iv = imageView
let x = (frame.width - iv.frame.width) / 2
iv.frame.origin.y = v
iv.frame.origin.x = x
if let tl = titleLabel
let x = (frame.width - tl.frame.width) / 2
tl.frame.origin.y = frame.height - tl.frame.height - v
tl.frame.origin.x = x
【讨论】:
以上是关于UIButton中图像下的标签的主要内容,如果未能解决你的问题,请参考以下文章
如何在 OBJECTIVE C 的 UITableViewCell 中更改先前按下的 UIButton 标签?
在 TableViewCell 内单击时 UIButton 标签重置
如何将自定义 UIControl 中按下的 UIButton 传递给我的视图控制器?