问题
I'm trying to create a button which has some text beneath the icon (sorta like the app buttons) however it seems to be quite difficult to achieve. Any ideas how can I go about get the text to display below the image with a UIButton?
回答1:
Or you can just use this category:
ObjC
@interface UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding;
- (void)centerVertically;
@end
@implementation UIButton (VerticalLayout)
- (void)centerVerticallyWithPadding:(float)padding {
CGSize imageSize = self.imageView.frame.size;
CGSize titleSize = self.titleLabel.frame.size;
CGFloat totalHeight = (imageSize.height + titleSize.height + padding);
self.imageEdgeInsets = UIEdgeInsetsMake(- (totalHeight - imageSize.height),
0.0f,
0.0f,
- titleSize.width);
self.titleEdgeInsets = UIEdgeInsetsMake(0.0f,
- imageSize.width,
- (totalHeight - titleSize.height),
0.0f);
self.contentEdgeInsets = UIEdgeInsetsMake(0.0f,
0.0f,
titleSize.height,
0.0f);
}
- (void)centerVertically {
const CGFloat kDefaultPadding = 6.0f;
[self centerVerticallyWithPadding:kDefaultPadding];
}
@end
Swift extension
extension UIButton {
func centerVertically(padding: CGFloat = 6.0) {
guard
let imageViewSize = self.imageView?.frame.size,
let titleLabelSize = self.titleLabel?.frame.size else {
return
}
let totalHeight = imageViewSize.height + titleLabelSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageViewSize.height),
left: 0.0,
bottom: 0.0,
right: -titleLabelSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0.0,
left: -imageViewSize.width,
bottom: -(totalHeight - titleLabelSize.height),
right: 0.0
)
self.contentEdgeInsets = UIEdgeInsets(
top: 0.0,
left: 0.0,
bottom: titleLabelSize.height,
right: 0.0
)
}
}
Suggestion:
If button height is less than totalHeight, then image will draw outside borders.
imageEdgeInset.top should be:
max(0, -(totalHeight - imageViewSize.height))
回答2:
In Xcode, you can simply set the Edge Title Left Inset to negative the width of the image. This will display the label in the center of the image.
To get the label to display below the image (sorta like the app buttons), you may need to set the Edge Title Top Inset to some positive number.
回答3:
This is a simple centered title button implemented in Swift by overriding titleRect(forContentRect:) and imageRect(forContentRect:). It also implements intrinsicContentSize for use with AutoLayout.
import UIKit
class CenteredButton: UIButton
{
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.titleRect(forContentRect: contentRect)
return CGRect(x: 0, y: contentRect.height - rect.height + 5,
width: contentRect.width, height: rect.height)
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let rect = super.imageRect(forContentRect: contentRect)
let titleRect = self.titleRect(forContentRect: contentRect)
return CGRect(x: contentRect.width/2.0 - rect.width/2.0,
y: (contentRect.height - titleRect.height)/2.0 - rect.height/2.0,
width: rect.width, height: rect.height)
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
if let image = imageView?.image {
var labelHeight: CGFloat = 0.0
if let size = titleLabel?.sizeThatFits(CGSize(width: self.contentRect(forBounds: self.bounds).width, height: CGFloat.greatestFiniteMagnitude)) {
labelHeight = size.height
}
return CGSize(width: size.width, height: image.size.height + labelHeight + 5)
}
return size
}
override init(frame: CGRect) {
super.init(frame: frame)
centerTitleLabel()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
centerTitleLabel()
}
private func centerTitleLabel() {
self.titleLabel?.textAlignment = .center
}
}
回答4:
Look at this great answer in Swift.
extension UIButton {
func alignImageAndTitleVertically(padding: CGFloat = 6.0) {
let imageSize = self.imageView!.frame.size
let titleSize = self.titleLabel!.frame.size
let totalHeight = imageSize.height + titleSize.height + padding
self.imageEdgeInsets = UIEdgeInsets(
top: -(totalHeight - imageSize.height),
left: 0,
bottom: 0,
right: -titleSize.width
)
self.titleEdgeInsets = UIEdgeInsets(
top: 0,
left: -imageSize.width,
bottom: -(totalHeight - titleSize.height),
right: 0
)
}
}
回答5:
Subclass UIButton. Override -layoutSubviews to move the built-in subviews into new positions:
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect frame = self.imageView.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), 0.0f, frame.size.width, frame.size.height);
self.imageView.frame = frame;
frame = self.titleLabel.frame;
frame = CGRectMake(truncf((self.bounds.size.width - frame.size.width) / 2), self.bounds.size.height - frame.size.height, frame.size.width, frame.size.height);
self.titleLabel.frame = frame;
}
回答6:
corrected one of the answers here:
Swift 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
}
}
回答7:
Refactored icecrystal23`s answer.
Swift 3, works with autolayouts, xib, storyboards, can be animated.
Button in orginal icecrystal23`s answer had a badly calculated frame. I think I fixed that.
Edit: Updated to Swift 5 and made work inside 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()
}
}
回答8:
This is a modified version of Erik W's excellent answer. But instead of placing the image centered at the TOP of the view, it places the image and the label centred in the view as a group.
The difference is:
+-----------+
| ( ) |
| Hello | // Erik W's code
| |
| |
+-----------+
vs
+-----------+
| |
| ( ) | // My modified version
| Hello |
| |
+-----------+
Source below:
-(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;
}
FYI: This can break when combined with UIView animations, as layoutSubviews is called during them.
回答9:
If you subclass UIButton and override layoutSubviews, you can use the below to center the image and place the title centered below it:
kTextTopPadding is a constant you'll have to introduce that determines the space between the image and the text below it.
-(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;
}
回答10:
Dave's solution in 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
}
}
回答11:
Updated Kenny Winker's answer since sizeWithFont was deprecated in 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;
}
回答12:
On iOS 11/ Swift 4 none of the above answers really worked for me. I found some examples and put my spin on it:
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)
}
}
Hope this helps someone.
回答13:
Using the code of Kenny Winker's and simeon i make this swift code that works for me.
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
}
}
回答14:
You just have to adjust all three edge insets based on the size of your image and title label:
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)
You can get the title label bounds by calling sizeToFit after setting its text. The horizontal spacing should work regardless of the size of the text, font and image but I don't know of a single solution to get the vertical spacing and bottom content edge inset consistent.
回答15:
Here is "Bear With Me"s answer as a subclass in Swift 2.0. To use it just change your button class in Interface Builder to VerticalButton and it will magically update the preview.
I also updated it to calculate the correct intrinsic content size for autolayout.
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)
}
}
回答16:
@Tiago I change your answer like this. Its works fine with all sizes
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:
I took a combination of the answers here and came up with one that seems to be working for me, in Swift. I don't love how I just overrode the insets, but it works. I'd be open to suggested improvements in the comments. It seems to work correctly with sizeToFit() and with auto layout.
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
}
}
回答18:
I found that Simeon's answer was probably the best but it was giving me strange results on some buttons and I just couldn't work out why. So using his answer as a base I implemented my buttons as per below:
#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
回答19:
Use this two methods:
func titleRect(forContentRect contentRect: CGRect) -> CGRect
func imageRect(forContentRect contentRect: CGRect) -> CGRect
Example:
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)
}
}
And you can use:
let myButton = UIButton.vertical(padding: 6)
回答20:
Here is my subclass of UIButton which solves this problem:
@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
That's it. Works like charm. Problem may appear if button will be to small to fit title and text.
回答21:
I think one of the best ways to do that is by subclassing UIButton and override some rendering methods:
- (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);
}
回答22:
If you are using custom fonts the calculation for the titleLabel size won't work properly, you should replace it with
let titleLabelSize = self.titleLabel?.text?.size(withAttributes: [NSAttributedStringKey.font: self.titleLabel!.font!])
回答23:
Localization Friendly Solution:
So many great solutions guys, but I'd like to add a note here for those who use localization.
You need to reverse the left and right EdgeInstets values to get the button laid out correctly in case of a change of language direction from LtR to RtL.
Using a similar solution I'd implement it as follows:
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
)
}
}
回答24:
Something like this inside UIButton subclass
public override func layoutSubviews() {
super.layoutSubviews()
imageEdgeInsets = UIEdgeInsetsMake(-10, 0, 0, 0)
titleEdgeInsets = UIEdgeInsetsMake(0, -bounds.size.width/2 - 10, -30, 0)
}
回答25:
Its pretty simple.
Instead of this:
button.setImage(UIImage(named: "image"), forState: .Normal)
Use this:
button.setBackgroundImage(UIImage(named: "image", forState: .Normal)
Then you can add text on the button easily using:
// button.titleLabel!.font = UIFont(name: "FontName", size: 30)
button.setTitle("TitleText", forState: UIControlState.Normal)
回答26:
@simeon's solution in 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;
}
@end
回答27:
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; }
回答28:
Instead of going through hell trying to position the icon and the text with edge insets, you could create a NSAttributedString with your image as an attachment and set it to your button's attributed title instead:
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)
来源:https://stackoverflow.com/questions/4201959/label-under-image-in-uibutton