Autolayout - rzeczywisty rozmiar UIButton nie uwzględnia wstawek tytułów

196

Jeśli mam UIButton ustawiony za pomocą autolayout, jego rozmiar ładnie dostosowuje się do zawartości.

Jeśli button.imageustawię obraz jako , rozmiar instruktażowy ponownie wydaje się to uwzględniać.

Jeśli jednak poprawię titleEdgeInsetsprzycisk, układ nie bierze tego pod uwagę i zamiast tego obcina tytuł przycisku.

Jak mogę się upewnić, że wewnętrzna szerokość przycisku odpowiada wstawce?

wprowadź opis zdjęcia tutaj

Edytować:

Używam następujących:

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

Celem jest dodanie pewnej separacji między obrazem a tekstem.

Ben Packard
źródło
3
Czy zapisałeś to jako radar? Z pewnością wydaje się to być błędem w wewnętrznych obliczeniach wielkości UIButton.
Ryan Poolos,
1
Byłem gotowy złożyć radar, ale tak naprawdę wydaje się to być oczekiwane zachowanie. Jest to udokumentowane we właściwościach * EdgeInsets UIButton : „Określone wstawki są stosowane do prostokąta tytułowego po tym, jak prostokąt zostanie dopasowany do tekstu przycisku. Zatem dodatnie wartości wstawki mogą faktycznie przyciąć tekst tytułowy. [...] przycisk nie używa tej właściwości do określania intrinsicContentSize i sizeThatFits: ”
Guillaume Algis
7
@GuillaumeAlgis Argumentowałbym, że chociaż jest to określone zachowanie, wcale nie jest to, czego można się spodziewać podczas korzystania z autolayout. Złożyłem błąd i zachęcam również innych do jego zgłoszenia.
memmons
Jeśli możesz tutaj link do błędu radaru, czy możemy go kliknąć i dać +1?
gprasant
1
z titleEdgeInsetdokumentacji: 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. Dodając wstawkę, na pewno
zmuszasz

Odpowiedzi:

192

Możesz rozwiązać ten problem bez konieczności zastępowania jakichkolwiek metod lub ustawiania dowolnego ograniczenia szerokości. Możesz to zrobić w Konstruktorze interfejsów w następujący sposób.

  • Wewnętrzna szerokość przycisku pochodzi od szerokości tytułu plus szerokość ikony oraz wstawki lewej i prawej krawędzi treści .

  • Jeśli przycisk zawiera zarówno obraz, jak i tekst, są one wyśrodkowane jako grupa, bez odstępów między nimi.

  • Jeśli dodasz lewą wstawkę treści, jest ona obliczana względem tekstu, a nie tekstu + ikony.

  • Po ustawieniu ujemnego wstawki lewego obrazu obraz zostanie wyciągnięty w lewo, ale ogólna szerokość przycisku nie ulegnie zmianie.

  • Jeśli ustawisz wstawkę ujemnego lewego obrazu, rzeczywisty układ wykorzystuje połowę tej wartości. Tak więc, aby uzyskać wstawkę o wartości -20 punktów w lewo, musisz użyć wartości wstawki o wartości -40 punktów w Konstruktorze interfejsu.

Zapewniasz więc wystarczająco dużą lewą wstawkę zawartości, aby stworzyć miejsce zarówno na pożądaną lewą wstawkę, jak i wewnętrzne wypełnienie między ikoną a tekstem, a następnie przesuń ikonę w lewo, podwajając żądaną ilość wypełnienia między ikoną a tekstem. Rezultatem jest przycisk z równymi lewymi i prawymi wstawkami treści oraz parę tekstu i ikon, które są wyśrodkowane jako grupa, z określoną ilością dopełnienia między nimi.

Niektóre przykładowe wartości:

// 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
źródło
dlaczego rzeczywisty układ wykorzystuje połowę ujemnej wartości lewej wstawki? Napotkałem ten sam problem!
Tony Lin,
1
To wspaniale, że istnieje obejście, ale mam nadzieję, że nie zostało to wykorzystane do uzasadnienia dziwnego zachowania UIButton.
funk7
205

Możesz sprawić, by działało to w Konstruktorze interfejsów (bez pisania kodu), używając kombinacji ujemnych i dodatnich wstawek tytułu i zawartości.

wprowadź opis zdjęcia tutaj

Aktualizacja : Xcode 7 zawiera błąd, w którym nie można wprowadzić wartości ujemnych w polu RightWypełnienie, ale można użyć kontrolki krokowej obok niego, aby zmniejszyć wartość. (Dzięki Stuart)

Spowoduje to dodanie 8 punktów odstępu między obrazem a tytułem i zwiększy wewnętrzną szerokość przycisku o tę samą wartość. Lubię to:

wprowadź opis zdjęcia tutaj

n.Drake
źródło
2
Korzysta z contentEdgeInsets (co nie jest błędem), aby umożliwić autolayoutowi zwiększenie szerokości przycisku. I przenieś etykietę na puste miejsce po prawej stronie. Sprytny sposób obejścia błędu wstawiania krawędzi tytułu.
ugur
7
Ta sztuczka już nie działa. Konstruktor interfejsów nie akceptuje już ujemnych wartości w Rightpolu.
Joris Mans,
7
@JorisMans Nie możesz wpisać wartości ujemnych, ale zadziałało dla mnie, używając elementu sterującego krokiem po prawej stronie pola tekstowego, aby zejść do wymaganej wartości ujemnej ... idź rysunek!
Stuart,
3
To powinna być pierwsza odpowiedź, dlaczego tu jest? Wypróbowałem pozostałe 5, zanim znalazłem to ...
Lord Zsolt
2
Wprowadziłem wstawkę Prawa treści 16, aby wyśrodkować tekst w UIButton
coolcool1994
96

Dlaczego nie zastąpić intrinsicContentSizemetody w UIView? Na przykład:

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

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

Powinno to powiedzieć systemowi autoukładu, że powinien zwiększyć rozmiar przycisku, aby umożliwić wstawki i wyświetlić pełny tekst. Nie mam własnego komputera, więc tego nie przetestowałem.

Maarten
źródło
1
O ile wiem, nie należy zastępować przycisków. Problem polega na tym, że każdy typ przycisku jest implementowany przez inną podklasę.
Sulthan
2
intrinsicContentSizejest metodą na UIView, a nie UIButton, więc nie będziesz bałaganu w żadnej z metod UIButton. Apple nie uważa, że ​​to problem: „Przesłonięcie tej metody pozwala niestandardowemu widokowi komunikować się z układem układu o tym, jaki rozmiar chciałby być oparty na jego zawartości”. I OP nie powiedział nic o różnych przyciskach, tylko o jednym.
Maarten
1
To zdecydowanie działa i jest to rozwiązanie, z którym poszedłem. intrinsicContentSizejest rzeczywiście metodą UIView, a UIButton jest podklasą UIView, więc oczywiście można zastąpić tę metodę; nic w dokumentach Apple'a nie mówi, że nie powinieneś. Po prostu stwórz podklasę UIButton przy użyciu przesłoniętej metody Maarten i zmień UIButton w Konstruktorze interfejsów, aby był typu Twoja UIButtonSubclass i będzie działał idealnie.
n8tr
37
Wydaje mi się, intrinsicContentSizeże UIButton powinien dodać w titleEdgeInsets, zamierzam zgłosić błąd w Apple.
progrmr
6
Zgadzam się, to samo dotyczy imageEdgeInsets.
Ricardo Sanchez-Saez
87

Nie określiłeś, w jaki sposób ustawiasz wstawki, więc domyślam się, że używasz titleEdgeInsets, ponieważ widzę ten sam efekt, który otrzymujesz. Jeśli zamiast tego użyję contentEdgeInsets, działa to poprawnie.

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}
rdelmar
źródło
Rzeczywiście używam titleEdgeInsets. Muszę zdystansować tytuł od obrazu, a nie od krawędzi przycisku. Może powinienem po prostu użyć obrazu z wypełnieniem? Wydaje się jednak zuchwały.
Ben Packard,
Działa to doskonale w połączeniu z autolayout, dziękuję!
Cal S
3
Jest to lepsze rozwiązanie, ponieważ robi dokładnie to, co chcesz bez dotykania intrinsicContentSize.
RyJ
28
To NIE odpowiada na pytanie podczas używania obrazu i konieczności dostosowania wstawki między obrazem a tytułem!
Brody Robertson,
23

I dla Swift działało to:

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

Love U Swift

pegpeg
źródło
1
Nawet jeśli nie powinieneś, w tym przypadku lepiej jest podklasę, ponieważ dokumenty Apple wyraźnie stwierdzają, że rozmiar wewnętrzny nie uwzględnia w swoich obliczeniach tytułu titleEdgeInsets, a więc używając rozszerzenia naruszasz nie tylko oczekiwania Apple, ale wszystkich innych programistów, którzy czytają dokumenty
Syreny
18

Ten wątek jest trochę stary, ale sam na niego wpadłem i byłem w stanie go rozwiązać za pomocą ujemnej wstawki. Na przykład zastąp tutaj żądane wartości wypełnienia:

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

W połączeniu z odpowiednimi ograniczeniami autoukładu otrzymujesz przycisk zmiany rozmiaru, który zawiera obraz i tekst! Patrz poniżej z desiredLeftPaddingustawieniem na 10.

Przycisk z obrazkiem i krótkim tekstem

Przycisk z obrazem i długim tekstem

Widać, że rzeczywista ramka przycisku nie obejmuje etykiety (ponieważ etykieta jest przesunięta o 10 punktów w prawo, poza granicami), ale osiągnęliśmy 10 punktów wypełnienia między tekstem a obrazem.

Brian Gerstle
źródło
1
Jest to rozwiązanie, którego użyłem, ponieważ nie wymaga ono podklasowania. Nie zadziała, jeśli przycisk ma tło, ale zwykle nie jest to problem z iOS 7
José Manuel Sánchez
Będzie to działać z obrazem tła, jeśli ustawisz również przesunięcie zawartości przycisku (wartość dodatnia> = wstawka tytułu).
Ben Flynn
9

W przypadku Swift 3 opartego na odpowiedzi 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
źródło
dzień dobry. Chcę użyć niestandardowego przycisku rozszerzenia w interfejsie. plz help
kemdo
6

Wszystkie powyższe nie działały na iOS 9+ , co zrobiłem to:

  • Dodaj ograniczenie szerokości (dla minimalnej szerokości, gdy przycisk nie zawiera tekstu. Przycisk automatycznie skaluje się, jeśli tekst jest podany)
  • ustaw relację na Większa niż lub Równa

wprowadź opis zdjęcia tutaj

Teraz, aby dodać ramkę wokół przycisku, po prostu użyj metody:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
Oritm
źródło
Dlaczego nie? Automatycznie skaluje się wraz z zawartością, wystarczy ustawić minimalną szerokość (która może być mniejsza niż wyświetlany tekst)
Oritm
Ponieważ definiujesz minimalną szerokość. Cała idea autolayout polega na tym, aby zrobić to bez ustawiania żadnej wyraźnej (minimalnej) szerokości.
Joris Mans,
Nie chodzi o szerokość, możesz ustawić szerokość na 1, jeśli wolisz, ale autoukład musi wiedzieć, że szerokość może być równa lub większa . Zaktualizowałem odpowiedź
Oritm,
W ogóle nie potrzebujesz ograniczenia szerokości, contentEdgeInset jest kluczem, automatyczny układ następnie używa go do wewnętrznego rozmiaru treści.
Chris Conover,
5

Chciałem dodać 5-punktową spację między ikoną UIButton a etykietą. Oto jak to osiągnąłem:

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

Sposób, w jaki contentEdgeInsets, titleEdgeInsets i imageEdgeInsets odnoszą się do siebie, wymaga trochę dawania i odbierania z każdej wstawki. Więc jeśli dodasz trochę wstawek po lewej stronie tytułu, musisz dodać ujemną wstawkę po prawej stronie i zapewnić trochę więcej miejsca (poprzez dodatnią wstawkę) po prawej stronie zawartości.

Dodając odpowiednią wstawkę treści, aby pasowała do przesunięcia wstawek tytułu, mój tekst nie wykracza poza granice przycisku.

orj
źródło
3

Ta opcja jest również dostępna w narzędziu do tworzenia interfejsów. Zobacz wstawkę. Ustawiłem lewą i prawą na 3. Działa jak urok.

Zrzut ekranu konstruktora interfejsów

zeiteisen
źródło
1
Tak, jak wyjaśnia ta odpowiedź , powodem jest to, że dostosowujesz tutaj Edge: Content zamiast Edge: Title lub Edge: Image .
smileyborg,
1

Rozwiązaniem, którego używam, jest dodanie ograniczenia szerokości przycisku. Następnie gdzieś podczas inicjalizacji, po ustawieniu tekstu, zaktualizuj ograniczenie szerokości w następujący sposób:

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

Gdzie 8 jest tym, czym jest twoja wstawka.

Bob Spryn
źródło
Co to jest buttonWidthConstraint?
Aleksiej Golikow
@AlexeyGolikov An NSLayoutConstraint - developer.apple.com/library/mac/documentation/AppKit/Reference/…
1in9ui5t
1
To nie jest świetne rozwiązanie, ponieważ jeśli zmienia się rozmiar zawartości przycisku, musisz ręcznie zaktualizować constantograniczenie do nowej wartości ... i wiedzieć, kiedy rozmiar zawartości zmiany przycisku jest trudny bez podklasowania przycisku.
smileyborg,
Ayup. Nie używam już tej metody. Zaskoczony, że warto było głosować w dół, ale ¯_ (ツ) _ / ¯
Bob Spryn
Wezwanie do setNeedsUpdateConstraintsmożna wykonać „ręcznie” po zaktualizowaniu tytułu lub obrazu przycisku. Następnie możesz zastąpić updateConstraintsi ponownie obliczyć buttonWidthConstraintstałą. To niekoniecznie najlepsze podejście, ale działa wystarczająco dobrze dla mnie. YMMV;)
Olivier,
1

wywołanie sizeToFit () zapewnia, że ​​contentEdgeInset zostanie zastosowany

extension UIButton {

   open override func draw(_ rect: CGRect) { 
       self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40)
       self.sizeToFit()
   }
}
Ali
źródło