iPhone UIButton - pozycja obrazu


116

Mam UIButtonz tekstem „Przeglądaj aplikację” i UIImage(>) W Interface Builderwyglądzie:

[ (>) Explore the app ]

Ale muszę to umieścić UIImagePO tekście:

[ Explore the app (>) ]

Jak mogę przesunąć UIImagew prawo?


Możesz także użyć Interface Builder. Sprawdź moją odpowiedź tutaj: stackoverflow.com/questions/2765024/ ...
— n8tr

1
Po prostu użyj tej podklasy z kilkoma wierszami kodu: github.com/k06a/RTLButton
— k06a

Odpowiedzi:


161

Moje rozwiązanie jest dość proste

[button sizeToFit];
button.titleEdgeInsets = UIEdgeInsetsMake(0, -button.imageView.frame.size.width, 0, button.imageView.frame.size.width);
button.imageEdgeInsets = UIEdgeInsetsMake(0, button.titleLabel.frame.size.width, 0, -button.titleLabel.frame.size.width);

4
To działa świetnie! Trudno jest zrozumieć koncepcję wstawki krawędziowej. Masz jakiś pomysł, dlaczego musimy ustawić zarówno lewą, jak i prawą krawędź? Teoretycznie, jeśli przesunę tytuł w lewo, a obraz w prawo, to wystarczy. Dlaczego muszę ustawić lewą i prawą stronę?
— PokerIncome.com

3
NAJLEPSZE ROZWIĄZANIE,
— JAKIE ZNALAZŁEM

2
Ta odpowiedź działa doskonale. Zaakceptowana odpowiedź jest poprawna i wskazuje na poprawną dokumentację API, ale jest to rozwiązanie kopiuj i wklej, aby zrobić to, o co prosił OP.
— mclaughlinj

Trochę się komplikuje, gdy zaczynasz zmieniać tekst przycisku. Rozwiązanie podklasowe działało lepiej w tym przypadku.
— Brad Goss

Dziękuję za pokazanie mi, że musisz zastosować równe szerokości po lewej i prawej stronie, nie rozumiem w 100%, jak to działa (ponieważ czasami wpłynie to na rozmiar i pochodzenie), ale udało mi się rozwiązać podobne problemy używając tej metody.
— Toby

128

W iOS 9 i nowszych wydaje się, że prostym sposobem osiągnięcia tego jest wymuszenie semantyki widoku.

wprowadź opis obrazu tutaj

Lub programowo, używając:

button.semanticContentAttribute = .ForceRightToLeft

4
Chociaż spodobała mi się Twoja odpowiedź (+1), nie chcę mówić, że może to nie być „właściwy” sposób, ale jest to jeden z najłatwiejszych.
— farzadshbfn

@farzadshbfn masz rację. Zmieniłem to słowo na „proste”, wydaje się bardziej spójne.
— Alvivi

@farzadshbfn Dlaczego nie miałby to być „właściwy” sposób?
— Samman Bikram Thapa

3
@SammanBikramThapa IMHO właściwym sposobem byłoby podklasowanie UIButton i nadpisanie layoutSubviews i szacunku semanticContentAttributew naszej logice układu, zamiast zmiany semanticContentAttributesamego siebie. (zmiana podejścia semantycznego, nie będzie działać dobrze z internacjonalizacją)
— farzadshbfn

@farzadshbfn ma całkowitą rację. Bez względu na to, że ta odpowiedź zdobędzie najwięcej punktów, jest nieprawidłowa - może zepsuć interfejs użytkownika od prawej do lewej.
— Pavel Yakimenko

86

Ustaw imageEdgeInseti, titleEdgeInsetaby przesuwać komponenty w obrębie obrazu. Możesz także utworzyć przycisk przy użyciu pełnowymiarowej grafiki i użyć go jako obrazu tła przycisku (następnie użyj, titleEdgeInsetsaby przesunąć tytuł).


8
Samo ustawienie wstawek wymaga znacznie mniej kodu niż implementacja podklasy. To jest cały sens wstawek. Manipulowanie ramkami w widokach podrzędnych (których nie utworzyłeś) przypomina bardziej hackowanie.
— Kim André Sand

6
@kimsnarf, naprawdę? Poprawianie wstawek za każdym razem, gdy wprowadzasz niewielką zmianę w rozmiarze obrazu lub długości tytułu, wymaga dużo mniej pracy (i mniej hakowania)?
— Kirk Woll,

54

Odpowiedź Raymonda W. jest tutaj najlepsza. Podklasa UIButton z niestandardowym układemSubviews. Niezwykle proste do zrobienia, oto implementacja layoutSubviews, która zadziałała dla mnie:

- (void)layoutSubviews
{
    // Allow default layout, then adjust image and label positions
    [super layoutSubviews];

    UIImageView *imageView = [self imageView];
    UILabel *label = [self titleLabel];

    CGRect imageFrame = imageView.frame;
    CGRect labelFrame = label.frame;

    labelFrame.origin.x = imageFrame.origin.x;
    imageFrame.origin.x = labelFrame.origin.x + CGRectGetWidth(labelFrame);

    imageView.frame = imageFrame;
    label.frame = labelFrame;
}

2
Tak jest lepiej w przypadku, gdy trzeba zarządzać wieloma przyciskami, ale muszę zmienić tylko jeden :)
— Pavel Yakimenko

2
Jeśli obraz przycisku ma wartość zero, wynik etykiety jest nieprawidłowy, prawdopodobnie dlatego, że UIImageView nie jest wstawiony (testowane w systemie iOS 6.0). Powinieneś rozważyć edycję ramek tylko wtedy, gdy imageView.image jest różna od nil.
— Scakko

2
Sugerowałbym następujące ulepszenie tej odpowiedzi, aby oba widoki pozostały wyśrodkowane: 'CGFloat cumulativeWidth = CGRectGetWidth (imageFrame) + CGRectGetWidth (labelFrame) + 10; CGFloat aboveWidth = CGRectGetWidth (self.bounds) - cumulativeWidth; labelFrame.origin.x = nadmierna szerokość / 2; imageFrame.origin.x = CGRectGetMaxX (labelFrame) + 10; '
— i-konov

To się psuje na iOS 7 dla mnie. Ktoś jeszcze? Działa dobrze na iOS 8.
— rounak

1
Nie obsługuj w ogóle iOS7, a twój problem zniknie. I tak nie powinieneś go wspierać.
— Gil Sand

30

A co z podklasą UIButtoni nadpisywaniem layoutSubviews?

Następnie przetwarzanie końcowe lokalizacji self.imageView&self.titleLabel


4
Jest to o wiele łatwiejsze niż wszystkie inne porady (takie jak ta powyżej) dotyczące próby prawidłowego umieszczenia wszystkiego za pomocą ręcznie dostrojonych wstawek.
— Steven Kramer

13

Innym prostym sposobem (który NIE jest tylko iOS 9) jest podklasa UIButton, aby zastąpić te dwie metody

override func titleRectForContentRect(contentRect: CGRect) -> CGRect {
    var rect = super.titleRectForContentRect(contentRect)
    rect.origin.x = 0
    return rect
}

override func imageRectForContentRect(contentRect: CGRect) -> CGRect {
    var rect = super.imageRectForContentRect(contentRect)
    rect.origin.x = CGRectGetMaxX(contentRect) - CGRectGetWidth(rect)
    return rect
}

contentEdgeInsets jest już brane pod uwagę przy użyciu super.


Dzięki! Podoba mi się to o wiele bardziej niż odpowiedzi, które podklasy i nadpisują układSubviews () :)
— Joe Susnick

1
W mojej skromnej opinii najlepszy sposób na zrobienie tego :)
— DrPatience

9

Wymuszanie „od prawej do lewej” dla przycisku nie jest opcją, jeśli Twoja aplikacja obsługuje zarówno „od lewej do prawej”, jak i „od prawej do lewej”.

Rozwiązanie, które u mnie zadziałało, to podklasa, którą można dodać do przycisku w Storyboard i dobrze współpracuje z ograniczeniami (testowane w iOS 11):

class ButtonWithImageAtEnd: UIButton {

    override func layoutSubviews() {
        super.layoutSubviews()

        if let imageView = imageView, let titleLabel = titleLabel {
            let padding: CGFloat = 15
            imageEdgeInsets = UIEdgeInsets(top: 5, left: titleLabel.frame.size.width+padding, bottom: 5, right: -titleLabel.frame.size.width-padding)
            titleEdgeInsets = UIEdgeInsets(top: 0, left: -imageView.frame.width, bottom: 0, right: imageView.frame.width)
        }

    }

}

Gdzie „dopełnienie” oznaczałoby przestrzeń między tytułem a obrazem.


Oczywiście .forceRightToLeftjest opcja! Po prostu użyj przeciwnej wartości ( .forceLeftToRight), jeśli UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft.
— manmal

5

W Swift:

override func layoutSubviews(){
    super.layoutSubviews()

    let inset: CGFloat = 5

    if var imageFrame = self.imageView?.frame,
       var labelFrame = self.titleLabel?.frame {

       let cumulativeWidth = imageFrame.width + labelFrame.width + inset
       let excessiveWidth = self.bounds.width - cumulativeWidth
       labelFrame.origin.x = excessiveWidth / 2
       imageFrame.origin.x = labelFrame.origin.x + labelFrame.width + inset

       self.imageView?.frame = imageFrame
       self.titleLabel?.frame = labelFrame  
    }
}

4

Opierając się na odpowiedzi @split ...

Odpowiedź jest fantastyczna, ale ignoruje fakt, że przycisk może mieć niestandardowe wstawki obrazu i tytułu, które są ustawione wcześniej (np. W scenorysie).

Na przykład możesz chcieć, aby obraz miał dopełnienie od góry i dołu kontenera, ale nadal przesuwaj obraz na prawą stronę przycisku.

Rozszerzyłem koncepcję tą metodą: -

- (void) moveImageToRightSide {
    [self sizeToFit];

    CGFloat titleWidth = self.titleLabel.frame.size.width;
    CGFloat imageWidth = self.imageView.frame.size.width;
    CGFloat gapWidth = self.frame.size.width - titleWidth - imageWidth;
    self.titleEdgeInsets = UIEdgeInsetsMake(self.titleEdgeInsets.top,
                                            -imageWidth + self.titleEdgeInsets.left,
                                            self.titleEdgeInsets.bottom,
                                            imageWidth - self.titleEdgeInsets.right);

    self.imageEdgeInsets = UIEdgeInsetsMake(self.imageEdgeInsets.top,
                                            titleWidth + self.imageEdgeInsets.left + gapWidth,
                                            self.imageEdgeInsets.bottom,
                                            -titleWidth + self.imageEdgeInsets.right - gapWidth);
}

2
// Get the size of the text and image
CGSize buttonLabelSize = [[self.button titleForState:UIControlStateNormal] sizeWithFont:self.button.titleLabel.font];
CGSize buttonImageSize = [[self.button imageForState:UIControlStateNormal] size];

// You can do this line in the xib too:
self.button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;

// Adjust Edge Insets according to the above measurement. The +2 adds a little space 
self.button.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, -(buttonLabelSize.width+2));
self.button.titleEdgeInsets = UIEdgeInsetsMake(0, 0, 0, buttonImageSize.width+2);

Spowoduje to utworzenie przycisku wyrównanego do prawej strony, na przykład:

[           button label (>)]

Przycisk nie dopasowuje swojej szerokości do kontekstu, więc po lewej stronie etykiety pojawi się spacja. Możesz rozwiązać ten problem, obliczając szerokość ramki przycisku na podstawie właściwości buttonLabelSize.width i buttonImageSize.width.


1
button.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;

0

To rozwiązanie działa na iOS 7 i nowszych

Po prostu podklasa UIButton

@interface UIButton (Image)

- (void)swapTextWithImage;

@end

@implementation UIButton (Image)

- (void)swapTextWithImage {
   const CGFloat kDefaultPadding = 6.0f;
   CGSize buttonSize = [self.titleLabel.text sizeWithAttributes:@{
                                                               NSFontAttributeName:self.titleLabel.font
                                                               }];

   self.titleEdgeInsets = UIEdgeInsetsMake(0, -self.imageView.frame.size.width, 0, self.imageView.frame.size.width);
   self.imageEdgeInsets = UIEdgeInsetsMake(0, buttonSize.width + kDefaultPadding, 0, -buttonSize.width); 
}

@end

Użycie (gdzieś w twojej klasie):

[self.myButton setTitle:@"Any text" forState:UIControlStateNormal];
[self.myButton swapTextWithImage];

0

Opierając się na wcześniejszych odpowiedziach. Jeśli chcesz mieć margines między ikoną a tytułem przycisku, kod musi się nieco zmienić, aby zapobiec unoszeniu się etykiety i ikony ponad granicami przycisków o rozmiarze wewnętrznym.

let margin = CGFloat(4.0)
button.titleEdgeInsets = UIEdgeInsetsMake(0, -button.imageView.frame.size.width, 0, button.imageView.frame.size.width);
button.imageEdgeInsets = UIEdgeInsetsMake(0, button.titleLabel.frame.size.width, 0, -button.titleLabel.frame.size.width)
button.contentEdgeInsets = UIEdgeInsetsMake(0, margin, 0, margin)

Ostatnia linia kodu jest ważna dla samoistnego obliczenia rozmiaru zawartości dla automatycznego układu.


0

Oto mój własny sposób, aby to zrobić (po około 10 latach)

  1. Podklasa z UIButton (Button, ponieważ żyjemy w erze Swift)
  2. Umieść obraz i etykietę w widoku stosu.
class CustomButton: Button {

    var didLayout: Bool = false // The code must be called only once

    override func layoutSubviews() {
        super.layoutSubviews()
        if !didLayout, let imageView = imageView, let titleLabel = titleLabel {
            didLayout = true
            let stack = UIStackView(arrangedSubviews: [titleLabel, imageView])
            addSubview(stack)
            stack.edgesToSuperview() // I use TinyConstraints library. You could handle the constraints directly
            stack.axis = .horizontal
        }
    }
}

0

Rozwiązanie jednoliniowe w Swift:

// iOS 9 and Onwards
button.semanticContentAttribute = .forceRightToLeft

To nie jest dobre, ponieważ zepsuje układ dla wszystkich użytkowników RTL. Będziesz musiał dla nich poruszać się od lewej do prawej. Lepiej wypróbuj inne rozwiązanie stąd.
— Pavel Yakimenko
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.