Изменение размера UILabel для размещения вставок

Я строю экран для сканирования штрих-кодов, и мне нужно поставить полупрозрачный экран позади некоторых UILabels улучшить видимость на светлом фоне.

Вот как выглядит экран сейчас:

Я устанавливаю цвет фона на UILabel чтобы получить полупрозрачные коробки. Я также создал обычай UILabel подкласс, чтобы позволить мне установить некоторые отступы между краем UILabel и текст с использованием этого подхода.

Как вы можете видеть на экране выше, UILabel неправильно изменяет размер, чтобы учитывать отступы. "Заполнение" просто смещает текст без изменения ширины метки, что приводит к усечению текста.

Обе эти метки будут содержать текст произвольной длины, и мне действительно нужно UILabel динамически изменять размер.

Какие UILabel метод я могу переопределить, чтобы увеличить ширину метки и фактор заполнения?

6 ответов

Решение

Вот класс меток, который правильно рассчитывает размеры. Размещенный код находится в Swift 3, но вы также можете скачать версии Swift 2 или Objective-C.

Как это работает?

Вычисляя правильный textRect все sizeToFit и автоматическая разметка работает как положено. Хитрость заключается в том, чтобы сначала вычесть вставки, затем вычислить исходные границы меток и, наконец, снова добавить вставки.

Код

class NRLabel : UILabel {

    var textInsets = UIEdgeInsets.zero {
        didSet { invalidateIntrinsicContentSize() }
    }

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

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

Необязательно: поддержка Interface Builder

Если вы хотите настроить текстовые вставки в раскадровках, вы можете использовать следующее расширение, чтобы включить поддержку Interface Builder:

@IBDesignable
extension NRLabel {

    // currently UIEdgeInsets is no supported IBDesignable type,
    // so we have to fan it out here:
    @IBInspectable
    var leftTextInset: CGFloat {
        set { textInsets.left = newValue }
        get { return textInsets.left }
    }

    // Same for the right, top and bottom edges.
}

Теперь вы можете удобно настроить вкладыши в IB, а затем просто нажать ⌘=, чтобы отрегулировать размер этикетки по размеру.

Отказ от ответственности:

Весь код находится в свободном доступе. Делай как пожелаешь.

Вот Swift-версия подкласса UILabel (такая же, как ответ @Nikolai), который создает дополнительный отступ вокруг текста UILabel:

class EdgeInsetLabel : UILabel {
    var edgeInsets:UIEdgeInsets = UIEdgeInsetsZero

    override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        var rect = super.textRectForBounds(UIEdgeInsetsInsetRect(bounds, edgeInsets), limitedToNumberOfLines: numberOfLines)

        rect.origin.x -= edgeInsets.left
        rect.origin.y -= edgeInsets.top
        rect.size.width  += (edgeInsets.left + edgeInsets.right);
        rect.size.height += (edgeInsets.top + edgeInsets.bottom);

        return rect
    }

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

Свифт 5 версия Николая Рухе ответит:

extension UIEdgeInsets {
   func apply(_ rect: CGRect) -> CGRect {
      return rect.inset(by: self)
   }
}

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

  override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
    let insetRect = bounds.inset(by: textInsets)
    let textRect = super.textRect(forBounds: insetRect, 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))
  }}

Вот версия C# (полезная для Xamarin), основанная на коде Николая:

public class UIEdgeableLabel : UILabel
{
    public UIEdgeableLabel() : base() { }
    public UIEdgeableLabel(NSCoder coder) : base(coder) { }
    public UIEdgeableLabel(CGRect frame) : base(frame) { }
    protected UIEdgeableLabel(NSObjectFlag t) : base(t) { }

    private UIEdgeInsets _edgeInset = UIEdgeInsets.Zero;
    public UIEdgeInsets EdgeInsets
    {
        get { return _edgeInset; }
        set
        {
            _edgeInset = value;
            this.InvalidateIntrinsicContentSize();
        }
    }

    public override CGRect TextRectForBounds(CGRect bounds, nint numberOfLines)
    {
        var rect = base.TextRectForBounds(EdgeInsets.InsetRect(bounds), numberOfLines);
        return new CGRect(x: rect.X - EdgeInsets.Left,
                          y: rect.Y - EdgeInsets.Top,
                          width: rect.Width + EdgeInsets.Left + EdgeInsets.Right,
                          height: rect.Height + EdgeInsets.Top + EdgeInsets.Bottom);
    }

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

В дополнение к ответу Николая Рюэ, вам нужно аннулировать внутренний размер контента для автоматического размещения, чтобы правильно пересчитать изменения размера. Вы заметите эту проблему, если вы измените edgeInsets в течение жизненного цикла приложения:

class NRLabel: UILabel {

    var edgeInsets = UIEdgeInsetsZero {
        didSet {
            self.invalidateIntrinsicContentSize()
        }
    }

    ...
}

Свифт 5. Вы можете создать собственныйUILabelучебный класс.
Я добавил 22 отступа в левую часть содержимого. когдаUILabel просит intrinsicContentSizeвернитесь, добавив размер отступа, который вы добавили, я добавил 22 и вернул индивидуальный размер. Вот и все.

// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation. 
override func draw(_ rect: CGRect) {
        // Drawing code
        let insets = UIEdgeInsets(top: 0, left: 22, bottom: 0, right: 0)
        super.drawText(in: rect.inset(by: insets))
        self.layoutSubviews()
}

// This will return custom size with flexible content size. Mainly it can be used in Chat.
override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize
        size.width = 22 + size.width
        return size
}

Вот быстрый, хакерский способ сделать это, чтобы вы могли понять быстрее. Он не такой надежный, как у Николая, но он выполняет свою работу. Я сделал это, когда пытался разместить свой текст в UILabel в UITableViewCell:

  1. Установите ограничение ширины для UILabel
  2. Подключите ограничение через IBOutlet к вашему коду, либо VC (пользовательский класс ячейки, если вы делаете расширяющуюся ячейку табличного представления)
  3. Создайте переменную для фактического размера текста, затем добавьте вставки + размер ширины к ограничению и обновите представление:

let messageTextSize: CGSize = (messageText as NSString).sizeWithAttributes([ NSFontAttributeName: UIFont.systemFontOfSize(14.0)]) cell.widthConstraint.constant = messageTextSize.width + myInsetsOrWhatever

Я еще не провёл тщательного тестирования, возможно, вам придется поиграться с точными значениями CGFloat, которые вы добавляете. Я обнаружил, что правильный размер не совсем ширина плюс вставки; это немного больше, чем это. Это гарантирует, что ширина UILabel всегда будет по крайней мере размером текста или больше.

Вот пример того, что я использовал для простого отступа в 10 единиц слева и справа от метки с закругленными углами. Просто установите текст метки так, чтобы он центрировался, и сделайте его классом IndentedLabel, а остальное позаботится о себе. Чтобы изменить отступы, просто увеличьте или уменьшите rect.size.width += (x)

class IndentedLabel: UILabel {

    var edgeInsets:UIEdgeInsets = UIEdgeInsetsZero

    override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        var rect = super.textRectForBounds(UIEdgeInsetsInsetRect(bounds, edgeInsets), limitedToNumberOfLines: numberOfLines)

        rect.size.width  += 20;

        return rect
    }

    override func drawTextInRect(rect: CGRect) {
        self.clipsToBounds = true
        self.layer.cornerRadius = 3
        super.drawTextInRect(UIEdgeInsetsInsetRect(rect, edgeInsets))
    }
}

Добавление к ответу @Pataphysicien выше для форм Xamarin, вот как вы используете UIEdgeableLabel:

[assembly: ExportRenderer(typeof(PaddedLabel), typeof(PaddedLabelRenderer))]
namespace MyProj.iOS.Renderers
{
    public class PaddedLabelRenderer : LabelRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
        {
            if (Control == null)
            {
                var label = new UIEdgeableLabel();
                label.EdgeInsets = new UIEdgeInsets(2, 2, 2, 2); //padding
                SetNativeControl(label);
            }

            base.OnElementChanged(e);
        }
    }
}
Другие вопросы по тегам