Dodawanie spacji / wypełnienia do UILabel

187

Mam miejsce, w UILabelktórym chcę dodać miejsce na górze i na dole. Przy minimalnej wysokości w ograniczeniu zmodyfikowałem ją do:

wprowadź opis zdjęcia tutaj

EDYCJA: Aby to zrobić, użyłem:

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

    } 

Ale muszę znaleźć inną metodę, ponieważ jeśli napiszę więcej niż dwie linie, problem będzie taki sam:

wprowadź opis zdjęcia tutaj

Annachiara
źródło
1
Możliwy duplikat marginesu tekstowego UILabel
gblazex
Nareszcie wreszcie wymyśliliśmy, jak to zrobić poprawnie we wszystkich dynamicznych przypadkach, jako idealny zamiennik UILabel bez potrzeby zmiany układu lub innych problemów. PHEW. stackoverflow.com/a/58876988/294884
Fattie

Odpowiedzi:

122

Jeśli chcesz pozostać przy UILabel, nie dzieląc go na klasy, Mundi dał ci jasne rozwiązanie.

Jeśli alternatywnie, chciałbyś uniknąć owijania UILabel za pomocą UIView, możesz użyć UITextView, aby włączyć użycie UIEdgeInsets (padding) lub UILabel podklasy do obsługi UIEdgeInsets.

Korzystanie z UITextView wymagałoby jedynie wstawienia (OBJ-C):

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

Alternatywnie, jeśli podklasę UILabel , przykładem tego podejścia byłoby zastąpienie metody drawTextInRect
(OBJ-C)

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

Możesz dodatkowo dostarczyć swoje nowe podklasowane UILabel ze zmiennymi wstawkami dla GÓRA, LEWO, DOLNA i PRAWA.

Przykładowy kod może być:

In .h (OBJ-C)

float topInset, leftInset,bottomInset, rightInset;

W .m (OBJ-C)

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

EDYCJA 1:

Z tego, co widziałem, wydaje się, że musisz zastąpić intrinsicContentSize UILabel podczas jego podklasy.

Więc powinieneś zastąpić intrinsicContentSize jak:

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

I dodaj następującą metodę, aby edytować wstawki, zamiast edytować je osobno:

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

Uaktualni rozmiar twojego UILabel, aby dopasować do wypustek krawędziowych i pokryje wieloliniową konieczność, o której mówiłeś.

Edytuj # 2

Po nieco przeszukaniu znalazłem Gist z IPInsetLabel. Jeśli żadne z tych rozwiązań nie działa, możesz je wypróbować.

Edytuj # 3

Było podobne pytanie (duplikat) w tej sprawie.
Aby uzyskać pełną listę dostępnych rozwiązań, zobacz tę odpowiedź: margines tekstowy UILabel

nunofmendes
źródło
Przepraszam, ale już użyłem: `override func drawTextInRect (rect: CGRect) {var insets: UIEdgeInsets = UIEdgeInsets (góra: 0,0, lewo: 10,0, dół: 0,0, prawo: 10,0) super.drawTextInRect (UIEdgeInsetsInsetRect (rect, wstawki ))} `nie działa, ponieważ wynik jest taki sam, nie działa dynamicznie ..
Annachiara
Czy próbowałeś z UITextView zamiast UILabel? A może naprawdę potrzebujesz UILabel?
nunofmendes
@ Annachiara sprawdź dokonaną przeze mnie zmianę. Sprawdź, czy to działa.
nunofmendes
Dobrze. Czy zadziałało wyświetlanie tekstu? Przepraszam, że nie piszę w Swift, ale wciąż jestem w trybie Obj-C. Moim celem z tym kodem było pomóc ci dojść do pewnych wniosków. Mam nadzieję, że tak.
nunofmendes
1
Korzystanie z TextView i niektórych ustawień scenorysu oraz self.textview.textContainerInset = UIEdgeInsetsMake (0, 10, 10, 10); W końcu to działa! Dzięki !
Annachiara
208

Próbowałem z nim w Swift 4.2 , mam nadzieję, że zadziała dla ciebie!

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

Możesz też użyć CocoaPods tutaj https://github.com/levantAJ/PaddingLabel

pod 'PaddingLabel', '1.2'

wprowadź opis zdjęcia tutaj

Tai Le
źródło
6
Szerokość uilabela nie zmienia się, dlatego tekst staje się „...”
neobie
1
@Tai Le, dzięki za udostępnienie, użyłem go w widoku tabeli, nie wiem, dlaczego przycina tekst, np. student zostaje studentem,
vishwa.deepak
1
@ Czas może chciałeś użyćmin
ielyamani,
4
Słowo ostrzeżenia tutaj. Korzystałem z tego rozwiązania w podklasie UILabel. Podczas korzystania z tych etykiet w trybie multilinii w pionowym UIStackView występuje problem. Czasami etykieta wydaje się owijać tekst bez prawidłowego zmiany rozmiaru etykiety - tak więc słowo lub 2 kończy się na końcu łańcucha. Nie mam teraz rozwiązania. Napiszę to tutaj, jeśli je zrobię. Spędziłem godziny, zastanawiając się nad tym problemem, zanim udowodniłem, że tu jest.
Darren Black,
1
Aby działało w takich sytuacjach, musisz zastąpić „setBounds” i ustawić self.preferredMaxLayoutWidth na szerokość granic, pomniejszoną o lewe i prawe wstawki
Arnaud Barisain-Monrose
82

Szybki 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
      }
   }
}
zambi
źródło
tylko mały komentarz: ustaw tę klasę na etykietę w inspektorze tożsamości (klasa niestandardowa) i użyj nowego atrybutu w inspektorze atrybutów o nazwie padding label. również poniżej 5 wypełnień jest bezskuteczne
iman kazemayni
3
Nie zawsze działa to poprawnie z etykietami wielowierszowymi, ponieważ kiedy etykieta oblicza swoją wysokość, zakłada zerowanie wypełnienia.
fhucho
76

Możesz to zrobić poprawnie z IB:

  1. zmień tekst na przypisany

przypisany tekst

  1. przejdź do listy rozwijanej z „...”

wprowadź opis zdjęcia tutaj

  1. zobaczysz niektóre właściwości wypełnienia linii, akapitów i tekstu, wcięcia pierwszego wiersza lub cokolwiek chcesz

wprowadź opis zdjęcia tutaj

  1. sprawdź wynik

wprowadź opis zdjęcia tutaj

Pierre-Yves Guillemet
źródło
1
W mojej serii ujęć widzę zmianę tekstu, ale po uruchomieniu aplikacji. Tekst nie pokazuje zmiany ... T_T .. moja etykieta znajduje się w niestandardowej komórce, czy jest jakiś problem?
A. Trejo,
1
@ A.Trejo Może być niestandardową komórką ustawiającą właściwość label w czasie wykonywania.
Pierre-Yves Guillemet
1
Zmiany mogą pojawiać się w serii ujęć, ale po uruchomieniu aplikacji nie ma żadnych zmian.
Rhenz
4
Nie dotyczy to programowego ustawiania tekstu.
Nij
1
To nie jest odpowiedź. Masz tylko kontrolę nad wcięciem pierwszego wiersza, ale nie dopełnianie we wszystkich kierunkach.
rommex
49

SWIFT 4

Łatwe w użyciu rozwiązanie, dostępne dla wszystkich dzieci UILabel w projekcie.

Przykład:

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

Rozszerzenie 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
źródło
1
proszę krótko wyjaśnij swoją odpowiedź i nie tylko kod pocztowy.
lmiguelvargasf
4
Twoje rozszerzenie anuluje wartość 0 do numberOfLines
Antoine Bodart
To świetnie, ale mam problemy z wieloma liniami, nawet pomyślałem, że dodałem liczby linii 2 lub zostawiłem 0, zawsze pokazuje jedną. wiesz jak to rozwiązać?
Mago Nicolas Palacios
1
@AntoineBodart udało Ci się rozwiązać problem numberOfLines?
Mago Nicolas Palacios
@ AntoineBodart, @Mago Nicolas Palacios - Was Solved!
iEvgen Podkorytov
47

Wystarczy użyć a UIViewjako podglądu i zdefiniować stały margines etykiety z automatycznym układem.

Mundi
źródło
1
drawTextInRect działa tylko dla 1 linii, intrinsicContentSizenie działa z poziomym wypełnieniem. Owiń UILabel w UIView to dobry sposób
onmyway133
8
Jeśli jesteś w IB, nadszedł czas, aby zapamiętać menu Edytor -> Osadź w -> Widok. Najpierw wybierz swój UILabel :)
Graham Perks
46

Wystarczy użyć przycisku UIB, który jest już wbudowany. Wyłącz wszystkie dodatkowe funkcje przycisków, a otrzymasz etykietę, na której możesz ustawić instity brzegowe.

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.
źródło
2
Hej, to świetna wskazówka! Nie wymaga rozszerzeń! :-D
Felipe Ferri
2
Ustawienie isUserInteractionEnabled = falsejest przydatne, aby je wyłączyć.
mxcl
Świetna wskazówka ... Wolę to zrobić niż wybrać rozszerzenie.
Ross
1
Dobra wskazówka, z dużą zaletą, że można to zrobić również w Konstruktorze interfejsów
Ely
1
Najlepsze rozwiązanie bez podklas itp.
MeGaPk,
16

Bez scenorysu:

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

Stosowanie:

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)

Wynik:

Alice Chan
źródło
Działa, ale czy wiesz, jak sprawić, by akceptował wiele linii? Wystarczy zmienić init za pomocą „PaddingLabel (withInsets: 8, 8, 16, 16)”
Camilo Ortegón,
16

Wypełnienie UILabel, pełne rozwiązanie. Zaktualizowano do 2020 r.

Okazuje się, że należy zrobić trzy rzeczy .

1. Musi wywołać textRect # forBounds z nowym mniejszym rozmiarem

2. Musi zastąpić drawText nowym, mniejszym rozmiarem

3. Jeśli komórka ma dynamiczny rozmiar, musi dostosować intrinsicContentSize

W typowym przykładzie poniżej jednostka tekstowa jest w widoku tabeli, widoku stosu lub podobnej konstrukcji, co daje mu stałą szerokość . W tym przykładzie chcemy dopełnienia 60,20,20,24.

Zatem bierzemy „istniejącą” wartość intrinsicContentSize i faktycznie dodajemy 80 do wysokości .

Powtarzać ...

Musisz dosłownie „uzyskać” wysokość obliczoną „jak dotąd” przez silnik i zmienić tę wartość.

Uważam ten proces za mylący, ale tak to działa. Dla mnie Apple powinien udostępnić połączenie o nazwie „wstępne obliczenie wysokości”.

Po drugie, musimy faktycznie użyć wywołania textRect # forBounds z naszym nowym mniejszym rozmiarem .

Tak więc w textRect # forBounds najpierw zmniejszamy rozmiar, a następnie wywołujemy super.

Alarm! Państwo musi zadzwonić Super po , a nie przed!

Jeśli dokładnie przestudiujesz wszystkie próby i dyskusje na tej stronie, jest to dokładny problem. Zauważ, że niektóre rozwiązania „wydają się prawie działać”, ale wtedy ktoś zgłosi, że w niektórych sytuacjach to nie zadziała. Jest to rzeczywiście dokładny powód - myląco musisz „zadzwonić super później”, a nie wcześniej.

Tak więc, jeśli nazwiesz super to „w niewłaściwej kolejności”, zwykle działa, ale nie dla niektórych określonych długości tekstu .

Oto dokładny wizualny przykład „niepoprawnego robienia super najpierw”:

wprowadź opis zdjęcia tutaj

Zauważ, że marginesy 60, 20, 20, 24, 24 są poprawne, ALE obliczenia rozmiaru są w rzeczywistości niepoprawne, ponieważ dokonano tego przy użyciu wzorca „super first” w textRect # forBounds.

Naprawiony:

Zauważ, że dopiero teraz silnik textRect # forBounds wie, jak poprawnie wykonać obliczenia :

wprowadź opis zdjęcia tutaj

Wreszcie!

Ponownie w tym przykładzie UILabel jest używany w typowej sytuacji, w której szerokość jest ustalona. Dlatego w intrinsicContentSize musimy „dodać” ogólną dodatkową wysokość, jakiej chcemy. (Nie musisz „dodawać” w żaden sposób do szerokości, co byłoby bez znaczenia, ponieważ jest ustalone).

Następnie w textRect # forBounds otrzymujesz granice „sugerowane do tej pory” przez autolayout, odejmujesz swoje marginesy i tylko wtedy ponownie wywołujesz silnik textRect # forBounds, to znaczy w super, który da ci wynik.

Wreszcie i po prostu w drawText oczywiście rysujesz w tym samym mniejszym polu.

Uff!

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
}

Jeszcze raz. Zauważ, że odpowiedzi na to i inne pytania, które są „prawie” poprawne, powodują problem z pierwszego obrazu powyżej - „super jest w niewłaściwym miejscu” . Musisz wymusić większy rozmiar w intrinsicContentSize, a następnie w textRect # forBounds musisz najpierw zmniejszyć granice pierwszej sugestii, a następnie wywołać super.

Podsumowanie: musisz „zadzwonić do super ostatniego ” w textRect # forBounds

To jest sekret.

Pamiętaj, że nie musisz i nie musisz dodatkowo wywoływać unieważnienia, sizeThatFits, needLayout ani żadnego innego wymuszającego połączenia. Prawidłowe rozwiązanie powinno działać poprawnie w normalnym cyklu losowania autoukładu.


Wskazówka:

Jeśli pracujesz z czcionkami monospace, oto świetna wskazówka: https://stackoverflow.com/a/59813420/294884

Fattie
źródło
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))
    } 
}

stosowanie:

let label = EdgeInsetLabel()
label.textInsets = UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6)
LE24
źródło
CZEKAJ - w niektórych przypadkach mam z tym problem. Wcześniej była to najbardziej poprawna odpowiedź. Podałem poprawną odpowiedź poniżej.
Fattie
W odpowiedzi umieściłem obraz przedstawiający problem
Fattie
9

Kod Swift 3 z przykładem implementacji

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

Nie zapomnij dodać nazwy klasy UIMarginLabel do obiektu etykiety scenorysu. Happy Coding!

mriaz0011
źródło
8

Zgodnie z Swift 4.2 (Xcode 10 beta 6) „UIEdgeInsetsInsetRect” jest przestarzałe. Zadeklarowałem również klasę publiczną, aby była bardziej użyteczna.

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
źródło
To dobrze działa. Ale próbuję użyć go wewnątrz CollectionViewCell i nie zmienia on dobrze rozmiaru po ponownym użyciu (zdarzenie po sizeToFit i layoutIfNeeded). Jakiś id, jak go zmienić?
Bogy,
1
Zaktualizowałem sizeToFit (), aby działał z widokiem wielokrotnego użytku
Bogy
sizeToFit()powinien być publiczny jako: „Metoda przesłaniająca instancję musi być tak dostępna, jak jej typ zamykający”
psyFi
7

Zredagowałem trochę w przyjętej odpowiedzi. Występuje problem, gdy leftInseti rightInsetzwiększenie, część tekstu zniknie, b / c szerokość etykiety zostanie zwężona, ale wysokość nie wzrośnie jak na rysunku:

etykieta wypełniająca z niewłaściwym rozmiarem treści wewnętrznej

Aby rozwiązać ten problem, należy ponownie obliczyć wysokość tekstu w następujący sposób:

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

i wynik:

wyściółka z odpowiednim rozmiarem zawartości wewnętrznej

Mam nadzieję pomóc niektórym osobom w takiej samej sytuacji jak ja.

Quang Tran
źródło
2
Jeśli planujesz używać Swift 3.0 , musisz zmienić nazwy funkcji, ponieważ nowy język Apple całkowicie psuje poprzednią definicję func. Więc override func drawTextInRect(rect: CGRect)staje się override func drawText(in rect: CGRect)i override func intrinsicContentSize() -> CGSizestaje się override var intrinsicContentSize : CGSize Ciesz się!
DoK
niestety nie sprawiłem, że działało. Próbowałem z naszym kodem swift 5 override var intrinsicContentSize: CGSize { // .. return intrinsicSuperViewContentSize }
Nazmul Hasan
7

Wystarczy użyć autolayout:

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

Gotowe.

Tim Shadel
źródło
Możesz również zrobić to samo z wysokością.
ShadeToD,
Wspaniale, dziękuje.
Mike Taverne
7

Inną opcją bez podklasy byłoby:

  1. Ustaw etykietę text
  2. sizeToFit()
  3. następnie nieznacznie zwiększ wysokość etykiety, aby zasymulować wypełnienie

    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)
Chłopak
źródło
Zaskakujące, to było wszystko, czego potrzebowałem, po prostu trochę zmodyfikowałem: 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)-10 i -4 do centralizacji
Mofe Ejegi
6

Rozwiązanie 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))
    }
}
Andrzej
źródło
6

W Swift 3

najlepszy i prosty sposób

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
źródło
4

Podklasa UILabel. (File-New-File- CocoaTouchClass-make Podklasa 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
 }
}

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

LUB Skojarz niestandardową klasę UILabel na Storyboard z klasą Label.

AG
źródło
oddałbym głos, jeśli zmienisz te zakodowane wartości we właściwości klasy, już używam tego kodu.
Juan Boero,
@ Juan: drawTextInRect jest domyślną właściwością klasy UILabel, której nie możemy zastąpić za pomocą kodu. Najlepsza praktyka, aby podklasować UILabel i dodać wymaganą zmianę ramki. W każdym razie jest to wygodne jako funkcja dziedziczenia.
AG
jest to poprawne, jednak przynajmniej od wersji Swift 3 intrinsicContentSize nie jest funkcją, ale raczej właściwością, więc powinno być „przesłonić var ​​intrinsicContentSize: CGFloat {}” zamiast „przesłonić func intrinsicContentSize”, tylko notatkę.
joey,
4

Podobnie jak inne odpowiedzi, ale napraw błąd. Gdy label.widthjest kontrolowany przez automatyczny układ, czasami tekst zostanie przycięty.

@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
źródło
2

Łatwe wypełnianie (Swift 3.0, odpowiedź 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
źródło
2

Opracowanie odpowiedzi Mundi.

tj. osadzanie etykiety w UIViewi wymuszanie wypełnienia za pomocą automatycznego układu. Przykład:

wygląda jak wyściełany UILabel

Przegląd:

1) Utwórz UIView(„panel”) i ustaw jego wygląd.

2) Utwórz a UILabeli dodaj go do panelu.

3) Dodaj ograniczenia, aby wymusić wypełnienie.

4) Dodaj panel do hierarchii widoku, a następnie umieść panel.

Detale:

1) Utwórz widok panelu.

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

2) Utwórz etykietę, dodaj ją do panelu jako widok podrzędny.

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

3) Dodaj ograniczenia między krawędziami etykiety a panelem. Zmusza to panel do zachowania odległości od etykiety. tzn. „padding”

Od redakcji: robienie tego wszystkiego ręcznie jest bardzo nużące, pełne i podatne na błędy. Sugeruję, abyś wybrał opakowanie Auto Layout z github lub sam je napisałeś

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) Dodaj panel do hierarchii widoku, a następnie dodaj ograniczenia pozycjonowania. np. przytul prawą stronę tableViewCell, jak na przykładowym obrazie.

Uwaga: musisz tylko dodać ograniczenia pozycyjne, a nie ograniczenia wymiarowe: Auto układ rozwiąże układ na podstawie zarówno intrinsicContentSizeetykiety, jak i wiązań dodanych wcześniej.

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
źródło
2

Użyj tego kodu, jeśli napotykasz problem przycinania tekstu podczas wypełniania.

@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
źródło
Dziękujemy za wysłanie, szukam rozwiązania w zakresie wypełnienia + przycinania. Wydaje mi się, że twoje rozwiązanie psuje label.numberOfLines = 0, czego potrzebuję. Jakieś obejście?
Don
1

Podobne do innych odpowiedzi, ale z klasą func, aby dynamicznie ustawić padding:

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))
    }
}
Pelanes
źródło
1

Jednym pragmatycznym rozwiązaniem jest dodanie pustych etykiet o tej samej wysokości i kolorze co główna etykieta. Ustaw odstęp początkowy / końcowy na głównej etykiecie na zero, wyrównaj pionowe środki i ustaw żądany margines.

pks1981
źródło
1

Jeśli chcesz dodać dopełnienie 2px wokół textRect, po prostu zrób to:

let insets = UIEdgeInsets(top: -2, left: -2, bottom: -2, right: -2)
label.frame = UIEdgeInsetsInsetRect(textRect, insets)
zs2020
źródło
co to jest textRect? label.frame?
Gustavo Baiocchi Costa
1

Jeśli nie chcesz lub nie chcesz używać @IBInspectable / @IBDesignable UILabel w Storyboard (myślę, że i tak są renderowane zbyt wolno), to łatwiej jest używać UIEdgeInsets zamiast 4 różnych CGFloats.

Przykład kodu dla 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
źródło
1

Cel C

Na podstawie odpowiedzi Tai Le , która implementuje tę funkcję w IB Designable, oto wersja Objective-C.

Umieść to w YourLabel.h

@interface YourLabel : UILabel

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

@end

I to pójdzie w 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

Następnie możesz modyfikować wstawki YourLabel bezpośrednio w Konstruktorze interfejsów po określeniu klasy w XIB lub serii ujęć, przy czym domyślna wartość wstawek wynosi zero.

Lmarceau
źródło
0

Łatwy sposób

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
źródło
0

Łatwe wypełnienie:

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
źródło
Wersja 3.0 na dolnej stronie
odemolliens 10.10.16
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
źródło