在UILabel中添加空格/填充

264

我有一个UILabel,想在顶部和底部添加间距。通过将最小高度约束进行修改,我已经实现了:

Enter image description here

为此,我使用了以下代码:

override func drawTextInRect(rect: CGRect) {
    var insets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0)
    super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
但我必须找到一种不同的方法,因为如果我写超过两行,问题就会相同:

Enter image description here


我们终于终于弄清楚了如何在所有动态情况下完美地替换UILabel,而无需重新布局或任何其他问题。太好了。 - Fattie
38个回答

307

我已经在Swift 4.2上尝试过了,希望它对你有用!

@IBDesignable class PaddingLabel: UILabel {

    @IBInspectable var topInset: CGFloat = 5.0
    @IBInspectable var bottomInset: CGFloat = 5.0
    @IBInspectable var leftInset: CGFloat = 7.0
    @IBInspectable var rightInset: CGFloat = 7.0

    override func drawText(in rect: CGRect) {
        let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
        super.drawText(in: rect.inset(by: insets))
    }

    override var intrinsicContentSize: CGSize {
        let size = super.intrinsicContentSize
        return CGSize(width: size.width + leftInset + rightInset,
                      height: size.height + topInset + bottomInset)
    }   

    override var bounds: CGRect {
        didSet {
            // ensures this works within stack views if multi-line
            preferredMaxLayoutWidth = bounds.width - (leftInset + rightInset)
        }
    } 
}

或者您可以在这里使用CocoaPods https://github.com/levantAJ/PaddingLabel

pod 'PaddingLabel', '1.2'

输入图像描述


7
UILabel的宽度没有改变,导致文本显示为“...”。 - neobie
1
@Tai Le,谢谢分享,我已经在tableview中使用了它,但不知道为什么会截断文本,例如student变成了studen。 - vishwa.deepak
1
@Tim 或许你想使用 min - ielyamani
7
这里需要警告一下。我在UILabel子类中使用了这个解决方案。当在垂直UIStackView中使用这些标签时,出现了一个问题。有时标签似乎会将文本换行,但没有正确调整标签的大小 - 因此字符串末尾会缺少一个或两个单词。目前我还没有解决方案。如果我找到了,我会在这里写出来。我花了几个小时来解决这个问题,最后证明是由这里造成的。 - Darren Black
2
为了使其在这些情况下正常工作,您需要覆盖“setBounds”并将self.preferredMaxLayoutWidth设置为边界的宽度,减去左右插图。 - Arnaud Barisain-Monrose
显示剩余15条评论

147
如果您想继续使用UILabel,而不必创建其子类,请参考Mundi所提供的清晰解决方案
或者,如果您愿意避免将UILabel包装在UIView中,则可以使用UITextView来启用UIEdgeInsets(填充),或者创建UILabel的子类以支持UIEdgeInsets。
只需使用UITextView即可提供插图(Objective-C):
textView.textContainerInset = UIEdgeInsetsMake(10, 0, 10, 0);

如果您要创建子类UILabel,可以通过覆盖 drawTextInRect 方法来实现此方法的示例
(Objective-C)

- (void)drawTextInRect:(CGRect)uiLabelRect {
    UIEdgeInsets myLabelInsets = {10, 0, 10, 0};
    [super drawTextInRect:UIEdgeInsetsInsetRect(uiLabelRect, myLabelInsets)];
}

你还可以为你的子类化UILabel提供上、左、下和右的内边距变量。

示例代码如下:

在 .h 文件中(Objective-C)

float topInset, leftInset,bottomInset, rightInset;

在.m文件中(Objective-C)

- (void)drawTextInRect:(CGRect)uiLabelRect {
    [super drawTextInRect:UIEdgeInsetsInsetRect(uiLabelRect, UIEdgeInsetsMake(topInset,leftInset,bottomInset,rightInset))];
}

根据我看到的,当你对UILabel进行子类化时,似乎必须覆盖intrinscContentSize。

因此,你应该这样覆盖intrinsicContentSize

- (CGSize) intrinsicContentSize {
    CGSize intrinsicSuperViewContentSize = [super intrinsicContentSize] ;
    intrinsicSuperViewContentSize.height += topInset + bottomInset ;
    intrinsicSuperViewContentSize.width += leftInset + rightInset ;
    return intrinsicSuperViewContentSize ;
}

不要逐个编辑您的插图,而是添加以下方法来编辑它们:

- (void) setContentEdgeInsets:(UIEdgeInsets)edgeInsets {
    topInset = edgeInsets.top;
    leftInset = edgeInsets.left;
    rightInset = edgeInsets.right;
    bottomInset = edgeInsets.bottom;
    [self invalidateIntrinsicContentSize] ;
}

它将更新您的UILabel的大小以匹配边缘插图,并覆盖您提到的多行必要性。

在搜索一番后,我找到了这个Gist,其中包含一个IPInsetLabel。如果这些解决方案都不起作用,您可以尝试使用它。

关于此问题有类似的问题(重复)。
有关可用解决方案的完整列表,请参见此答案:UILabel文本边距


抱歉,但我已经使用了以下代码:override func drawTextInRect(rect: CGRect) { var insets: UIEdgeInsets = UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0) super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets)) }但它不起作用,因为结果是相同的,不能动态工作。 - Annachiara
你尝试过使用UITextView而不是UILabel吗?或者你真的需要使用UILabel吗? - nunofmendes
1
使用TextView和一些故事板设置,以及self.textview.textContainerInset = UIEdgeInsetsMake(0, 10, 10, 10); 最终成功了!谢谢! - Annachiara
leftInsetrightInset 增加时,intrinsicContentSize 方法返回的结果将是错误的,因为标签的宽度会变窄,但高度不会增加,因此一部分文本将消失。要解决这个问题,您需要重新计算文本的高度,并将其赋值给 intrinsicSuperViewContentSize.height。我还在这个线程中发布了一个答案。 - Quang Tran
我认为这个答案更好,因为它可以很好地与sizeToFit一起使用。 - Luiz Henrique Guimaraes
显示剩余8条评论

93

Swift 3

import UIKit

class PaddingLabel: UILabel {

   @IBInspectable var topInset: CGFloat = 5.0
   @IBInspectable var bottomInset: CGFloat = 5.0
   @IBInspectable var leftInset: CGFloat = 5.0
   @IBInspectable var rightInset: CGFloat = 5.0

   override func drawText(in rect: CGRect) {
      let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
      super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
   }

   override var intrinsicContentSize: CGSize {
      get {
         var contentSize = super.intrinsicContentSize
         contentSize.height += topInset + bottomInset
         contentSize.width += leftInset + rightInset
         return contentSize
      }
   }
}

只是一个小注释:将此类设置为标签(自定义类)中的标识符检查器,并在属性检查器中使用名为“标签填充”的新属性。此外,5以下的填充是无效的。 - iman kazemayni
4
多行标签时,这种方法并不总是能够正确工作,因为在标签计算其高度时,它假设零填充。 - fhucho

92

您可以通过以下步骤正确完成:

  1. 将文本更改为属性文本

属性文本

  1. 进入带有“…”的下拉列表

在这里输入图片描述

  1. 您将看到一些关于行、段落和文本的填充属性,更改缩进第一行或任何您想要的内容

在这里输入图片描述

  1. 检查结果

在这里输入图片描述


1
在我的故事板中,我可以看到文本更改,但是当我运行应用程序时,文本没有显示更改... T_T..我的标签位于自定义单元格内,是否存在任何问题? - A. Trejo
1
@A.Trejo 也许你的自定义单元格可以在运行时设置标签属性。 - GPY
1
故事板上可能会出现更改,但运行应用程序时没有任何更改。 - Rhenz
5
当您以编程方式设置文本时,这不适用。 - Neeraj Joshi
5
这不是答案。你只能控制第一行的缩进,但不能控制所有方向的填充。 - rommex
显示剩余2条评论

72

只需使用UIButton,它已经内置了。关闭所有额外的按钮功能,您就可以拥有一个可以设置边缘插图的标签。

let button = UIButton()
button.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.setTitle("title", for: .normal)
button.tintColor = .white // this will be the textColor
button.isUserInteractionEnabled = false

6
这是一个很棒的提示!不需要安装任何扩展程序! :-D - Felipe Ferri
5
isUserInteractionEnabled = false设置为false可以方便地禁用它。 - mxcl
1
不错的技巧,最大的优点是它也可以在界面构建器中完成。 - Ely
1
最佳解决方案,无需子类化等。 - MeGaPk
真的是最简单的解决方案。在设置标题后,您可以使用 button.sizeToFit() 自动调整按钮大小,而在视图中嵌入标签时更难实现。 - Martin Braun
显示剩余4条评论

65
只需将 UIView 用作父视图,并使用自动布局为标签定义固定的边距即可。

2
drawTextInRect只适用于单行文本,intrinsicContentSize在水平填充时无效。将UILabel包裹在UIView中是一个好的选择。 - onmyway133
13
如果你正在 IB 中,现在是时候记住菜单 编辑器 -> 嵌入 -> 查看。只需首先选择你的 UILabel :) - Graham Perks
1
这是我看到的最简单的解决方案。只需要确保将标签在视图上居中对齐(水平和垂直),并且更新视图的背景颜色以匹配您的UILabel的背景颜色。 - learner
如果您需要动态更新和调整 UILabel 的大小,请独立创建 UIView(而不是作为超级视图),然后自动布局将根据标签的大小调整 UIView - craft
这个命题很有趣,只需注意在UITesting中标签将位于“其他”元素内部,其次标签和容器之间的约束具有1000优先级,因此在为小屏幕压缩视图时,这可能是需要解决的问题。 - Blazej SLEBODA

59

SWIFT 4

易于使用的解决方案,适用于项目中的所有UILabel子类。

示例:

let label = UILabel()
    label.<Do something>
    label.padding = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 0)

UILabel扩展

import UIKit

extension UILabel {
    private struct AssociatedKeys {
        static var padding = UIEdgeInsets()
    }

    public var padding: UIEdgeInsets? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.padding) as? UIEdgeInsets
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(self, &AssociatedKeys.padding, newValue as UIEdgeInsets?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }

    override open func draw(_ rect: CGRect) {
        if let insets = padding {
            self.drawText(in: rect.inset(by: insets))
        } else {
            self.drawText(in: rect)
        }
    }

    override open var intrinsicContentSize: CGSize {
        guard let text = self.text else { return super.intrinsicContentSize }

        var contentSize = super.intrinsicContentSize
        var textWidth: CGFloat = frame.size.width
        var insetsHeight: CGFloat = 0.0
        var insetsWidth: CGFloat = 0.0

        if let insets = padding {
            insetsWidth += insets.left + insets.right
            insetsHeight += insets.top + insets.bottom
            textWidth -= insetsWidth
        }

        let newSize = text.boundingRect(with: CGSize(width: textWidth, height: CGFloat.greatestFiniteMagnitude),
                                        options: NSStringDrawingOptions.usesLineFragmentOrigin,
                                        attributes: [NSAttributedString.Key.font: self.font], context: nil)

        contentSize.height = ceil(newSize.size.height) + insetsHeight
        contentSize.width = ceil(newSize.size.width) + insetsWidth

        return contentSize
    }
}

3
把英语翻译成中文。只返回翻译的文本:请简要解释您的答案,不要只发布代码。 - lmiguelvargasf
4
您的扩展程序会将0值取消为numberOfLines。 - Antoine Bodart
这很好,但我在处理多行时遇到了问题,即使我添加了2行或将其保留为0行,它始终显示为1行。你知道如何解决吗? - Mago Nicolas Palacios
1
@AntoineBodart,你解决了numberOfLines的问题吗? - Mago Nicolas Palacios
2
不要使用扩展来覆盖方法,这是非常糟糕的做法。 更多信息请参见此处:https://dev59.com/dFoT5IYBdhLWcg3wvRlk#38274660 - Patrick
显示剩余15条评论

33
我们终于找到了一个完整且正确的解决方案,适用于所有情况,包括堆栈视图、动态单元格、动态行数、集合视图、动画填充、每个字符计数和其他情况。

UILabel 的填充,全面解决方案。已更新至2023年。

原来有三件事情必须要做

1. 必须使用新的较小尺寸调用 textRect#forBounds 方法

2. 必须覆盖 drawText 方法并使用新的较小尺寸

3. 如果是动态大小的单元格,则必须调整 intrinsicContentSize

在下面的典型示例中,文本单位位于表格视图、堆叠视图或类似结构中,这使得它具有固定的宽度。 在该示例中,我们希望添加60,20,20,24的填充。

因此,我们取“现有”的 intrinsicContentSize 并实际将高度增加80

再次强调...

你必须“获取”引擎已经计算出的“到目前为止”的高度,并更改该值。

我发现这个过程很混乱,但是这就是它的工作方式。对我来说,苹果应该公开一个名为“初步高度计算”的调用。

其次,我们必须使用textRect#forBounds调用我们新的较小尺寸

因此,在textRect#forBounds中,我们首先使尺寸变小,然后调用super。

警告!您必须在之后而不是之前调用super!

如果您仔细研究本页上的所有尝试和讨论,那么这就是确切的问题。

请注意,一些解决方案“似乎通常有效”。这确实是确切的原因-令人困惑的是,您必须“在之后调用super”,而不是之前。

如果您按错误顺序调用super,则通常可以正常工作,但会在某些特定文本长度下失败

这里是“不正确地首先执行super”的精确视觉示例:

enter image description here

请注意,60、20、20、24的边距是正确的,但是尺寸计算实际上是错误的,因为它是在textRect#forBounds中使用了"super first"模式进行的。
修复后:
现在,textRect#forBounds引擎才知道如何正确地进行计算。

enter image description here

终于完成了!

在这个例子中,UILabel被用在典型的宽度固定的情况下。因此,在intrinsicContentSize中,我们必须“添加”我们想要的整体额外高度。(您不需要以任何方式“添加”宽度,因为它是固定的,这样做是没有意义的。)

然后,在textRect#forBounds中,您可以获取autolayout建议的边界“到目前为止”,您需要减去您的边距,然后再次调用textRect#forBounds引擎,也就是在super中,这将给您一个结果。

最后,在drawText中,您当然会在同样较小的框中进行绘制。

哎呀,终于完成了!

let UIEI = UIEdgeInsets(top: 60, left: 20, bottom: 20, right: 24) // as desired

override var intrinsicContentSize:CGSize {
    numberOfLines = 0       // don't forget!
    var s = super.intrinsicContentSize
    s.height = s.height + UIEI.top + UIEI.bottom
    s.width = s.width + UIEI.left + UIEI.right
    return s
}

override func drawText(in rect:CGRect) {
    let r = rect.inset(by: UIEI)
    super.drawText(in: r)
}

override func textRect(forBounds bounds:CGRect,
                           limitedToNumberOfLines n:Int) -> CGRect {
    let b = bounds
    let tr = b.inset(by: UIEI)
    let ctr = super.textRect(forBounds: tr, limitedToNumberOfLines: 0)
    // that line of code MUST be LAST in this function, NOT first
    return ctr
}

再一次强调。请注意,本QA和其他“几乎正确”的答案都存在上图中第一个问题的问题 - "super位置错误"。你必须在intrinsicContentSize中强制增大尺寸,然后在textRect# forBounds中先收缩第一个建议边界,然后再调用super方法。

总结:您必须在textRect#forBounds中“最后调用super”

这就是诀窍。

请注意,您不需要并且不应该额外调用invalidate、sizeThatFits、needsLayout或任何其他强制调用。正确的解决方案应该在正常的自动布局绘制周期中正常工作。


1
很好的解决方案。您能否详细说明为什么要覆盖 numberOfLines - DrMickeyLauer
2
谢谢!确实花了很多时间才达到这个效果 :/ (在iOS中,“numberOfLines zero”表示文本换行..我只是在示例代码中设置了它,因为有些人可能会忘记在Storyboard上设置它!) - Fattie
3
2021年了,很遗憾你仍然不能直接在 UILabel 上设置属性以自动完成这个操作。 - JaredH
我尝试过这种方法,但是之后我无法动态设置字体大小?尽管我将其设置为更高的值(如24),它似乎会缩小到10。当我禁用此代码时,大小是正确的。我应该补充说,我在一个带有单元格和标签的表视图中,我认为它正在调整其内在大小,通过填充和行间距。 - Chucky
1
不幸的是,这会覆盖我的 numberOfLines 设置。我只需要显示2行(之后加上...),但它会将其设置为0并显示所有文本。如果我删除numberOfLines设置,则会显示正确的行数,但间距会出现问题。 - CyberMew
显示剩余7条评论

22

没有故事板:

class PaddingLabel: UILabel {

    var topInset: CGFloat
    var bottomInset: CGFloat
    var leftInset: CGFloat
    var rightInset: CGFloat

    required init(withInsets top: CGFloat, _ bottom: CGFloat,_ left: CGFloat,_ right: CGFloat) {
        self.topInset = top
        self.bottomInset = bottom
        self.leftInset = left
        self.rightInset = right
        super.init(frame: CGRect.zero)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func drawText(in rect: CGRect) {
        let insets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
        super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
    }

    override var intrinsicContentSize: CGSize {
        get {
            var contentSize = super.intrinsicContentSize
            contentSize.height += topInset + bottomInset
            contentSize.width += leftInset + rightInset
            return contentSize
        }
    }
}

使用方法:

let label = PaddingLabel(8, 8, 16, 16)
label.font = .boldSystemFont(ofSize: 16)
label.text = "Hello World"
label.backgroundColor = .black
label.textColor = .white
label.textAlignment = .center
label.layer.cornerRadius = 8
label.clipsToBounds = true
label.sizeToFit()

view.addSubview(label)

结果:


可以使用这个方法,但您知道怎么让它接受多行吗?只需将“init”更改为“PaddingLabel(withInsets:8、8、16、16)”。 - Camilo Ortegón

20

Swift 4+

class EdgeInsetLabel: UILabel {
    var textInsets = UIEdgeInsets.zero {
        didSet { invalidateIntrinsicContentSize() }
    }

    override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
        let invertedInsets = UIEdgeInsets(top: -textInsets.top,
                                          left: -textInsets.left,
                                          bottom: -textInsets.bottom,
                                          right: -textInsets.right)
        return textRect.inset(by: invertedInsets)
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: textInsets))
    }
}

用法:

let label = EdgeInsetLabel()
label.textInsets = UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6)

等等 - 实际上我发现在某些情况下存在问题。以前这是最正确的答案。我已经在下面放入了正确的答案。 - Fattie
我在我的回答中包含了一张图片,展示了问题。 - Fattie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接