用imageEdgeInsets和titleEdgeInsets对齐UIButton上的文本和图像

我想在两行文本的左边放置一个图标,这样在图像和文本开始之间有大约2-3像素的空间。控件本身水平居中对齐(通过接口生成器设置)

这个按钮应该是这样的:

|                  |
|[Image] Add To    |
|        Favorites |

我试图配置这与contenttedgeinset, imageEdgeInsets和titleEdgeInsets无效。我知道,负值会使边缘扩大,而正值会使边缘缩小,使边缘更接近中心。

我试着:

[button setTitleEdgeInsets:UIEdgeInsetsMake(0, -image.size.width, 0, 0)];
[button setImageEdgeInsets:UIEdgeInsetsMake(0, button.titleLabel.bounds.size.width, 0, 0)];

但这并没有正确显示它。我一直在调整这些值,但从-5到-10的左插入值似乎不会以预期的方式移动它。-10将文本一直向左移动,所以我期望-5将它从左侧移动一半,但事实并非如此。

嵌套背后的逻辑是什么?我不熟悉图像放置和相关术语。

我用这个SO问题作为参考,但我的价值观有些不对。 UIButton:如何使用imageEdgeInsets和titleEdgeInsets居中图像和文本? < / p >
252441 次浏览

我同意关于imageEdgeInsetstitleEdgeInsets的文档应该更好,但我想出了如何获得正确的定位而不求助于试验和错误。

一般思想在这个问题,但前提是你想同时以文本和图像为中心。我们不希望图像和文本分别居中,我们希望图像和文本作为一个单独的实体放在一起。这实际上是UIButton已经做的,所以我们只需要调整间距。

CGFloat spacing = 10; // the amount of spacing to appear between image and title
tabBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
tabBtn.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);

我还把它变成了UIButton的一个类别,这样它就很容易使用:

UIButton + Position.h

@interface UIButton(ImageTitleCentering)


-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing;


@end

UIButton + Position.m

@implementation UIButton(ImageTitleCentering)


-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing {
self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
self.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);
}


@end

所以现在我要做的就是

[button centerButtonAndImageWithSpacing:10];

我每次都能得到我想要的。没有更多的混乱与边缘插入手动。

编辑:交换图像和文本

回复@Javal的评论

使用相同的机制,我们可以交换图像和文本。要完成交换,只需使用负间距,但也包括文本和图像的宽度。这将要求帧是已知的和布局已经执行。

[self.view layoutIfNeeded];
CGFloat flippedSpacing = -(desiredSpacing + button.currentImage.size.width + button.titleLabel.frame.size.width);
[button centerButtonAndImageWithSpacing:flippedSpacing];

当然,你可能会想要为此创建一个很好的方法,可能会添加第二个类别方法,这是留给读者的练习。

在接口生成器中。选择UIButton -> Attributes Inspector -> Edge=Title并修改边缘插入

你可以用这个——来避免很多麻烦

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
myButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

这将自动对齐你所有的内容到左边(或任何你想要的地方)

斯威夫特3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;

我来这个派对有点晚,但我想我有一些有用的东西要补充。

Kekoa的答案是伟大的,但是,正如RonLugge提到的,它可以使按钮不再尊重sizeToFit,或者更重要的是,可以导致按钮剪辑它的内容时,它的本质大小。呵!

不过,首先

简单解释一下我认为imageEdgeInsetstitleEdgeInsets是如何工作的:

imageEdgeInsets的docs的部分内容如下:

使用此属性可调整按钮图像的有效绘图矩形的大小和位置。您可以为这四个嵌入(顶部、左侧、底部、右侧)中的每一个指定不同的值。正值会缩小或插入这条边——使它更靠近按钮的中心。负值会使这条边扩大或开始。

我相信这个文档是在想象这个按钮没有标题,只有一个图像的情况下编写的。以这种方式思考更有意义,并且表现为UIEdgeInsets通常做的事情。基本上,图像的帧(或带有titleEdgeInsets的标题)在正插入时向内移动,在负插入时向外移动。

好吧,那又怎样?

我快到了!这是你默认设置的,设置一个图像和一个标题(按钮边框是绿色的,只是为了显示它的位置):

开始图片;标题和图片之间没有空格。”/></p>
<p>当你想要图像和标题之间有空格,而又不造成两者被压缩时,你需要设置四个不同的insets,图像和标题各两个。这是因为你不想改变这些元素帧的<em>大小</em>,而只是改变它们的位置。当你开始以这种方式思考时,Kekoa的优秀类别所需要的改变就变得清晰起来:</p>
<pre><code>@implementation UIButton(ImageTitleCentering)


- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
CGFloat insetAmount = spacing / 2.0;
self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
}


@end
</code></pre>
<p><em>但是等等</em>,你说,<em>当我这样做的时候,我得到了这个:</em></p>
<p><img src=

噢,是的!我忘了,的文档警告过我这个。他们部分地说:

此属性仅用于布局期间定位图像。按钮不使用此属性来确定intrinsicContentSizesizeThatFits:

但是有一个属性可以提供帮助,那就是contentEdgeInsets的文档表示部分:

按钮使用此属性确定intrinsicContentSizesizeThatFits:

听起来不错。所以让我们再一次调整这个类别:

@implementation UIButton(ImageTitleCentering)


- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
CGFloat insetAmount = spacing / 2.0;
self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
self.contentEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, insetAmount);
}


@end

得到什么?

间隔和帧现在正确。

在我看来是赢家。


在Swift中工作,却不想做任何思考?下面是Swift扩展的最终版本:

extension UIButton {


func centerTextAndImage(spacing: CGFloat) {
let insetAmount = spacing / 2
let isRTL = UIView.userInterfaceLayoutDirection(for: semanticContentAttribute) == .rightToLeft
if isRTL {
imageEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
titleEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
contentEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: -insetAmount)
} else {
imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
}
}
}
下面是一个如何使用imageEdgeInsets的简单示例 这将创建一个30x30的按钮,可点击区域增大10像素(50x50)

    var expandHittableAreaAmt : CGFloat = 10
var buttonWidth : CGFloat = 30
var button = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
button.frame = CGRectMake(0, 0, buttonWidth+expandHittableAreaAmt, buttonWidth+expandHittableAreaAmt)
button.imageEdgeInsets = UIEdgeInsetsMake(expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt)
button.setImage(UIImage(named: "buttonImage"), forState: .Normal)
button.addTarget(self, action: "didTouchButton:", forControlEvents:.TouchUpInside)

如果你想做一些类似的东西

enter image description here

你需要

1.为按钮设置水平和垂直对齐

enter image description here

  1. 找到所有需要的值并设置UIImageEdgeInsets

            CGSize buttonSize = button.frame.size;
    NSString *buttonTitle = button.titleLabel.text;
    CGSize titleSize = [buttonTitle sizeWithAttributes:@{ NSFontAttributeName : [UIFont camFontZonaProBoldWithSize:12.f] }];
    UIImage *buttonImage = button.imageView.image;
    CGSize buttonImageSize = buttonImage.size;
    
    
    CGFloat offsetBetweenImageAndText = 10; //vertical space between image and text
    
    
    [button setImageEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 - offsetBetweenImageAndText,
    (buttonSize.width - buttonImageSize.width) / 2,
    0,0)];
    [button setTitleEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 + buttonImageSize.height + offsetBetweenImageAndText,
    titleSize.width + [button imageEdgeInsets].left > buttonSize.width ? -buttonImage.size.width  +  (buttonSize.width - titleSize.width) / 2 : (buttonSize.width - titleSize.width) / 2 - buttonImage.size.width,
    0,0)];
    

This will arrange your title and image on button.

Also please note update this on each relayout


Swift

import UIKit


extension UIButton {
// MARK: - UIButton+Aligment


func alignContentVerticallyByCenter(offset:CGFloat = 10) {
let buttonSize = frame.size


if let titleLabel = titleLabel,
let imageView = imageView {


if let buttonTitle = titleLabel.text,
let image = imageView.image {
let titleString:NSString = NSString(string: buttonTitle)
let titleSize = titleString.sizeWithAttributes([
NSFontAttributeName : titleLabel.font
])
let buttonImageSize = image.size


let topImageOffset = (buttonSize.height - (titleSize.height + buttonImageSize.height + offset)) / 2
let leftImageOffset = (buttonSize.width - buttonImageSize.width) / 2
imageEdgeInsets = UIEdgeInsetsMake(topImageOffset,
leftImageOffset,
0,0)


let titleTopOffset = topImageOffset + offset + buttonImageSize.height
let leftTitleOffset = (buttonSize.width - titleSize.width) / 2 - image.size.width


titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset,
leftTitleOffset,
0,0)
}
}
}
}

Riley Avron回答的一个小补充,以解释地区变化:

extension UIButton {
func centerTextAndImage(spacing: CGFloat) {
let insetAmount = spacing / 2
let writingDirection = UIApplication.sharedApplication().userInterfaceLayoutDirection
let factor: CGFloat = writingDirection == .LeftToRight ? 1 : -1


self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
}
}

我来这个派对也有点晚了,但我想我有一些有用的补充:o)。

我创建了一个UIButton子类,它的目的是能够选择按钮图像的布局位置,无论是垂直还是水平。

这意味着你可以制作这样的按钮: different kind of buttons < / p >

下面是如何用我的类创建这些按钮的细节:

func makeButton (imageVerticalAlignment:LayoutableButton.VerticalAlignment, imageHorizontalAlignment:LayoutableButton.HorizontalAlignment, title:String) -> LayoutableButton {
let button = LayoutableButton ()


button.imageVerticalAlignment = imageVerticalAlignment
button.imageHorizontalAlignment = imageHorizontalAlignment


button.setTitle(title, for: .normal)


// add image, border, ...


return button
}


let button1 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .left, title: "button1")
let button2 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .right, title: "button2")
let button3 = makeButton(imageVerticalAlignment: .top, imageHorizontalAlignment: .center, title: "button3")
let button4 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button4")
let button5 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button5")
button5.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

为此,我添加了2个属性:imageVerticalAlignmentimageHorizontalAlignment。当然,如果你的按钮只有一个图片或标题…不要使用这个类!

我还添加了一个名为imageToTitleSpacing的属性,它允许你调整标题和图像之间的空格。

如果你想直接使用imageEdgeInsetstitleEdgeInsetscontentEdgeInsets,或者与新的布局属性结合使用,这个类会尽量兼容。

正如@ravron解释的那样,我尽我最大的努力使按钮内容边缘正确(正如你可以看到的红色边界)。

你也可以在Interface Builder中使用它:

  1. 创建一个UIButton
  2. 更改按钮类
  3. 使用“center”,“top”,“bottom”,“left”或“right”调整可布局属性按钮属性

下面是代码(要点):

@IBDesignable
class LayoutableButton: UIButton {


enum VerticalAlignment : String {
case center, top, bottom, unset
}




enum HorizontalAlignment : String {
case center, left, right, unset
}




@IBInspectable
var imageToTitleSpacing: CGFloat = 8.0 {
didSet {
setNeedsLayout()
}
}




var imageVerticalAlignment: VerticalAlignment = .unset {
didSet {
setNeedsLayout()
}
}


var imageHorizontalAlignment: HorizontalAlignment = .unset {
didSet {
setNeedsLayout()
}
}


@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageVerticalAlignment' instead.")
@IBInspectable
var imageVerticalAlignmentName: String {
get {
return imageVerticalAlignment.rawValue
}
set {
if let value = VerticalAlignment(rawValue: newValue) {
imageVerticalAlignment = value
} else {
imageVerticalAlignment = .unset
}
}
}


@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageHorizontalAlignment' instead.")
@IBInspectable
var imageHorizontalAlignmentName: String {
get {
return imageHorizontalAlignment.rawValue
}
set {
if let value = HorizontalAlignment(rawValue: newValue) {
imageHorizontalAlignment = value
} else {
imageHorizontalAlignment = .unset
}
}
}


var extraContentEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero


override var contentEdgeInsets: UIEdgeInsets {
get {
return super.contentEdgeInsets
}
set {
super.contentEdgeInsets = newValue
self.extraContentEdgeInsets = newValue
}
}


var extraImageEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero


override var imageEdgeInsets: UIEdgeInsets {
get {
return super.imageEdgeInsets
}
set {
super.imageEdgeInsets = newValue
self.extraImageEdgeInsets = newValue
}
}


var extraTitleEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero


override var titleEdgeInsets: UIEdgeInsets {
get {
return super.titleEdgeInsets
}
set {
super.titleEdgeInsets = newValue
self.extraTitleEdgeInsets = newValue
}
}


//Needed to avoid IB crash during autolayout
override init(frame: CGRect) {
super.init(frame: frame)
}




required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)


self.imageEdgeInsets = super.imageEdgeInsets
self.titleEdgeInsets = super.titleEdgeInsets
self.contentEdgeInsets = super.contentEdgeInsets
}


override func layoutSubviews() {
if let imageSize = self.imageView?.image?.size,
let font = self.titleLabel?.font,
let textSize = self.titleLabel?.attributedText?.size() ?? self.titleLabel?.text?.size(attributes: [NSFontAttributeName: font]) {


var _imageEdgeInsets = UIEdgeInsets.zero
var _titleEdgeInsets = UIEdgeInsets.zero
var _contentEdgeInsets = UIEdgeInsets.zero


let halfImageToTitleSpacing = imageToTitleSpacing / 2.0


switch imageVerticalAlignment {
case .bottom:
_imageEdgeInsets.top = (textSize.height + imageToTitleSpacing) / 2.0
_imageEdgeInsets.bottom = (-textSize.height - imageToTitleSpacing) / 2.0
_titleEdgeInsets.top = (-imageSize.height - imageToTitleSpacing) / 2.0
_titleEdgeInsets.bottom = (imageSize.height + imageToTitleSpacing) / 2.0
_contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
_contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
//only works with contentVerticalAlignment = .center
contentVerticalAlignment = .center
case .top:
_imageEdgeInsets.top = (-textSize.height - imageToTitleSpacing) / 2.0
_imageEdgeInsets.bottom = (textSize.height + imageToTitleSpacing) / 2.0
_titleEdgeInsets.top = (imageSize.height + imageToTitleSpacing) / 2.0
_titleEdgeInsets.bottom = (-imageSize.height - imageToTitleSpacing) / 2.0
_contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
_contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
//only works with contentVerticalAlignment = .center
contentVerticalAlignment = .center
case .center:
//only works with contentVerticalAlignment = .center
contentVerticalAlignment = .center
break
case .unset:
break
}


switch imageHorizontalAlignment {
case .left:
_imageEdgeInsets.left = -halfImageToTitleSpacing
_imageEdgeInsets.right = halfImageToTitleSpacing
_titleEdgeInsets.left = halfImageToTitleSpacing
_titleEdgeInsets.right = -halfImageToTitleSpacing
_contentEdgeInsets.left = halfImageToTitleSpacing
_contentEdgeInsets.right = halfImageToTitleSpacing
case .right:
_imageEdgeInsets.left = textSize.width + halfImageToTitleSpacing
_imageEdgeInsets.right = -textSize.width - halfImageToTitleSpacing
_titleEdgeInsets.left = -imageSize.width - halfImageToTitleSpacing
_titleEdgeInsets.right = imageSize.width + halfImageToTitleSpacing
_contentEdgeInsets.left = halfImageToTitleSpacing
_contentEdgeInsets.right = halfImageToTitleSpacing
case .center:
_imageEdgeInsets.left = textSize.width / 2.0
_imageEdgeInsets.right = -textSize.width / 2.0
_titleEdgeInsets.left = -imageSize.width / 2.0
_titleEdgeInsets.right = imageSize.width / 2.0
_contentEdgeInsets.left = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
_contentEdgeInsets.right = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
case .unset:
break
}


_contentEdgeInsets.top += extraContentEdgeInsets.top
_contentEdgeInsets.bottom += extraContentEdgeInsets.bottom
_contentEdgeInsets.left += extraContentEdgeInsets.left
_contentEdgeInsets.right += extraContentEdgeInsets.right


_imageEdgeInsets.top += extraImageEdgeInsets.top
_imageEdgeInsets.bottom += extraImageEdgeInsets.bottom
_imageEdgeInsets.left += extraImageEdgeInsets.left
_imageEdgeInsets.right += extraImageEdgeInsets.right


_titleEdgeInsets.top += extraTitleEdgeInsets.top
_titleEdgeInsets.bottom += extraTitleEdgeInsets.bottom
_titleEdgeInsets.left += extraTitleEdgeInsets.left
_titleEdgeInsets.right += extraTitleEdgeInsets.right


super.imageEdgeInsets = _imageEdgeInsets
super.titleEdgeInsets = _titleEdgeInsets
super.contentEdgeInsets = _contentEdgeInsets


} else {
super.imageEdgeInsets = extraImageEdgeInsets
super.titleEdgeInsets = extraTitleEdgeInsets
super.contentEdgeInsets = extraContentEdgeInsets
}


super.layoutSubviews()
}
}

Xcode 8.0中,你可以通过在大小检查器中改变insets来简单地做到这一点。

选择UIButton ->属性检查器->进入大小检查器,修改内容,图像和标题插入。

enter image description here

如果你想改变右边的图像,你可以在属性检查器中简单地将语义属性更改为Force Right-to-left

enter image description here

在Swift 3中有一种优雅的方式,更好地理解:

override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
let leftMargin:CGFloat = 40
let imgWidth:CGFloat = 24
let imgHeight:CGFloat = 24
return CGRect(x: leftMargin, y: (contentRect.size.height-imgHeight) * 0.5, width: imgWidth, height: imgHeight)
}


override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
let leftMargin:CGFloat = 80
let rightMargin:CGFloat = 80
return CGRect(x: leftMargin, y: 0, width: contentRect.size.width-leftMargin-rightMargin, height: contentRect.size.height)
}
override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
let leftMargin:CGFloat = 10
let rightMargin:CGFloat = 10
let topMargin:CGFloat = 10
let bottomMargin:CGFloat = 10
return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}
override func contentRect(forBounds bounds: CGRect) -> CGRect {
let leftMargin:CGFloat = 5
let rightMargin:CGFloat = 5
let topMargin:CGFloat = 5
let bottomMargin:CGFloat = 5
return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}

快4.倍

extension UIButton {
func centerTextAndImage(spacing: CGFloat) {
let insetAmount = spacing / 2
let writingDirection = UIApplication.shared.userInterfaceLayoutDirection
let factor: CGFloat = writingDirection == .leftToRight ? 1 : -1


self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
}
}

使用:

button.centerTextAndImage(spacing: 10.0)

下面是我写的代码。它在产品版本中运行良好。支持Swift 4.2 +

extension UIButton{
enum ImageTitleRelativeLocation {
case imageUpTitleDown
case imageDownTitleUp
case imageLeftTitleRight
case imageRightTitleLeft
}
func centerContentRelativeLocation(_ relativeLocation:
ImageTitleRelativeLocation,
spacing: CGFloat = 0) {
assert(contentVerticalAlignment == .center,
"only works with contentVerticalAlignment = .center !!!")


guard (title(for: .normal) != nil) || (attributedTitle(for: .normal) != nil) else {
assert(false, "TITLE IS NIL! SET TITTLE FIRST!")
return
}


guard let imageSize = self.currentImage?.size else {
assert(false, "IMGAGE IS NIL! SET IMAGE FIRST!!!")
return
}
guard let titleSize = titleLabel?
.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) else {
assert(false, "TITLELABEL IS NIL!")
return
}


let horizontalResistent: CGFloat
// extend contenArea in case of title is shrink
if frame.width < titleSize.width + imageSize.width {
horizontalResistent = titleSize.width + imageSize.width - frame.width
print("horizontalResistent", horizontalResistent)
} else {
horizontalResistent = 0
}


var adjustImageEdgeInsets: UIEdgeInsets = .zero
var adjustTitleEdgeInsets: UIEdgeInsets = .zero
var adjustContentEdgeInsets: UIEdgeInsets = .zero


let verticalImageAbsOffset = abs((titleSize.height + spacing) / 2)
let verticalTitleAbsOffset = abs((imageSize.height + spacing) / 2)


switch relativeLocation {
case .imageUpTitleDown:


adjustImageEdgeInsets.top = -verticalImageAbsOffset
adjustImageEdgeInsets.bottom = verticalImageAbsOffset
adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2


adjustTitleEdgeInsets.top = verticalTitleAbsOffset
adjustTitleEdgeInsets.bottom = -verticalTitleAbsOffset
adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2


adjustContentEdgeInsets.top = spacing
adjustContentEdgeInsets.bottom = spacing
adjustContentEdgeInsets.left = -horizontalResistent
adjustContentEdgeInsets.right = -horizontalResistent
case .imageDownTitleUp:
adjustImageEdgeInsets.top = verticalImageAbsOffset
adjustImageEdgeInsets.bottom = -verticalImageAbsOffset
adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2


adjustTitleEdgeInsets.top = -verticalTitleAbsOffset
adjustTitleEdgeInsets.bottom = verticalTitleAbsOffset
adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2


adjustContentEdgeInsets.top = spacing
adjustContentEdgeInsets.bottom = spacing
adjustContentEdgeInsets.left = -horizontalResistent
adjustContentEdgeInsets.right = -horizontalResistent
case .imageLeftTitleRight:
adjustImageEdgeInsets.left = -spacing / 2
adjustImageEdgeInsets.right = spacing / 2


adjustTitleEdgeInsets.left = spacing / 2
adjustTitleEdgeInsets.right = -spacing / 2


adjustContentEdgeInsets.left = spacing
adjustContentEdgeInsets.right = spacing
case .imageRightTitleLeft:
adjustImageEdgeInsets.left = titleSize.width + spacing / 2
adjustImageEdgeInsets.right = -titleSize.width - spacing / 2


adjustTitleEdgeInsets.left = -imageSize.width - spacing / 2
adjustTitleEdgeInsets.right = imageSize.width + spacing / 2


adjustContentEdgeInsets.left = spacing
adjustContentEdgeInsets.right = spacing
}


imageEdgeInsets = adjustImageEdgeInsets
titleEdgeInsets = adjustTitleEdgeInsets
contentEdgeInsets = adjustContentEdgeInsets


setNeedsLayout()
}
}

swift 4.2版本的解决方案如下:

let spacing: CGFloat = 10 // the amount of spacing to appear between image and title
self.button?.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing)
self.button?.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0)

在swift 5.3和@ravron的启发下回答:

extension UIButton {
/// Fits the image and text content with a given spacing
/// - Parameters:
///   - spacing: Spacing between the Image and the text
///   - contentXInset: The spacing between the view to the left image and the right text to the view
func setHorizontalMargins(imageTextSpacing: CGFloat, contentXInset: CGFloat = 0) {
let imageTextSpacing = imageTextSpacing / 2
        

contentEdgeInsets = UIEdgeInsets(top: 0, left: (imageTextSpacing + contentXInset), bottom: 0, right: (imageTextSpacing + contentXInset))
imageEdgeInsets = UIEdgeInsets(top: 0, left: -imageTextSpacing, bottom: 0, right: imageTextSpacing)
titleEdgeInsets = UIEdgeInsets(top: 0, left: imageTextSpacing, bottom: 0, right: -imageTextSpacing)
}
}

它增加了一个额外的水平边距,从视图到图像,从标签到视图

我的垂直定心方法:

extension UIButton {
    

/// Layout image and title with vertical centering.
/// - Parameters:
///   - size: The button size.
///   - imageTopOffset: Top offset for image.
///   - spacing: Distance between image and title.
func verticalAlignmentByCenter(size: CGSize, imageTopOffset: CGFloat, spacing: CGFloat) {
let contentRect = contentRect(forBounds: CGRect(origin: .zero, size: size))
let imageRect = imageRect(forContentRect: contentRect)
let titleRect = titleRect(forContentRect: contentRect)


let imageTop = imageTopOffset - imageRect.origin.y
let imageLeft = contentRect.width/2 - imageRect.width/2
imageEdgeInsets = UIEdgeInsets(top: imageTop, left: imageLeft, bottom: 0, right: 0)


let titleTop = imageTopOffset + spacing + imageRect.height - titleRect.origin.y
let titleLeft = titleRect.origin.x - contentRect.width/2 - titleRect.width/2
titleEdgeInsets = UIEdgeInsets(top: titleTop, left: titleLeft, bottom: 0, right: 0)
}
    

}

@ravron非常出色地给出了这个答案。

在我的例子中,我不仅需要在图片和标题之间添加水平宽度,而且还需要在“导语”中添加水平空间。和“;trailing"按钮。

因此,我使用了内部图像和标签的intrinsicContentSize:

接收视图的自然大小,只考虑视图本身的属性。

|                                                                                   |
|[LEADING SPACE] [Image] [SPACE BETWEEN IMAGE AND TITLE] Add To     [TRAILING SPACE]|
|                                                        Favorites      |


let leadingTrailingSpace = 10
let horizontalWidthBetweenImageAndTitle = 4
let insetAmount = horizontalWidthBetweenImageAndTitle / CGFloat(2)
button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -CGFloat(insetAmount), bottom: 0, right: insetAmount);
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: CGFloat(insetAmount), bottom: 0, right: -insetAmount);
button.contentEdgeInsets = UIEdgeInsets(top: 0, left: CGFloat(insetAmount), bottom: 0, right: insetAmount);
    

let buttonWidth =
(button.titleLabel?.intrinsicContentSize.width ?? 0) +
(button.imageView?.intrinsicContentSize.width ?? 0)
+ insetAmount
+ leadingTrailingSpace
button.widthAnchor.constraint(equalToConstant: buttonWidth).isActive = true

iOS 15+中,你可以使用UIButton。配置:

var configuration = button.configuration
configuration?.imagePadding = 16
configuration?.titlePadding = 10
            

button.configuration = configuration

我将根据更新的Xcode 13更新答案。

用于图像和文本对齐。我们不需要使用任何扩展或一行代码。Xcode在属性选项卡中提供了预定义属性,如下图所示。

放置属性有4个图像属性——Top, Bottom, Leading, and Trailing

XCode 13 - Attribute Tab

接口构建器解决方案

事情正在发生变化,现在在XCode 13.1和iOS 15+中,Size inspector不影响嵌入,相反,在Attribute inspector下有PaddingContent insets属性,这带来了预期的效果

enter image description here

为了向后兼容,需要在Size inspector下做insets,正如@ravron所说。在IB中需要做一些组合:

  1. 让我们假设图像和标题之间的距离为8pt
  2. 添加标题左插图为8页
  3. 这将从右侧剪辑文本,所以需要平衡添加-8pt右插入标题
  4. 然后按钮的右插入也需要调整增加右插入8pt
  5. 完成了!这个按钮在iOS 14和15中看起来很棒

enter image description here

如果您不想使用按钮配置的原因不同的情况。你可以实现下面的函数。

如果您将图像设置为按钮。你可以随意使用按钮imageView。更改按钮的位置imageView。函数,实现您输入的值作为偏移从您的按钮的前导锚。

private func leftPadding(value: CGFloat, button: UIButton) {
button.imageView?.translatesAutoresizingMaskIntoConstraints = false
button.imageView?.leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: value).isActive = true
button.imageView?.centerYAnchor.constraint(equalTo: button.centerYAnchor).isActive = true
}

根据iOS 15及以上版本:

var config = UIButton.Configuration.plain() // depends on the button you want
config.image = UIImage(named: "view_all") // set the image
config.imagePadding = 5 // add the insets for the image
button.configuration = config // assign the config to the button
< p >来源: https://developer.apple.com/documentation/uikit/uibutton/configuration < / p >