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?

Pavel Yakimenko
źródło
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);
Rozdzielać
źródło
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
Alvivi
źródło
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ł).

Steven Canfield
źródło
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;
}
Chris Miles
źródło
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

Raymond W.
źródło
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.

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

Luisma
źródło
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  
    }
}
ChikabuZ
źródło
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);
}
B daleko
źródło
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.

Caliss
źródło
1
button.semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
Todd Vanderlin
źródło
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];
Pavel Volobuev
źródło
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.

Kie
źródło
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
        }
    }
}
Pavel Yakimenko
źródło
0

Rozwiązanie jednoliniowe w Swift:

// iOS 9 and Onwards
button.semanticContentAttribute = .forceRightToLeft
Sunil Targe
źródło
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