UIButton:如何使用imageEdgeInsets和titleEdgeInsets使图像和文本居中?

rei*_*man 158 objective-c uibutton ios

如果我只在一个按钮中放置一个图像并将imageEdgeInsets设置得更接近顶部,则图像保持居中并且所有图像都按预期工作:

[button setImage:image forState:UIControlStateNormal];
[button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, 0.0)];
Run Code Online (Sandbox Code Playgroud)

如果我只在一个按钮中放置一个文本并将titleEdgeInsets设置得更接近底部,则文本保持居中并且所有文件都按预期工作:

[button setTitle:title forState:UIControlStateNormal];
[button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, 0.0, -30, 0.0)];
Run Code Online (Sandbox Code Playgroud)

但是,如果我将4条线放在一起,文本会干扰图像,并且两者都会失去中心对齐.

我的所有图像都有30像素的宽度,如果我在setTitleEdgeInsets的UIEdgeInsetMake的左参数中放入30,则文本再次居中.问题是图像永远不会居中,因为它似乎依赖于button.titleLabel大小.我已经尝试了许多计算按钮大小,图像大小,titleLabel大小,永远不会完全居中.

有人已经有同样的问题吗?

Jes*_*sen 408

对于它的价值,这里是一个通用的解决方案,将图像定位在文本上方,而不使用任何幻数.请注意,以下代码已过时,您应该使用以下某个更新版本:

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.frame.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = button.titleLabel.frame.size;
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);
Run Code Online (Sandbox Code Playgroud)

以下版本包含支持iOS 7+的更改,这些更改已在下面的评论中推荐.我自己没有测试过这段代码,所以我不确定它是如何工作的,或者如果在以前版本的iOS下使用它会破坏它.

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.image.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = [button.titleLabel.text sizeWithAttributes:@{NSFontAttributeName: button.titleLabel.font}];
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

// increase the content height to avoid clipping
CGFloat edgeOffset = fabsf(titleSize.height - imageSize.height) / 2.0;
button.contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0);
Run Code Online (Sandbox Code Playgroud)

Swift版本

extension UIButton {
  func alignVertical(spacing: CGFloat = 6.0) {
    guard let imageSize = imageView?.image?.size,
      let text = titleLabel?.text,
      let font = titleLabel?.font
    else { return }

    titleEdgeInsets = UIEdgeInsets(
      top: 0.0,
      left: -imageSize.width,
      bottom: -(imageSize.height + spacing),
      right: 0.0
    )

    let titleSize = text.size(withAttributes: [.font: font])
    imageEdgeInsets = UIEdgeInsets(
      top: -(titleSize.height + spacing),
      left: 0.0,
      bottom: 0.0, right: -titleSize.width
    )

    let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0
    contentEdgeInsets = UIEdgeInsets(
      top: edgeOffset,
      left: 0.0,
      bottom: edgeOffset,
      right: 0.0
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 太棒了 - 谢谢!我认为很多其他答案都是关于这种"艰难的方式" - 这看起来好多了. (5认同)
  • 在iOS8中,我发现使用`button.currentTitle`代替`button.titleLabel.text`会更好,特别是如果按钮文本会改变的话.`currentTitle`会立即填充,而`titleLabel.text`可能会变慢,这可能会导致插入错位. (5认同)
  • @Hemang和Dan Jackson,我在没有自己测试的情况下整合你的建议.我觉得我最初为iOS 4编写这个版本有点荒谬,在这个版本之后,我们仍然需要对Apple的布局算法进行逆向工程以获得如此明显的功能.或者至少我认为没有一个更好的解决方案来自一致的upvotes流和下面同样的hackish答案(没有侮辱意图). (4认同)
  • 我发现了上面的内容,但我绝对没有心理模型.任何人都有链接到图形说明,了解各个EdgeInsets参数的影响?为什么文本宽度会发生变化? (3认同)
  • 当图像缩小以适合按钮时,这不起作用.似乎UIButton(至少在iOS 7中)使用image.size,而不是imageView.frame.size进行居中计算. (3认同)
  • 我写了一个小扩展,用于定位按钮内的图像和文本.如果您有兴趣,这是源代码.https://github.com/sssbohdan/ButtonAlignmentExtension/blob/master/UIButton.swift (2认同)

rei*_*man 59

发现如何.

首先,配置文本titleLabel(因为样式,即粗体,斜体等).然后,使用setTitleEdgeInsets考虑图像的宽度:

[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button setTitle:title forState:UIControlStateNormal];
[button.titleLabel setFont:[UIFont boldSystemFontOfSize:10.0]];

// Left inset is the negative of image width.
[button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, -image.size.width, -25.0, 0.0)]; 
Run Code Online (Sandbox Code Playgroud)

之后,使用setTitleEdgeInsets考虑文本边界的宽度:

[button setImage:image forState:UIControlStateNormal];

// Right inset is the negative of text bounds width.
[button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, -button.titleLabel.bounds.size.width)];
Run Code Online (Sandbox Code Playgroud)

现在图像和文本将居中(在此示例中,图像显示在文本上方).

干杯.

  • 7年,伙计.真的不记得了.当我问起时,我回答了自己(我的答案是当时唯一的答案).当当前所选答案的作者专注于消除那些神奇数字时,我已经改变了所选答案.因此,在选定的答案中,您可以弄清楚它们的含义. (2认同)

alg*_*gal 20

您可以使用此Swift扩展程序来完成此操作,该扩展程序部分基于Jesse Crossen的答案:

extension UIButton {
  func centerLabelVerticallyWithPadding(spacing:CGFloat) {
    // update positioning of image and title
    let imageSize = self.imageView.frame.size
    self.titleEdgeInsets = UIEdgeInsets(top:0,
                                        left:-imageSize.width,
                                        bottom:-(imageSize.height + spacing),
                                        right:0)
    let titleSize = self.titleLabel.frame.size
    self.imageEdgeInsets = UIEdgeInsets(top:-(titleSize.height + spacing),
                                        left:0,
                                        bottom: 0,
                                        right:-titleSize.width)

    // reset contentInset, so intrinsicContentSize() is still accurate
    let trueContentSize = CGRectUnion(self.titleLabel.frame, self.imageView.frame).size
    let oldContentSize = self.intrinsicContentSize()
    let heightDelta = trueContentSize.height - oldContentSize.height
    let widthDelta = trueContentSize.width - oldContentSize.width
    self.contentEdgeInsets = UIEdgeInsets(top:heightDelta/2.0,
                                          left:widthDelta/2.0,
                                          bottom:heightDelta/2.0,
                                          right:widthDelta/2.0)
  }
}
Run Code Online (Sandbox Code Playgroud)

这定义了一个centerLabelVerticallyWithPadding适当设置标题和图像插入的函数.

它还设置了contentEdgeInsets,我认为这是确保intrinsicContentSize仍能正常工作所必需的,需要使用自动布局.

我相信所有UIButton子类的解决方案在技术上都是非法的,因为你不应该继承UIKit控件的子类.即,从理论上讲,它们可能会在未来版本中破裂.


Tho*_*eek 13

编辑:已更新为Swift 3

如果您正在寻找Jesse Crossen答案的Swift解决方案,您可以将其添加到UIButton的子类中:

override func layoutSubviews() {

    let spacing: CGFloat = 6.0

    // lower the text and push it left so it appears centered
    //  below the image
    var titleEdgeInsets = UIEdgeInsets.zero
    if let image = self.imageView?.image {
        titleEdgeInsets.left = -image.size.width
        titleEdgeInsets.bottom = -(image.size.height + spacing)
    }
    self.titleEdgeInsets = titleEdgeInsets

    // raise the image and push it right so it appears centered
    //  above the text
    var imageEdgeInsets = UIEdgeInsets.zero
    if let text = self.titleLabel?.text, let font = self.titleLabel?.font {
        let attributes = [NSFontAttributeName: font]
        let titleSize = text.size(attributes: attributes)
        imageEdgeInsets.top = -(titleSize.height + spacing)
        imageEdgeInsets.right = -titleSize.width
    }
    self.imageEdgeInsets = imageEdgeInsets

    super.layoutSubviews()
}
Run Code Online (Sandbox Code Playgroud)


Tod*_*ham 9

这里有一些很好的例子,但在处理多行文本(文本换行)时,我无法在所有情况下都能使用它.为了最终使它工作,我结合了几种技术:

  1. 我上面用了Jesse Crossen的例子.但是,我修复了文本高度问题,并添加了指定水平文本边距的功能.允许文本换行时边距非常有用,因此它不会触及按钮的边缘:

    // the space between the image and text
    CGFloat spacing = 10.0;
    float   textMargin = 6;
    
    // get the size of the elements here for readability
    CGSize  imageSize   = picImage.size;
    CGSize  titleSize   = button.titleLabel.frame.size;
    CGFloat totalHeight = (imageSize.height + titleSize.height + spacing);      // get the height they will take up as a unit
    
    // lower the text and push it left to center it
    button.titleEdgeInsets = UIEdgeInsetsMake( 0.0, -imageSize.width +textMargin, - (totalHeight - titleSize.height), +textMargin );   // top, left, bottom, right
    
    // the text width might have changed (in case it was shortened before due to 
    // lack of space and isn't anymore now), so we get the frame size again
    titleSize = button.titleLabel.bounds.size;
    
    button.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0.0, 0.0, -titleSize.width );     // top, left, bottom, right        
    
    Run Code Online (Sandbox Code Playgroud)
  2. 确保设置要包装的文本标签

    button.titleLabel.numberOfLines = 2; 
    button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
    button.titleLabel.textAlignment = UITextAlignmentCenter;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 这将主要工作.但是,我有一些按钮无法正确渲染图像.图像要么向右或向左移动(它没有居中).所以我使用了UIButton布局覆盖技术来强制imageView居中.

    @interface CategoryButton : UIButton
    @end
    
    @implementation CategoryButton
    
    - (void)layoutSubviews
    {
        // Allow default layout, then center imageView
        [super layoutSubviews];
    
        UIImageView *imageView = [self imageView];
        CGRect imageFrame = imageView.frame;
        imageFrame.origin.x = (int)((self.frame.size.width - imageFrame.size.width)/ 2);
        imageView.frame = imageFrame;
    }
    @end
    
    Run Code Online (Sandbox Code Playgroud)


小智 9

我为@ TodCunningham的答案做了一个方法

 -(void) AlignTextAndImageOfButton:(UIButton *)button
 {
   CGFloat spacing = 2; // the amount of spacing to appear between image and title
   button.imageView.backgroundColor=[UIColor clearColor];
   button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
   button.titleLabel.textAlignment = UITextAlignmentCenter;
   // get the size of the elements here for readability
   CGSize imageSize = button.imageView.frame.size;
   CGSize titleSize = button.titleLabel.frame.size;

  // lower the text and push it left to center it
  button.titleEdgeInsets = UIEdgeInsetsMake(0.0, - imageSize.width, - (imageSize.height   + spacing), 0.0);

  // the text width might have changed (in case it was shortened before due to 
  // lack of space and isn't anymore now), so we get the frame size again
   titleSize = button.titleLabel.frame.size;

  // raise the image and push it right to center it
  button.imageEdgeInsets = UIEdgeInsetsMake(- (titleSize.height + spacing), 0.0, 0.0, -     titleSize.width);
 }
Run Code Online (Sandbox Code Playgroud)


Ste*_*mer 6

不要打架系统.如果你的布局变得太复杂而无法使用Interface Builder +或者一些简单的配置代码进行管理,那么可以使用更简单的方式手动完成布局layoutSubviews- 这就是它的用途!其他一切都将成为黑客.

创建一个UIButton子类并覆盖其layoutSubviews方法以编程方式对齐文本和图像.或者使用类似https://github.com/nickpaulson/BlockKit/blob/master/Source/UIView-BKAdditions.h的内容,以便您可以使用块实现layoutSubviews.


小智 6

子类UIButton

- (void)layoutSubviews {
    [super layoutSubviews];
    CGFloat spacing = 6.0;
    CGSize imageSize = self.imageView.image.size;
    CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(self.frame.size.width, self.frame.size.height - (imageSize.height + spacing))];
    self.imageView.frame = CGRectMake((self.frame.size.width - imageSize.width)/2, (self.frame.size.height - (imageSize.height+spacing+titleSize.height))/2, imageSize.width, imageSize.height);
    self.titleLabel.frame = CGRectMake((self.frame.size.width - titleSize.width)/2, CGRectGetMaxY(self.imageView.frame)+spacing, titleSize.width, titleSize.height);
}
Run Code Online (Sandbox Code Playgroud)


Tom*_* C. 6

其他答案没有错,但我只想注意,使用零行代码可以在Xcode中直观地完成相同的行为.如果您不需要计算值或使用storyboard/xib构建(否则应用其他解决方案),此解决方案非常有用.

注意 - 我确实理解OP的问题是需要代码的问题.我只是提供完整性的答案,并作为使用storyboards/xibs的逻辑替代品.

要使用边缘插入修改按钮的图像,标题和内容视图的间距,可以选择按钮/控件并打开属性检查器.向下滚动到检查器的中间,找到Edge insets的部分.

边缘插图

还可以访问和修改标题,图像或内容视图的特定边缘插入.

菜单选项


Doc*_*oca 6

Jesse CrossenSwift 4的更新答案:

extension UIButton {
    func alignVertical(spacing: CGFloat = 6.0) {
        guard let imageSize = self.imageView?.image?.size,
            let text = self.titleLabel?.text,
            let font = self.titleLabel?.font
            else { return }
        self.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0.0)
        let labelString = NSString(string: text)
        let titleSize = labelString.size(withAttributes: [kCTFontAttributeName as NSAttributedStringKey: font])
        self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
        let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
        self.contentEdgeInsets = UIEdgeInsets(top: edgeOffset, left: 0.0, bottom: edgeOffset, right: 0.0)
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这种方式:

override func viewDidLayoutSubviews() {
    button.alignVertical()
}
Run Code Online (Sandbox Code Playgroud)


Boh*_*ych 5

通过这段代码,你会得到类似这样的东西标题和图像对齐

extension UIButton {
    func alignTextUnderImage() {
        guard let imageView = imageView else {
                return
        }
        self.contentVerticalAlignment = .Top
        self.contentHorizontalAlignment = .Center
        let imageLeftOffset = (CGRectGetWidth(self.bounds) - CGRectGetWidth(imageView.bounds)) / 2//put image in center
        let titleTopOffset = CGRectGetHeight(imageView.bounds) + 5
        self.imageEdgeInsets = UIEdgeInsetsMake(0, imageLeftOffset, 0, 0)
        self.titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset, -CGRectGetWidth(imageView.bounds), 0, 0)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我编写了一个小扩展,用于在按钮内部定位图像和文本。如果您有兴趣,这里是源代码。https://github.com/sssbohdan/ButtonAlignmentExtension/blob/master/UIButton.swift (2认同)