Ajout d'espace / de remplissage à un UILabel

187

J'ai un UILabelendroit où je veux ajouter de l'espace en haut et en bas. Avec une hauteur minimale dans contrainte, je l'ai modifié pour:

entrez la description de l'image ici

EDIT: Pour ce faire, j'ai utilisé:

  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))

    } 

Mais je dois trouver une méthode différente car si j'écris plus de deux lignes, le problème est le même:

entrez la description de l'image ici

Annachiara
la source
1
Copie
gblazex
Nous avons finalement trouvé exactement comment faire cela correctement, dans tous les cas dynamiques, comme un remplacement parfait pour UILabel sans avoir besoin de remodeler ou de tout autre problème. PHEW. stackoverflow.com/a/58876988/294884
Fattie

Réponses:

122

Si vous souhaitez vous en tenir à UILabel, sans le sous-classer, Mundi vous a donné une solution claire.

Si vous souhaitez également éviter d'encapsuler l'UILabel avec un UIView, vous pouvez utiliser UITextView pour activer l'utilisation de UIEdgeInsets (remplissage) ou de la sous-classe UILabel pour prendre en charge UIEdgeInsets.

L'utilisation d'un UITextView aurait seulement besoin de fournir les inserts (OBJ-C):

textView.textContainerInset = UIEdgeInsetsMake(10, 0, 10, 0);

Alternative, si vous sous- classez UILabel , un exemple de cette approche remplacerait la méthode drawTextInRect
(OBJ-C)

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

Vous pouvez en outre fournir à votre nouveau UILabel sous-classé avec des variables d'encart pour TOP, LEFT, BOTTOM et RIGHT.

Un exemple de code pourrait être:

Dans .h (OBJ-C)

float topInset, leftInset,bottomInset, rightInset;

En .m (OBJ-C)

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

EDIT # 1:

D'après ce que j'ai vu, il semble que vous deviez remplacer intrinsicContentSize du UILabel lors du sous-classement.

Vous devez donc remplacer intrinsicContentSize comme:

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

Et ajoutez la méthode suivante pour modifier vos inserts, au lieu de les modifier individuellement:

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

Il mettra à jour la taille de votre UILabel pour correspondre aux inserts de bord et couvrir la nécessité multiligne dont vous avez parlé.

Modifier # 2

Après avoir cherché un peu, j'ai trouvé ce Gist avec un IPInsetLabel. Si aucune de ces solutions ne fonctionne, vous pouvez l'essayer.

Modifier # 3

Il y avait une question similaire (duplicata) à ce sujet.
Pour une liste complète des solutions disponibles, consultez cette réponse: marge de texte UILabel

nunofmendes
la source
Désolé mais j'ai déjà utilisé: `override func drawTextInRect (rect: CGRect) {var insets: UIEdgeInsets = UIEdgeInsets (haut: 0.0, gauche: 10.0, bas: 0.0, droite: 10.0) super.drawTextInRect (UIEdgeInsetsInsetRect (rect, insets) ))} `cela ne fonctionne pas parce que le résultat est le même, ne fonctionne pas dynamiquement ..
Annachiara
Avez-vous essayé avec un UITextView au lieu d'un UILabel? Ou avez-vous vraiment besoin d'utiliser un UILabel?
nunofmendes
@Annachiara vérifie la modification que j'ai faite. Voyez si ça marche.
nunofmendes
D'accord. Cela a-t-il fonctionné la vue de texte? Désolé de ne pas avoir écrit en Swift mais je suis toujours en mode Obj-C. Mon objectif avec ce code était de vous aider à parvenir à une conclusion. J'espère que c'est le cas.
nunofmendes
1
Utilisation de TextView et de certains paramètres de storyboard et self.textview.textContainerInset = UIEdgeInsetsMake (0, 10, 10, 10); Ça marche enfin! Merci !
Annachiara
208

J'ai essayé avec Swift 4.2 , j'espère que cela fonctionnera pour vous!

@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)
        }
    } 
}

Ou vous pouvez utiliser CocoaPods ici https://github.com/levantAJ/PaddingLabel

pod 'PaddingLabel', '1.2'

entrez la description de l'image ici

Tai Le
la source
6
La largeur de l'étiquette ne change pas, ce qui fait que le texte devient "..."
neobie
1
@Tai Le, merci pour le partage, je l'ai utilisé dans tableview, je ne sais pas pourquoi il coupe le texte, par exemple. étudiant devient studen,
vishwa.deepak
1
@Tim peut-être que vous avez voulu utilisermin
ielyamani
4
Un mot d'avertissement ici. J'ai utilisé cette solution dans une sous-classe UILabel. Lors de l'utilisation de ces étiquettes en mode multiligne, dans un UIStackView vertical, il y a un problème. Parfois, l'étiquette semble envelopper le texte sans dimensionner correctement l'étiquette - donc un mot ou 2 finit par manquer à la fin de la chaîne. Je n'ai pas de solution pour le moment. Je vais l'écrire ici si j'en fais un. J'ai passé des heures à me pencher sur ce problème, avant de prouver qu'il était là.
Darren Black
1
Pour que cela fonctionne dans ces situations, vous devez remplacer "setBounds" et définir self.preferredMaxLayoutWidth sur la largeur des limites, moins vos encarts gauche et droit
Arnaud Barisain-Monrose
82

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
      }
   }
}
zombi
la source
juste un petit commentaire: définissez cette classe sur label dans l'inspecteur d'identité (classe personnalisée) et utilisez un nouvel attribut dans l'inspecteur d'attributs nommé label padding. également en dessous de 5 rembourrage est sans effet
iman kazemayni
3
Cela ne fonctionne pas toujours correctement avec les étiquettes multilignes, car lorsque l'étiquette calcule sa hauteur, elle suppose un remplissage nul.
fhucho
76

Vous pouvez le faire correctement depuis IB:

  1. changer le texte en attribué

texte attribué

  1. aller à la liste déroulante avec "..."

entrez la description de l'image ici

  1. vous verrez quelques propriétés de remplissage pour les lignes, les paragraphes et le texte changer le retrait de la première ligne ou tout ce que vous voulez

entrez la description de l'image ici

  1. vérifier le résultat

entrez la description de l'image ici

Pierre-Yves Guillemet
la source
1
Dans mon storyboard, je peux voir le texte changer, mais lorsque j'exécute l'application. Le texte ne montre pas le changement ... T_T .. mon étiquette est à l'intérieur d'une cellule personnalisée, y a-t-il un problème?
A. Trejo
1
@ A.Trejo Peut-être votre cellule personnalisée définit la propriété label au moment de l'exécution.
Pierre-Yves Guillemet
1
Les modifications peuvent apparaître sur le storyboard, mais lorsque vous exécutez l'application, il n'y a pas de modifications.
Rhenz
4
Cela ne s'applique pas lorsque vous définissez le texte par programme.
Nij
1
Ce n'est pas la réponse. Vous ne contrôlez que le retrait de la première ligne, mais pas le remplissage dans toutes les directions.
rommex
49

SWIFT 4

Solution facile à utiliser, disponible pour tous les enfants UILabel du projet.

Exemple:

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

Extension 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
    }
}
iEvgen Podkorytov
la source
1
Veuillez expliquer brièvement votre réponse et ne pas seulement publier le code.
lmiguelvargasf
4
Votre extension annuler la valeur 0 de numberOfLines
Antoine Bodart
C'est génial, mais j'ai des problèmes avec plusieurs lignes, même si j'ajoute un nombre de lignes 2 ou que je laisse à 0, il en montre toujours un. vous savez comment le résoudre?
Mago Nicolas Palacios
1
@AntoineBodart avez-vous réussi à résoudre le problème numberOfLines?
Mago Nicolas Palacios
@ AntoineBodart, @Mago Nicolas Palacios - A été résolu!
iEvgen Podkorytov
47

Utilisez simplement a UIViewcomme vue de supervision et définissez une marge fixe sur l'étiquette avec mise en page automatique.

Mundi
la source
1
drawTextInRect ne fonctionne que pour 1 ligne, intrinsicContentSizene fonctionne pas avec un remplissage horizontal. Envelopper UILabel dans UIView est la bonne façon d'aller
onmyway133
8
Si vous êtes dans IB, il est maintenant temps de se souvenir du menu Éditeur -> Intégrer dans -> Afficher. Sélectionnez d'abord votre UILabel :)
Graham Perks
46

Utilisez simplement un UIButton, il est déjà intégré. Désactivez toutes les fonctionnalités supplémentaires des boutons et vous obtenez une étiquette sur laquelle vous pouvez définir des instets de bord.

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
Steve M
la source
2
Hé, c'est un bon conseil! Aucune extension requise! :-D
Felipe Ferri
2
Le réglage isUserInteractionEnabled = falseest pratique pour le désactiver.
mxcl le
Bon conseil ... Je préfère faire ça plutôt que d'aller chercher une extension.
Ross
1
Belle astuce, avec le gros avantage que cela peut également être fait dans Interface Builder
Ely
1
La meilleure solution sans sous
classement
16

Sans Storyboard:

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
        }
    }
}

Usage:

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)

Résultat:

Alice Chan
la source
Fonctionne, mais savez-vous comment le faire accepter plusieurs lignes? Changez simplement init avec "PaddingLabel (withInsets: 8, 8, 16, 16)"
Camilo Ortegón
16

Rembourrage d'une UILabelsolution complète. Mis à jour pour 2020.

Il s'avère qu'il y a trois choses à faire.

1. Doit appeler textRect # forBounds avec la nouvelle taille plus petite

2. Doit remplacer drawText par la nouvelle taille plus petite

3. Si une cellule de taille dynamique, doit ajuster intrinsicContentSize

Dans l'exemple typique ci-dessous, l'unité de texte est dans une vue de table, une vue de pile ou une construction similaire, ce qui lui donne une largeur fixe . Dans l'exemple, nous voulons un rembourrage de 60,20,20,24.

Ainsi, nous prenons le intrinsicContentSize "existant" et ajoutons en fait 80 à la hauteur .

Répéter ...

Vous devez littéralement "obtenir" la hauteur calculée "jusqu'ici" par le moteur et modifier cette valeur.

Je trouve ce processus déroutant, mais c'est ainsi que cela fonctionne. Pour moi, Apple devrait exposer un appel nommé quelque chose comme "calcul préliminaire de la hauteur".

Deuxièmement, nous devons utiliser l'appel textRect # forBounds avec notre nouvelle taille plus petite .

Donc, dans textRect # forBounds, nous réduisons d' abord la taille, puis appelons super.

Alerte! Vous devez appeler super après , pas avant!

Si vous étudiez attentivement toutes les tentatives et discussions sur cette page, c'est exactement le problème. Notez que certaines solutions «semblent presque fonctionner», mais quelqu'un signalera alors que dans certaines situations, cela ne fonctionnera pas. C'est en effet la raison exacte - de façon déroutante, vous devez "appeler super après", pas avant.

Donc, si vous appelez super "dans le mauvais ordre", cela fonctionne généralement, mais pas pour certaines longueurs de texte spécifiques .

Voici un exemple visuel exact de "faire un super premier incorrectement":

entrez la description de l'image ici

Notez que les marges 60,20,20,24 sont correctes MAIS le calcul de la taille est en fait erroné, car il a été fait avec le modèle "super first" dans textRect # forBounds.

Fixé:

Notez que ce n'est que maintenant que le moteur textRect # forBounds sait comment effectuer correctement le calcul :

entrez la description de l'image ici

Finalement!

Là encore, dans cet exemple, le UILabel est utilisé dans la situation typique où la largeur est fixe. Donc, dans intrinsicContentSize, nous devons "ajouter" la hauteur supplémentaire globale que nous voulons. (Vous n'avez pas besoin d '«ajouter» de quelque façon que ce soit à la largeur, cela n'aurait aucun sens car elle est fixée.)

Ensuite, dans textRect # forBounds vous obtenez les limites "suggérées jusqu'ici" par autolayout, vous soustrayez vos marges, et seulement ensuite appelez à nouveau le moteur textRect # forBounds, c'est-à-dire en super, qui vous donnera un résultat.

Enfin et simplement dans drawText, vous dessinez bien sûr dans cette même boîte plus petite.

Phew!

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
}

Encore une fois. Notez que les réponses sur ceci et d'autres QA qui sont "presque" correctes souffrent du problème dans la première image ci-dessus - le "super est au mauvais endroit" . Vous devez forcer la taille plus grande dans intrinsicContentSize, puis dans textRect # forBounds, vous devez d'abord réduire les limites de la première suggestion , puis appeler super.

Résumé: vous devez "appeler le super dernier " dans textRect # forBounds

Voilà le secret.

Notez que vous n'avez pas besoin et ne devriez pas avoir besoin d'appeler en plus invalidate, sizeThatFits, needsLayout ou tout autre appel forcé. Une solution correcte devrait fonctionner correctement dans le cycle normal de dessin de mise en page automatique.


Pointe:

Si vous travaillez avec des polices monospaces, voici un bon conseil: https://stackoverflow.com/a/59813420/294884

Fattie
la source
11

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))
    } 
}

usage:

let label = EdgeInsetLabel()
label.textInsets = UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6)
LE24
la source
ATTENDEZ - il y a en fait un problème que j'ai trouvé avec cela dans certains cas. Auparavant, c'était la réponse la plus correcte. J'ai mis la bonne réponse ci-dessous.
Fattie
J'ai inclus une image dans ma réponse montrant le problème
Fattie
9

Code Swift 3 avec exemple d'implémentation

class UIMarginLabel: UILabel {

    var topInset:       CGFloat = 0
    var rightInset:     CGFloat = 0
    var bottomInset:    CGFloat = 0
    var leftInset:      CGFloat = 0

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

class LabelVC: UIViewController {

    //Outlets
    @IBOutlet weak var labelWithMargin: UIMarginLabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        //Label settings.
        labelWithMargin.leftInset = 10
        view.layoutIfNeeded()
    }
}

N'oubliez pas d'ajouter le nom de classe UIMarginLabel dans l'objet d'étiquette de storyboard. Bon codage!

mriaz0011
la source
8

Selon Swift 4.2 (Xcode 10 beta 6) "UIEdgeInsetsInsetRect" est obsolète. J'ai également déclaré la classe publique pour la rendre plus utile.

public class UIPaddedLabel: 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

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

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

    public override func sizeToFit() {
        super.sizeThatFits(intrinsicContentSize)
    }
}
Stéphane de Luca
la source
Ça marche bien. Mais j'essaie de l'utiliser dans un CollectionViewCell et il ne se redimensionne pas bien après la réutilisation (événement après sizeToFit et layoutIfNeeded). Un identifiant comment le redimensionner?
Bogy
1
J'ai mis à jour sizeToFit () pour le faire fonctionner avec une vue réutilisable
Bogy
sizeToFit()devrait être public comme: "La méthode d'instance de remplacement doit être aussi accessible que son type englobant"
psyFi
7

J'ai édité un peu dans la réponse acceptée. Il y a un problème quand leftInsetet rightInsetaugmenter, une partie du texte disparaîtra, b / c la largeur de l'étiquette sera rétrécie mais la hauteur n'augmentera pas comme sur la figure:

étiquette de remplissage avec une taille de contenu intrinsèque incorrecte

Pour résoudre ce problème, vous devez recalculer la hauteur du texte comme suit:

@IBDesignable class PaddingLabel: UILabel {

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

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

  override func intrinsicContentSize() -> CGSize {
    var intrinsicSuperViewContentSize = super.intrinsicContentSize()

    let textWidth = frame.size.width - (self.leftInset + self.rightInset)
    let newSize = self.text!.boundingRectWithSize(CGSizeMake(textWidth, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: self.font], context: nil)
    intrinsicSuperViewContentSize.height = ceil(newSize.size.height) + self.topInset + self.bottomInset

    return intrinsicSuperViewContentSize
  }
}

et résultat:

étiquette de remplissage avec la bonne taille de contenu intrinsèque

J'espère aider certaines personnes dans la même situation que moi.

Quang Tran
la source
2
Si vous prévoyez d'utiliser Swift 3.0 , vous devez changer les noms de fonction, car le nouveau langage Apple rompt complètement la définition de fonction précédente. Alors, override func drawTextInRect(rect: CGRect)devient override func drawText(in rect: CGRect)et override func intrinsicContentSize() -> CGSizedevient override var intrinsicContentSize : CGSize Enjoy!
DoK
malheureusement je ne l'ai pas fait fonctionner. J'ai essayé avec notre code swift 5 override var intrinsicContentSize: CGSize { // .. return intrinsicSuperViewContentSize }
Nazmul Hasan
7

Utilisez simplement la mise en page automatique:

let paddedWidth = myLabel.intrinsicContentSize.width + 2 * padding
myLabel.widthAnchor.constraint(equalToConstant: paddedWidth).isActive = true

Terminé.

Tim Shadel
la source
Vous pouvez également faire de même avec la hauteur.
ShadeToD
Excellent, merci.
Mike Taverne le
7

Une autre option sans sous-classement serait de:

  1. Définir l'étiquette text
  2. sizeToFit()
  3. puis augmentez un peu la hauteur de l'étiquette pour simuler le remplissage

    label.text = "someText"
    label.textAlignment = .center    
    label.sizeToFit()  
    label.frame = CGRect( x: label.frame.x, y: label.frame.y,width:  label.frame.width + 20,height: label.frame.height + 8)
Gars
la source
Étonnamment, c'était tout ce dont j'avais besoin, juste un peu modifié: label.frame = CGRect( x: label.frame.origin.x - 10, y: label.frame.origin.y - 4, width: label.frame.width + 20,height: label.frame.height + 8)les -10 et -4 pour la centralisation
Mofe Ejegi
6

Solution Swift 3, iOS10:

open class UIInsetLabel: UILabel {

    open var insets : UIEdgeInsets = UIEdgeInsets() {
        didSet {
            super.invalidateIntrinsicContentSize()
        }
    }

    open override var intrinsicContentSize: CGSize {
        var size = super.intrinsicContentSize
        size.width += insets.left + insets.right
        size.height += insets.top + insets.bottom
        return size
    }

    override open func drawText(in rect: CGRect) {
        return super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
    }
}
Andrewz
la source
6

Dans Swift 3

meilleure et simple manière

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

}
SanRam
la source
4

Sous-classe UILabel. (File-New-File- CocoaTouchClass-make sous-classe de UILabel).

//  sampleLabel.swift

import UIKit

class sampleLabel: UILabel {

 let topInset = CGFloat(5.0), bottomInset = CGFloat(5.0), leftInset = CGFloat(8.0), rightInset = CGFloat(8.0)

 override func drawTextInRect(rect: CGRect) {

  let insets: UIEdgeInsets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
  super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))

 }
 override func intrinsicContentSize() -> CGSize {
  var intrinsicSuperViewContentSize = super.intrinsicContentSize()
  intrinsicSuperViewContentSize.height += topInset + bottomInset
  intrinsicSuperViewContentSize.width += leftInset + rightInset
  return intrinsicSuperViewContentSize
 }
}

Sur ViewController:

override func viewDidLoad() {
  super.viewDidLoad()

  let labelName = sampleLabel(frame: CGRectMake(0, 100, 300, 25))
  labelName.text = "Sample Label"
  labelName.backgroundColor =  UIColor.grayColor()

  labelName.textColor = UIColor.redColor()
  labelName.shadowColor = UIColor.blackColor()
  labelName.font = UIFont(name: "HelveticaNeue", size: CGFloat(22))
  self.view.addSubview(labelName)
 }

OU Associez la classe UILabel personnalisée sur Storyboard en tant que classe de Label.

AG
la source
Je voterais si vous changez ces valeurs codées en dur en propriétés de classe, j'utilise déjà ce code.
Juan Boero
@ Juan: drawTextInRect est une propriété de classe par défaut de UILabel que nous ne pouvons pas remplacer à l'aide de code. La meilleure pratique pour sous-classer UILabel et ajouter le changement de trame requis. Quoi qu'il en soit, c'est pratique comme fonction d'héritage.
AG
c'est correct, cependant à partir de Swift 3 au moins, intrinsicContentSize n'est pas une fonction mais plutôt une propriété, donc devrait être "override var intrinsicContentSize: CGFloat {}" au lieu de "override func intrinsicContentSize", juste une note.
joey
4

Tout comme les autres réponses mais corrige un bogue. Lorsque label.widthest contrôlé par la mise en page automatique, parfois le texte sera rogné.

@IBDesignable
class InsetLabel: UILabel {

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

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

    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 {
        let systemContentSize = super.intrinsicContentSize
        let adjustSize = CGSize(width: systemContentSize.width + leftInset + rightInset, height: systemContentSize.height + topInset +  bottomInset) 
        if adjustSize.width > preferredMaxLayoutWidth && preferredMaxLayoutWidth != 0 {
            let constraintSize = CGSize(width: bounds.width - (leftInset + rightInset), height: .greatestFiniteMagnitude)
            let newSize = super.sizeThatFits(constraintSize)
            return CGSize(width: systemContentSize.width, height: ceil(newSize.height) + topInset + bottomInset)
        } else {
            return adjustSize
        }
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: insets))
    }
}
PowHu
la source
2

Rembourrage facile (Swift 3.0, réponse d'Alvin George):

  class NewLabel: UILabel {

        override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
                return self.bounds.insetBy(dx: CGFloat(15.0), dy: CGFloat(15.0))
        }

        override func draw(_ rect: CGRect) {
                super.drawText(in: self.bounds.insetBy(dx: CGFloat(5.0), dy: CGFloat(5.0)))
        }

  }
odemolliens
la source
2

Une élaboration sur la réponse de Mundi.

c'est-à-dire incorporer une étiquette dans un UIViewet appliquer le remplissage via la mise en page automatique. Exemple:

ressemble à un UILabel rembourré

Aperçu:

1) Créez un UIView("panneau") et définissez son apparence.

2) Créez un UILabelet ajoutez-le au panneau.

3) Ajoutez des contraintes pour appliquer le remplissage.

4) Ajoutez le panneau à votre hiérarchie de vues, puis positionnez le panneau.

Détails:

1) Créez la vue de panneau.

let panel = UIView()
panel.backgroundColor = .green
panel.layer.cornerRadius = 12

2) Créez l'étiquette, ajoutez-la au panneau en tant que sous-vue.

let label = UILabel()
panel.addSubview(label)

3) Ajoutez des contraintes entre les bords de l'étiquette et le panneau. Cela oblige le panneau à se tenir à distance de l'étiquette. c'est-à-dire "rembourrage"

Éditorial: faire tout cela à la main est très fastidieux, verbeux et sujet aux erreurs. Je vous suggère de choisir un wrapper de mise en page automatique de github ou d'en écrire un vous-même

label.panel.translatesAutoresizingMaskIntoConstraints = false
label.topAnchor.constraint(equalTo: panel.topAnchor,
    constant: vPadding).isActive = true
label.bottomAnchor.constraint(equalTo: panel.bottomAnchor,
    constant: -vPadding).isActive = true
label.leadingAnchor.constraint(equalTo: panel.leadingAnchor,
    constant: hPadding).isActive = true
label.trailingAnchor.constraint(equalTo: panel.trailingAnchor,
    constant: -hPadding).isActive = true

label.textAlignment = .center

4) Ajoutez le panneau à votre hiérarchie de vues, puis ajoutez des contraintes de positionnement. par exemple, étreignez le côté droit d'une tableViewCell, comme dans l'exemple d'image.

Remarque: il vous suffit d'ajouter des contraintes de position, pas des contraintes dimensionnelles: la mise en page automatique résoudra la mise en page en fonction à la fois intrinsicContentSizede l'étiquette et des contraintes ajoutées précédemment.

hostView.addSubview(panel)
panel.translatesAutoresizingMaskIntoConstraints = false
panel.trailingAnchor.constraint(equalTo: hostView.trailingAnchor,
    constant: -16).isActive = true
panel.centerYAnchor.constraint(equalTo: hostView.centerYAnchor).isActive = true
Womble
la source
2

Utilisez ce code si vous rencontrez un problème de rognage de texte lors de l'application d'un remplissage.

@IBDesignable 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.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset)
        super.drawText(in: UIEdgeInsetsInsetRect(rect, insets))
    }

    override var intrinsicContentSize: CGSize {
        var intrinsicSuperViewContentSize = super.intrinsicContentSize
        let textWidth = frame.size.width - (self.leftInset + self.rightInset)
        let newSize = self.text!.boundingRect(with: CGSize(textWidth, CGFloat.greatestFiniteMagnitude), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: self.font], context: nil)
        intrinsicSuperViewContentSize.height = ceil(newSize.size.height) + self.topInset + self.bottomInset
        return intrinsicSuperViewContentSize
    }
}

extension CGSize{
    init(_ width:CGFloat,_ height:CGFloat) {
        self.init(width:width,height:height)
    }
}
Ceekay
la source
Merci d'avoir posté, je recherche une solution en matière de rembourrage + rognage. Il me semble que votre solution casse label.numberOfLines = 0 dont j'ai besoin. Une solution de contournement?
Don
1

Similaire à d'autres réponses, mais avec une classe func pour configurer le rembourrage de manière dynamique:

class UILabelExtendedView: UILabel
{
    var topInset: CGFloat = 4.0
    var bottomInset: CGFloat = 4.0
    var leftInset: CGFloat = 8.0
    var rightInset: CGFloat = 8.0

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

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

    func setPadding(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat){
        self.topInset = top
        self.bottomInset = bottom
        self.leftInset = left
        self.rightInset = right
        let insets: UIEdgeInsets = UIEdgeInsets(top: top, left: left, bottom: bottom, right: right)
        super.drawText(in: UIEdgeInsetsInsetRect(self.frame, insets))
    }
}
Pélanes
la source
1

Une solution pragmatique consiste à ajouter des étiquettes vierges de même hauteur et couleur que l'étiquette principale. Définissez l'espace de début / de fin de l'étiquette principale sur zéro, alignez les centres verticaux et définissez la largeur de la marge souhaitée.

pks1981
la source
1

Si vous souhaitez ajouter un remplissage de 2 pixels autour du textRect, procédez comme suit:

let insets = UIEdgeInsets(top: -2, left: -2, bottom: -2, right: -2)
label.frame = UIEdgeInsetsInsetRect(textRect, insets)
zs2020
la source
qu'est-ce que textRect? label.frame?
Gustavo Baiocchi Costa
1

Si vous ne voulez pas ou n'avez pas besoin d'utiliser un UILabel @IBInspectable / @IBDesignable dans Storyboard (je pense que ceux-ci sont rendus trop lentement de toute façon), alors il est plus propre d'utiliser UIEdgeInsets au lieu de 4 CGFloats différents.

Exemple de code pour Swift 4.2:

class UIPaddedLabel: UILabel {
    var padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

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

    public override var intrinsicContentSize: CGSize {
        let size = super.intrinsicContentSize
        return CGSize(width: size.width + padding.left + padding.right,
                      height: size.height + padding.top + padding.bottom)
    }
}
Simon Backx
la source
1

Objectif c

Basé sur la réponse de Tai Le qui implémente la fonctionnalité dans un IB Designable, voici la version Objective-C.

Mettez ceci dans YourLabel.h

@interface YourLabel : UILabel

@property IBInspectable CGFloat topInset;
@property IBInspectable CGFloat bottomInset;
@property IBInspectable CGFloat leftInset;
@property IBInspectable CGFloat rightInset;

@end

Et cela irait dans YourLabel.m

IB_DESIGNABLE

@implementation YourLabel

#pragma mark - Super

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        self.topInset = 0;
        self.bottomInset = 0;
        self.leftInset = 0;
        self.rightInset = 0;
    }
    return self;
}

- (void)drawTextInRect:(CGRect)rect {
    UIEdgeInsets insets = UIEdgeInsetsMake(self.topInset, self.leftInset, self.bottomInset, self.rightInset);
    [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}

- (CGSize)intrinsicContentSize {

    CGSize size = [super intrinsicContentSize];
    return CGSizeMake(size.width + self.leftInset + self.rightInset,
                      size.height + self.topInset + self.bottomInset);
}

@end

Vous pouvez ensuite modifier les inserts YourLabel directement dans Interface Builder après avoir spécifié la classe à l'intérieur du XIB ou du storyboard, la valeur par défaut des inserts étant zéro.

lmarceau
la source
0

Moyen facile

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        self.view.addSubview(makeLabel("my title",x: 0, y: 100, w: 320, h: 30))
    }

    func makeLabel(title:String, x:CGFloat, y:CGFloat, w:CGFloat, h:CGFloat)->UILabel{
        var myLabel : UILabel = UILabel(frame: CGRectMake(x,y,w,h))
        myLabel.textAlignment = NSTextAlignment.Right

        // inser last char to right
        var titlePlus1char = "\(title)1"
        myLabel.text = titlePlus1char
        var titleSize:Int = count(titlePlus1char)-1

        myLabel.textColor = UIColor(red:1.0, green:1.0,blue:1.0,alpha:1.0)
        myLabel.backgroundColor = UIColor(red: 214/255, green: 167/255, blue: 0/255,alpha:1.0)


        // create myMutable String
        var myMutableString = NSMutableAttributedString()

        // create myMutable font
        myMutableString = NSMutableAttributedString(string: titlePlus1char, attributes: [NSFontAttributeName:UIFont(name: "HelveticaNeue", size: 20)!])

        // set margin size
        myMutableString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue", size: 10)!, range: NSRange(location: titleSize,length: 1))

        // set last char to alpha 0
        myMutableString.addAttribute(NSForegroundColorAttributeName, value: UIColor(red:1.0, green:1.0,blue:1.0,alpha:0), range: NSRange(location: titleSize,length: 1))

        myLabel.attributedText = myMutableString

        return myLabel
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}
Alex Freitas
la source
0

Rembourrage facile:

import UIKit

    class NewLabel: UILabel {

        override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {

            return CGRectInset(self.bounds, CGFloat(15.0), CGFloat(15.0))
        }

        override func drawRect(rect: CGRect) {

            super.drawTextInRect(CGRectInset(self.bounds,CGFloat(5.0), CGFloat(5.0)))
        }

    }
AG
la source
Version 3.0 en bas de la page
odemolliens
0

Swift 4+

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.firstLineHeadIndent = 10

// Swift 4.2++
label.attributedText = NSAttributedString(string: "Your text", attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle])

// Swift 4.1--
label.attributedText = NSAttributedString(string: "Your text", attributes: [NSAttributedStringKey.paragraphStyle: paragraphStyle])
Maksim Gridin
la source