Autolayout - Die Eigengröße von UIButton enthält keine Titeleinfügungen

196

Wenn ich einen UIButton mit Autolayout angeordnet habe, passt sich seine Größe gut an den Inhalt an.

Wenn ich ein Bild als button.imageeinstelle, scheint dies wieder auf die Instrumentengröße zurückzuführen zu sein.

Wenn ich jedoch titleEdgeInsetsdie Schaltfläche optimiere , berücksichtigt das Layout dies nicht und schneidet stattdessen den Schaltflächentitel ab.

Wie kann ich sicherstellen, dass die Eigenbreite der Schaltfläche den Einschub berücksichtigt?

Geben Sie hier die Bildbeschreibung ein

Bearbeiten:

Ich benutze folgendes:

[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];

Ziel ist es, eine gewisse Trennung zwischen Bild und Text zu erreichen.

Ben Packard
quelle
3
Haben Sie dies als Radar abgelegt? Es scheint sicherlich ein Fehler in den intrinsischen Größenberechnungen des UIButton zu sein.
Ryan Poolos
1
Ich war bereit, ein Radar einzureichen, aber dies scheint tatsächlich ein erwartetes Verhalten zu sein. Dies ist in den * EdgeInsets- Eigenschaften von UIButton dokumentiert : "Die von Ihnen angegebenen Einfügungen werden auf das Titelrechteck angewendet, nachdem die Größe dieses Rechtecks ​​an den Text der Schaltfläche angepasst wurde . Positive Einfügungswerte können daher den Titeltext tatsächlich beschneiden . [...] Die Die Schaltfläche verwendet diese Eigenschaft nicht, um intrinsicContentSize und sizeThatFits: zu bestimmen. "
Guillaume Algis
7
@ GuillaumeAlgis Ich würde argumentieren, dass, obwohl dies angegebenes Verhalten ist, es überhaupt nicht das ist, was man erwarten würde, wenn man Autolayout verwendet. Ich habe einen Fehler gemeldet und würde andere dazu ermutigen, auch einen einzureichen.
Memmons
Wenn Sie hier auf den Radarfehler verlinken können, können wir darauf klicken und +1 darauf?
Gprasant
1
aus der titleEdgeInsetDokumentation: The insets you specify are applied to the title rectangle after that rectangle has been sized to fit the button’s text. Thus, positive inset values may actually clip the title text. Wenn Sie also einen Einschub hinzufügen, zwingen Sie die Schaltfläche, den Text sicher
Marco Pappalardo

Antworten:

192

Sie können dies lösen, ohne Methoden überschreiben oder eine beliebige Breitenbeschränkung festlegen zu müssen. Sie können alles in Interface Builder wie folgt tun.

  • Die intrinsische Schaltflächenbreite wird aus der Titelbreite plus der Symbolbreite plus den linken und rechten Inhaltskanteneinfügungen abgeleitet .

  • Wenn eine Schaltfläche sowohl ein Bild als auch einen Text enthält, werden sie als Gruppe zentriert, ohne dass ein Abstand dazwischen besteht.

  • Wenn Sie einen linken Inhaltseinsatz hinzufügen, wird dieser relativ zum Text berechnet, nicht zum Text + Symbol.

  • Wenn Sie einen negativen linken Bildeinsatz festlegen, wird das Bild nach links herausgezogen, die Gesamtbreite der Schaltflächen bleibt jedoch unberührt.

  • Wenn Sie einen negativen linken Bildeinsatz festlegen, verwendet das tatsächliche Layout die Hälfte dieses Werts. Um einen linken Einschub von -20 Punkten zu erhalten, müssen Sie im Interface Builder einen Wert für den linken Einschub von -40 Punkten verwenden.

Sie stellen also einen ausreichend großen linken Inhaltseinsatz bereit, um Platz für beide gewünschten linken Einfügungen zu schaffen und den inneren Abstand zwischen dem Symbol und dem Text zu schaffen, und verschieben das Symbol dann nach links, indem Sie zwischen dem Symbol und dem Text verdoppeln. Das Ergebnis ist eine Schaltfläche mit gleichen linken und rechten Inhaltseinfügungen und einem Text- und Symbolpaar, die als Gruppe zentriert sind und zwischen denen ein bestimmter Abstand vorhanden ist.

Einige Beispielwerte:

// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
jaredsinclair
quelle
Warum verwendet das tatsächliche Layout die Hälfte des negativen linken Einschubwerts? Ich bin auf das gleiche Problem gestoßen!
Tony Lin
1
Es ist großartig, dass es eine Problemumgehung gibt, aber ich hoffe, dass dies nicht verwendet wird, um das seltsame Verhalten von zu rechtfertigen UIButton.
Funkt7
205

Sie können dies in Interface Builder zum Laufen bringen (ohne Code zu schreiben), indem Sie eine Kombination aus negativen und positiven Titel- und Inhaltseinfügungen verwenden.

Geben Sie hier die Bildbeschreibung ein

Update : Xcode 7 hat einen Fehler, bei dem Sie keine negativen Werte in das RightFeld Einfügen eingeben können , aber Sie können die Schrittsteuerung daneben verwenden, um den Wert zu verringern. (Danke Stuart)

Dadurch wird ein Abstand von 8pt zwischen dem Bild und dem Titel hinzugefügt und die intrinsische Breite der Schaltfläche um den gleichen Betrag erhöht. So was:

Geben Sie hier die Bildbeschreibung ein

n.Drake
quelle
2
Es verwendet contentEdgeInsets (was nicht fehlerhaft ist), damit das automatische Layout die Tastenbreite erhöht. Bewegen Sie das Etikett in den leeren Bereich rechts. Clevere Möglichkeit, den Fehler beim Einfügen der Titelkante zu umgehen.
Ugur
7
Dieser Trick funktioniert nicht mehr. Der Interface Builder akzeptiert keine negativen Werte mehr im RightFeld.
Joris Mans
7
@JorisMans Sie können nicht geben negative Werte in, aber es funktioniert für mich durch die Schrittsteuer rechts von dem Textfeld mit dem erforderlichen negativen Wert zurücktreten ... go figure!
Stuart
3
Dies sollte die erste Antwort sein, warum ist es hier unten? Ich habe die anderen 5 ausprobiert, bevor ich diese gefunden habe ...
Lord Zsolt
2
Ich habe Right of Content Inset 16 gemacht, um den Text in UIButton zu zentrieren
coolcool1994
96

Warum nicht die intrinsicContentSizeMethode in UIView überschreiben? Beispielsweise:

- (CGSize) intrinsicContentSize
{
    CGSize s = [super intrinsicContentSize];

    return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
                      s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}

Dies sollte dem Autolayout-System mitteilen, dass die Schaltfläche vergrößert werden soll, um die Einfügungen zu berücksichtigen und den vollständigen Text anzuzeigen. Ich bin nicht an meinem eigenen Computer, daher habe ich dies nicht getestet.

Maarten
quelle
1
Schaltflächen sollten meines Wissens nicht überschrieben werden. Das Problem ist, dass jeder Schaltflächentyp von einer anderen Unterklasse implementiert wird.
Sulthan
2
intrinsicContentSizeist eine Methode in UIView, nicht in UIButton, sodass Sie keine UIButton-Methoden durcheinander bringen würden. Apple glaubt nicht, dass dies ein Problem ist: "Durch Überschreiben dieser Methode kann eine benutzerdefinierte Ansicht dem Layoutsystem mitteilen, welche Größe sie basierend auf ihrem Inhalt haben möchte." Und das OP sagte nichts über verschiedene Tasten, nur die eine.
Maarten
1
Dies funktioniert definitiv und ist die Lösung, mit der ich mich entschieden habe. intrinsicContentSizeist in der Tat eine Methode in UIView und UIButton ist eine Unterklasse von UIView, sodass Sie diese Methode natürlich überschreiben können. Nichts in Apples Dokumenten sagt, dass Sie nicht sollten. Erstellen Sie einfach eine UIButton-Unterklasse mit der überschriebenen Methode von Maarten und ändern Sie Ihren UIButton im Interface Builder so, dass er vom Typ YourUIButtonSubclass ist, und er funktioniert einwandfrei.
n8tr
37
Scheint mir, intrinsicContentSizedass UIButton in den titleEdgeInsets hinzufügen sollte, ich werde einen Fehler bei Apple melden.
Programm
6
Ich stimme zu, und das gleiche gilt für imageEdgeInsets.
Ricardo Sanchez-Saez
87

Sie haben nicht angegeben, wie Sie die Einfügungen festlegen. Ich vermute also, dass Sie titleEdgeInsets verwenden, da ich den gleichen Effekt sehe, den Sie erhalten. Wenn ich stattdessen contentEdgeInsets verwende, funktioniert es ordnungsgemäß.

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}
rdelmar
quelle
Ich benutze tatsächlich titleEdgeInsets. Ich muss den Titel vom Bild entfernen, nicht das Bild vom Rand der Schaltfläche. Vielleicht sollte ich einfach ein Bild mit etwas Polsterung verwenden? Scheint aber hacky.
Ben Packard
Dies funktioniert perfekt in Kombination mit Autolayout, danke!
Cal S
3
Dies ist die bessere Lösung, da sie genau das tut, was Sie wollen, ohne intrinsicContentSize zu berühren.
RyJ
28
Dies beantwortet NICHT die Frage, wenn Sie ein Bild verwenden und den Einschub zwischen dem Bild und dem Titel anpassen müssen!
Brody Robertson
23

Und für Swift funktionierte das:

extension UIButton {
    override open var intrinsicContentSize: CGSize {
        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)
    }
}

Liebe U Swift

Pegpeg
quelle
1
Auch wenn Sie dies nicht tun sollten, ist es in diesem Fall besser, eine Unterklasse zu erstellen, da in Apple-Dokumenten ausdrücklich angegeben ist, dass die intrinsische Größe titleEdgeInsets nicht in die Berechnung einbezieht. Wenn Sie also eine Erweiterung verwenden, verletzen Sie nicht nur die Erwartungen von Apple, sondern alle anderen Entwickler, die lesen die docs.
Sirenen
18

Dieser Thread ist ein bisschen alt, aber ich bin gerade selbst darauf gestoßen und konnte ihn mit einem negativen Einschub lösen. Ersetzen Sie beispielsweise hier Ihre gewünschten Füllwerte:

UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
                                            -desiredRightPadding,
                                            -desiredTopPadding,
                                            -desiredLeftPadding);

In Kombination mit den richtigen Einschränkungen für das automatische Layout erhalten Sie eine Schaltfläche zur automatischen Größenänderung, die ein Bild und einen Text enthält! Unten mit desiredLeftPadding10 eingestellt.

Button mit Bild und Kurztext

Button mit Bild und Langtext

Sie können sehen, dass der tatsächliche Rahmen der Schaltfläche die Beschriftung nicht umfasst (da die Beschriftung außerhalb der Grenzen um 10 Punkte nach rechts verschoben ist), aber wir haben 10 Auffüllpunkte zwischen Text und Bild erreicht.

Brian Gerstle
quelle
1
Dies ist die Lösung, die ich verwendet habe, da keine Unterklassen erforderlich sind. Funktioniert nicht, wenn Ihre Schaltfläche einen Hintergrund hat, aber das ist normalerweise kein Problem mit iOS 7
José Manuel Sánchez
Dies funktioniert mit einem Hintergrundbild, wenn Sie auch den Inhaltsversatz der Schaltfläche festlegen (positiver Wert> = Titeleinfügung).
Ben Flynn
9

Für Swift 3 basierend auf der Antwort von Pegpeg :

extension UIButton {

    override open var intrinsicContentSize: CGSize {

        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)

    }

}
TheoK
quelle
Hallo. Ich möchte benutzerdefinierte Erweiterungsschaltfläche in Interfacebuilder verwenden. PLZ Hilfe
Kemdo
6

Alles oben funktionierte nicht für iOS 9+ , was ich getan habe ist:

  • Fügen Sie eine Breitenbeschränkung hinzu (für eine Mindestbreite, wenn die Schaltfläche keinen Text enthält. Die Schaltfläche wird automatisch skaliert, wenn Text bereitgestellt wird).
  • Stellen Sie die Beziehung auf Größer als oder gleich ein

Geben Sie hier die Bildbeschreibung ein

Um nun einen Rahmen um die Schaltfläche hinzuzufügen, verwenden Sie einfach die folgende Methode:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
Oritm
quelle
Warum nicht? Es skaliert automatisch mit dem Inhalt, Sie müssen nur eine Mindestbreite festlegen (die kleiner sein kann als der anzuzeigende Text)
Oritm
Weil Sie eine Mindestbreite definieren. Die gesamte Idee des automatischen Layouts besteht darin, keine explizite (minimale) Breite festzulegen.
Joris Mans
Es geht nicht um die Breite, Sie können die Breite auf 1 setzen, wenn Sie möchten, aber Autolayout muss wissen, dass die Breite gleich oder größer sein kann . Ich habe meine Antwort aktualisiert
Oritm
Sie benötigen die Breitenbeschränkung überhaupt nicht, das contentEdgeInset ist der Schlüssel, das automatische Layout verwendet es dann für die intrinsische Inhaltsgröße.
Chris Conover
5

Ich wollte ein 5-Punkt-Leerzeichen zwischen meinem UIButton-Symbol und dem Etikett einfügen. So habe ich es erreicht:

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);

Die Beziehung zwischen contentEdgeInsets, titleEdgeInsets und imageEdgeInsets erfordert ein kleines Geben und Nehmen von jedem Einschub. Wenn Sie also links links einige Einfügungen hinzufügen, müssen Sie rechts negative Einfügungen hinzufügen und rechts mehr Platz (über einen positiven Einfügungen) für den Inhalt bereitstellen.

Durch Hinzufügen eines richtigen Inhaltseinsatzes, der der Verschiebung der Titeleinfügungen entspricht, geht mein Text nicht über die Grenzen der Schaltfläche hinaus.

orj
quelle
3

Die Option ist auch im Interface Builder verfügbar. Siehe den Einschub. Ich setze links und rechts auf 3. Funktioniert wie ein Zauber.

Screenshot des Interface Builder

zeiteisen
quelle
1
Ja, wie diese Antwort erklärt, funktioniert dies, weil Sie Edge: Content hier anstelle von Edge: Title oder Edge: Image anpassen .
Smileyborg
1

Die Lösung, die ich verwende, besteht darin, der Schaltfläche eine Breitenbeschränkung hinzuzufügen. Aktualisieren Sie dann irgendwo in der Initialisierung, nachdem Ihr Text festgelegt wurde, die Breitenbeschränkung wie folgt:

self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;

Wo 8 ist, was auch immer Ihr Einschub ist.

Bob Spryn
quelle
Was ist buttonWidthConstraint?
Alexey Golikov
@AlexeyGolikov Eine NSLayoutConstraint - developer.apple.com/library/mac/documentation/AppKit/Reference/…
1in9ui5t
1
Dies ist keine großartige Lösung, denn wenn sich die Größe des intrinsischen Inhalts der Schaltfläche ändert, müssen Sie constantdie Einschränkung manuell auf den neuen Wert aktualisieren ... und wissen, wann sich die Größe des intrinsischen Inhalts der Schaltfläche ändert ohne die Schaltfläche zu unterklassifizieren.
Smileyborg
Ayup. Ich benutze diese Methode nicht mehr. Überrascht war es eine Abwertung wert, aber ¯_ (ツ) _ / ¯
Bob Spryn
Ein Aufruf von setNeedsUpdateConstraintskann "manuell" erfolgen, nachdem der Titel oder das Bild der Schaltfläche aktualisiert wurden. Von dort aus können Sie die Konstante überschreiben updateConstraintsund neu berechnen buttonWidthConstraint. Dies ist nicht unbedingt der beste Ansatz, aber er funktioniert gut genug für mich. YMMV;)
Olivier
1

Durch Aufrufen von sizeToFit () wird sichergestellt, dass contentEdgeInset wirksam wird

extension UIButton {

   open override func draw(_ rect: CGRect) { 
       self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40)
       self.sizeToFit()
   }
}
Ali
quelle