UILabel文本边距

333

我想设置UILabel的左缩进/边距,但找不到相应的方法。标签已经设置了背景,所以只改变其起点位置并不能达到效果。最好能够在文本左侧插入大约10px的空白。


一旦你创建了子类,对于插图来说就很简单了,https://dev59.com/LF4c5IYBdhLWcg3wl7Xm#43197662 - Fattie
另一种方法可能是将标签嵌入水平堆栈视图中,并在其左侧/右侧添加任意宽度的uiview。 - ugur
我们终于终于完全解决了这个问题。在textRect中,您必须最后调用super。https://dev59.com/514c5IYBdhLWcg3w3dV_#58876988 - Fattie
38个回答

470

我通过子类化 UILabel 并重写 drawTextInRect: 方法来解决了这个问题:

- (void)drawTextInRect:(CGRect)rect {
    UIEdgeInsets insets = {0, 5, 0, 5};
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}

Swift 3.1:

override func drawText(in rect: CGRect) {
    let insets = UIEdgeInsets.init(top: 0, left: 5, bottom: 0, right: 5)
    super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}

Swift 4.2.1:

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

你可能已经了解到,这是对tc.的答案的改编。它有两个优点:

  1. 不需要发送 sizeToFit 消息来触发它。
  2. 它不会改变标签的框架大小 - 如果标签具有背景并且您不希望其缩小,则非常方便。

2
这里的 "return" 的作用是什么? - Radu Simionescu
15
你可能想要查看这个答案,它适当地处理了sizeToFit和自动布局。这里是链接。 - Nikolai Ruhe
2
如果你想要在输入文本时获得插入效果,你还需要继承editingRectForBounds: - fabb
21
你还应该覆盖intrinsicContentSize以使其与自动布局一起使用。我已经将它添加到@Brody答案中的示例代码中。 - progrmr
13
我不明白这个答案怎么会有那么多的赞?! 这种方法很可能会引起许多关于lineBreakingMode和省略号位置的问题。计算出来的字符串需要的大小并不等于绘制时给定的大小,难道我错了吗? - Patrik
显示剩余11条评论

153

对于多行文本,可以使用NSAttributedString来设置左右边距。

NSMutableParagraphStyle *style =  [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.alignment = NSTextAlignmentJustified;
style.firstLineHeadIndent = 10.0f;
style.headIndent = 10.0f;
style.tailIndent = -10.0f;   

NSAttributedString *attrText = [[NSAttributedString alloc] initWithString:title attributes:@{ NSParagraphStyleAttributeName : style}];  

UILabel * label = [[UILabel alloc] initWithFrame:someFrame];
label.numberOfLines = 0;
label.attributedText = attrText;

这里是针对 Swift 5 调整后的上述示例:

extension UILabel {
    func setMargins(margin: CGFloat = 10) {
        if let textString = self.text {
            var paragraphStyle = NSMutableParagraphStyle()
            paragraphStyle.firstLineHeadIndent = margin
            paragraphStyle.headIndent = margin
            paragraphStyle.tailIndent = -margin
            let attributedString = NSMutableAttributedString(string: textString)
            attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributedString.length))
            attributedText = attributedString
        }
    }
}

4
style.tailIndent 应设置为 -10.0f。 - cetcet
4
无法在 IB 中添加 tailIndent = -10,仅允许使用正值:/ - HamzaGhazouani
2
需要一个上下解决方案。 - Alyoshak
非常感谢。 - Rodrigo Jardim
谢谢,但如果您已经在标签上使用attributedText,则该扩展程序无法工作。不过,它包含了修改现有属性文本所需的代码行。 - clearlight

96

添加内边距到UILabel的最佳方法是继承UILabel并添加一个edgeInsets属性。然后设置所需的插入并相应地绘制标签。

OSLabel.h

#import <UIKit/UIKit.h>

@interface OSLabel : UILabel

@property (nonatomic, assign) UIEdgeInsets edgeInsets;

@end

OSLabel.m

#import "OSLabel.h"

@implementation OSLabel

- (id)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.edgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
    }
    return self;
}

- (void)drawTextInRect:(CGRect)rect {
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, self.edgeInsets)];
}

- (CGSize)intrinsicContentSize
{
    CGSize size = [super intrinsicContentSize];
    size.width  += self.edgeInsets.left + self.edgeInsets.right;
    size.height += self.edgeInsets.top + self.edgeInsets.bottom;
    return size;
}

@end

6
您可以使用TTTAttributedLabel(https://github.com/mattt/TTTAttributedLabel)来实现此功能。 - Ajith
5
这个解决方案存在问题 - 如果标签文本足够长并且插图足够大,标签文本的最后一行将被切断。我刚试过最新的iOS 7。 - BVB
5
您还应该重写intrinsicContentSize方法,将内边距考虑在内以增加固有大小,以便自动布局正常工作。 - progrmr
3
如果我将 numberOfLines 设置为 0,它会截断我的文本 :( - Ferran Maylinch
2
@AsifBilal 你还需要重写 textRectForBounds: 方法。 - Kostub Deshmukh
显示剩余2条评论

76

对于这种简单的情况,子类化有点繁琐。另一种选择是将没有设置背景的UILabel添加到设置了背景的UIView中。将标签的x设置为10,并使外部视图的大小比标签宽20个像素。


8
咳嗽声* 外部视图需要比标签宽20个点,每侧10个点。 - Tommy
2
虽然子类化可以创建可重用的组件,但这种方法确实为我节省了时间。谢谢,Peter。 - SagarU
请记住,UILabel已经是UIView的子类,因此这样做有点多余,但它确实实现了目标。 - Cyril
在XCode中,我们经常寻找复杂的答案。而事实上,最简单、最有效的方法就是分层视图,可以解决更多的问题。事实上,早些时候,NS(NextStep)就是为了这个目的而设计视图的。随着约束条件的出现,我们中的许多人已经忘记了只使用视图可以多么简单(和快速)。 - PDG

50

使用 Swift 3,您可以通过创建UILabel的子类来实现所需的效果。在此子类中,您将需要添加一个具有所需插入值的UIEdgeInsets属性,并重写drawText(in:)方法、intrinsicContentSize属性(用于自动布局代码)和/或sizeThatFits(_ :) 方法(用于 Springs&amp; Struts 代码)。

import UIKit

class PaddingLabel: UILabel {

    let padding: UIEdgeInsets

    // Create a new PaddingLabel instance programamtically with the desired insets
    required init(padding: UIEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)) {
        self.padding = padding
        super.init(frame: CGRect.zero)
    }

    // Create a new PaddingLabel instance programamtically with default insets
    override init(frame: CGRect) {
        padding = UIEdgeInsets.zero // set desired insets value according to your needs
        super.init(frame: frame)
    }

    // Create a new PaddingLabel instance from Storyboard with default insets
    required init?(coder aDecoder: NSCoder) {
        padding = UIEdgeInsets.zero // set desired insets value according to your needs
        super.init(coder: aDecoder)
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: UIEdgeInsetsInsetRect(rect, padding))
    }

    // Override `intrinsicContentSize` property for Auto layout code
    override var intrinsicContentSize: CGSize {
        let superContentSize = super.intrinsicContentSize
        let width = superContentSize.width + padding.left + padding.right
        let height = superContentSize.height + padding.top + padding.bottom
        return CGSize(width: width, height: height)
    }

    // Override `sizeThatFits(_:)` method for Springs & Struts code
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        let superSizeThatFits = super.sizeThatFits(size)
        let width = superSizeThatFits.width + padding.left + padding.right
        let heigth = superSizeThatFits.height + padding.top + padding.bottom
        return CGSize(width: width, height: heigth)
    }

}
以下示例展示如何在UIViewController中使用PaddingLabel实例:
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var storyboardAutoLayoutLabel: PaddingLabel!
    let autoLayoutLabel = PaddingLabel(padding: UIEdgeInsets(top: 20, left: 40, bottom: 20, right: 40))
    let springsAndStructsLabel = PaddingLabel(frame: CGRect.zero)
    var textToDisplay = "Lorem ipsum dolor sit er elit lamet."

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set autoLayoutLabel
        autoLayoutLabel.text = textToDisplay
        autoLayoutLabel.backgroundColor = .red
        autoLayoutLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(autoLayoutLabel)
        autoLayoutLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
        autoLayoutLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true

        // Set springsAndStructsLabel
        springsAndStructsLabel.text = textToDisplay
        springsAndStructsLabel.backgroundColor = .green
        view.addSubview(springsAndStructsLabel)
        springsAndStructsLabel.frame.origin = CGPoint(x: 30, y: 90)
        springsAndStructsLabel.sizeToFit()

        // Set storyboardAutoLayoutLabel
        storyboardAutoLayoutLabel.text = textToDisplay
        storyboardAutoLayoutLabel.backgroundColor = .blue
    }

    // Link this IBAction to a UIButton or a UIBarButtonItem in Storyboard
    @IBAction func updateLabelText(_ sender: Any) {
        textToDisplay = textToDisplay == "Lorem ipsum dolor sit er elit lamet." ? "Lorem ipsum." : "Lorem ipsum dolor sit er elit lamet."

        // autoLayoutLabel
        autoLayoutLabel.text = textToDisplay

        // springsAndStructsLabel
        springsAndStructsLabel.text = textToDisplay
        springsAndStructsLabel.sizeToFit()

        // storyboardAutoLayoutLabel
        storyboardAutoLayoutLabel.text = textToDisplay
    }

}

实现缺少对textRect(forBounds:limitedToNumberOfLines:)的重写,需要调用带有边界设置为UIEdgeInsetsInsetRect(bounds, padding)的超级方法,否则文本可能会被截断 - 当视图大小受限制时,大小计算不正确(因此未使用intrinsicContentSize)。 - Marián Černý
你能否添加属性,以便我们可以在Storyboard中使用它而不是通过编程方式使用吗? - user924

48

Recycled Steel的答案的Swift版本 + intrinsizeContentSize()

它支持在Interface Builder中设置内边距,同时支持其他视图对象采用更传统的方式进行内边距设置,即通过编程方式设置内边距:

label.insets = UIEdgeInsetsMake(0, 0, 5, 0)

如果有任何错误,请告诉我。

Swift 5

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

var insets: UIEdgeInsets {
    get {
        return UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
    }
    set {
        topInset = newValue.top
        leftInset = newValue.left
        bottomInset = newValue.bottom
        rightInset = newValue.right
    }
}

override func drawText(in rect: CGRect) {
    super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
}

override func sizeThatFits(_ size: CGSize) -> CGSize {
    var adjSize = super.sizeThatFits(size)
    adjSize.width += leftInset + rightInset
    adjSize.height += topInset + bottomInset
    
    return adjSize
}

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

Swift 4.2

@IBDesignable class InsetLabel: UILabel {
    @IBInspectable var topInset: CGFloat = 0.0
    @IBInspectable var leftInset: CGFloat = 0.0
    @IBInspectable var bottomInset: CGFloat = 0.0
    @IBInspectable var rightInset: CGFloat = 0.0
    
    var insets: UIEdgeInsets {
        get {
            return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
        }
        set {
            topInset = newValue.top
            leftInset = newValue.left
            bottomInset = newValue.bottom
            rightInset = newValue.right
        }
    }
    
    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: insets))
    }
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var adjSize = super.sizeThatFits(size)
        adjSize.width += leftInset + rightInset
        adjSize.height += topInset + bottomInset
        
        return adjSize
    }
    
    override var intrinsicContentSize: CGSize {
        var contentSize = super.intrinsicContentSize
        contentSize.width += leftInset + rightInset
        contentSize.height += topInset + bottomInset
        
        return contentSize
    }
}

Swift 3

@IBDesignable class InsetLabel: UILabel {
    @IBInspectable var topInset: CGFloat = 0.0
    @IBInspectable var leftInset: CGFloat = 0.0
    @IBInspectable var bottomInset: CGFloat = 0.0
    @IBInspectable var rightInset: CGFloat = 0.0
    
    var insets: UIEdgeInsets {
        get {
            return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
        }
        set {
            topInset = newValue.top
            leftInset = newValue.left
            bottomInset = newValue.bottom
            rightInset = newValue.right
        }
    }
    
    override func drawText(in rect: CGRect) {
        super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
    }
    
    override func sizeThatFits(_ size: CGSize) -> CGSize {
        var adjSize = super.sizeThatFits(size)
        adjSize.width += leftInset + rightInset
        adjSize.height += topInset + bottomInset
        
        return adjSize
    }
    
    override var intrinsicContentSize: CGSize {
        var contentSize = super.intrinsicContentSize
        contentSize.width += leftInset + rightInset
        contentSize.height += topInset + bottomInset
        
        return contentSize
    }
}

Swift 2.2

@IBDesignable class InsetLabel: UILabel {
    @IBInspectable var topInset: CGFloat = 0.0
    @IBInspectable var leftInset: CGFloat = 0.0
    @IBInspectable var bottomInset: CGFloat = 0.0
    @IBInspectable var rightInset: CGFloat = 0.0
    
    var insets: UIEdgeInsets {
        get {
            return UIEdgeInsetsMake(topInset, leftInset, bottomInset, rightInset)
        }
        set {
            topInset = newValue.top
            leftInset = newValue.left
            bottomInset = newValue.bottom
            rightInset = newValue.right
        }
    }
        
    override func drawTextInRect(rect: CGRect) {
        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
    }
        
    override func sizeThatFits(size: CGSize) -> CGSize {
        var adjSize = super.sizeThatFits(size)
        adjSize.width += leftInset + rightInset
        adjSize.height += topInset + bottomInset
        
        return adjSize
    }
    
    override func intrinsicContentSize() -> CGSize {
        var contentSize = super.intrinsicContentSize()
        contentSize.width += leftInset + rightInset
        contentSize.height += topInset + bottomInset
        
        return contentSize
    }
}

4
我建议在insets的setter方法中添加invalidateIntrinsicContentSize()setNeedsDisplay() - Archagon
它对于Swift 4也像魔法一样运行良好!谢谢@funct7。 - R. Mohan
label.insets = UIEdgeInsetsMake(0, 0, 5, 0) 不是 label.inset = UIEdgeInsetsMake(0, 0, 5, 0)。 - coders

42

编辑:这篇文章很旧了,上面有更好的解决方案。

我最终只是在文本中添加了一些空格:

self.titleLabel.text = [NSString stringWithFormat:@"    %@", self.titleLabel.text];

丑陋但有效,无需子类化。

您也可以尝试使用"\t"。对于通用解决方案,请参阅已接受的答案。


3
你认为这对多行标签有用吗? - Ali
5
间距取决于字体。我发现这是一种不太优雅的技巧。 - meaning-matters
在单行上,这很容易并且对我有效。 - zephyr
很遗憾不得不给这个点踩,因为这只是一个能够应付现有情况的技巧。它和返回固定值的hacky函数没有什么区别,这通常无法通过SO审核。 - bitwit
@bitwit 嗅探测试? - Yariv Nissim
显示剩余4条评论

29

你还可以通过使用自定义框架初始化UILabel来解决此问题。

    CGRect initialFrame = CGRectMake(0, 0, 100, 100);
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0, 10, 0, 0);
    CGRect paddedFrame = UIEdgeInsetsInsetRect(initialFrame, contentInsets);

    self.label = [[UILabel alloc] initWithFrame:paddedFrame];

致敬CGRect Tricks


14
如果标签有背景,那么这个东西就没有用了。 - Radu Simionescu
在自动布局中,初始帧基本上会被忽略。 - fishinear

19

还有一个@IBDesignable,使其能够与Interface Builder一起使用

Swift 4

//
//  PaddedLabel.swift
//  TrainCentric
//
//  Created by Arsonik
//  https://dev59.com/qHA75IYBdhLWcg3wGU-I#33244365
//

import UIKit

@IBDesignable
class PaddedLabel: UILabel {

    @IBInspectable var inset:CGSize = CGSize(width: 0, height: 0)

    var padding: UIEdgeInsets {
        var hasText:Bool = false
        if let t = self.text?.count, t > 0 {
            hasText = true
        }
        else if let t = attributedText?.length, t > 0 {
            hasText = true
        }

        return hasText ? UIEdgeInsets(top: inset.height, left: inset.width, bottom: inset.height, right: inset.width) : UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }

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

    override var intrinsicContentSize: CGSize {
        let superContentSize = super.intrinsicContentSize
        let p = padding
        let width = superContentSize.width + p.left + p.right
        let heigth = superContentSize.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        let superSizeThatFits = super.sizeThatFits(size)
        let p = padding
        let width = superSizeThatFits.width + p.left + p.right
        let heigth = superSizeThatFits.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }
}

Swift 2

@IBDesignable
class PaddedLabel: UILabel {

    @IBInspectable var inset:CGSize = CGSize(width: 0, height: 0)

    var padding: UIEdgeInsets {
        var hasText:Bool = false
        if let t = text?.length where t > 0 {
            hasText = true
        }
        else if let t = attributedText?.length where t > 0 {
            hasText = true
        }

        return hasText ? UIEdgeInsets(top: inset.height, left: inset.width, bottom: inset.height, right: inset.width) : UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    }

    override func drawTextInRect(rect: CGRect) {
        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, padding))
    }

    override func intrinsicContentSize() -> CGSize {
        let superContentSize = super.intrinsicContentSize()
        let p = padding
        let width = superContentSize.width + p.left + p.right
        let heigth = superContentSize.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }

    override func sizeThatFits(size: CGSize) -> CGSize {
        let superSizeThatFits = super.sizeThatFits(size)
        let p = padding
        let width = superSizeThatFits.width + p.left + p.right
        let heigth = superSizeThatFits.height + p.top + p.bottom
        return CGSize(width: width, height: heigth)
    }
}

18

对于使用统一 API 的 Xamarin 用户:

class UIMarginLabel : UILabel
{
    public UIMarginLabel()
    {
    }

    public UIMarginLabel( CGRect frame ) : base( frame )
    {
    }

    public UIEdgeInsets Insets { get; set; }

    public override void DrawText( CGRect rect )
    {
        base.DrawText( Insets.InsetRect( rect ) );
    }
}

对于那些使用原始的MonoTouch API的人:

public class UIMarginLabel : UILabel
{
    public UIEdgeInsets Insets { get; set; }

    public UIMarginLabel() : base()
    {
        Insets = new UIEdgeInsets(0, 0, 0, 0);
    }
    public UIMarginLabel(RectangleF frame) : base(frame)
    {
        Insets = new UIEdgeInsets(0, 0, 0, 0);
    }

    public override void DrawText(RectangleF frame)
    {
        base.DrawText(new RectangleF(
            frame.X + Insets.Left,
            frame.Y + Insets.Top,
            frame.Width - Insets.Left - Insets.Right,
            frame.Height - Insets.Top - Insets.Bottom));
    }
}

在统一API示例中,ctor中使用的RectangleF应该是CGRect才能正常工作。 - vividos

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