Wyrównanie tekstu i obrazu na UIButton za pomocą imageEdgeInsets i titleEdgeInsets

249

Chciałbym umieścić ikonę po lewej stronie dwóch linii tekstu, tak aby między obrazem a początkiem tekstu było około 2-3 pikseli przestrzeni. Samo sterowanie jest wyrównane do środka w poziomie (ustawiane za pomocą Konstruktora interfejsów)

Przycisk przypominałby coś takiego:

|                  |
|[Image] Add To    |
|        Favorites |

Próbuję skonfigurować to z contentEdgeInset, imageEdgeInsets i titleEdgeInsets bezskutecznie. Rozumiem, że wartość ujemna rozszerza krawędź, podczas gdy wartość dodatnia zmniejsza ją, aby przesunąć ją bliżej środka.

Próbowałem:

[button setTitleEdgeInsets:UIEdgeInsetsMake(0, -image.size.width, 0, 0)];
[button setImageEdgeInsets:UIEdgeInsetsMake(0, button.titleLabel.bounds.size.width, 0, 0)];

ale to nie wyświetla go poprawnie. Poprawiałem wartości, ale przejście od powiedzmy -5 do -10 na lewej wartości wstawki nie wydaje się przesuwać jej w oczekiwany sposób. -10 przesuwa tekst do końca w lewo, więc spodziewałem się, że -5 przeskoczy go do połowy z lewej strony, ale tak nie jest.

Jaka jest logika wypustek? Nie znam umiejscowienia obrazu i powiązanej terminologii.

Użyłem tego pytania SO jako odniesienia, ale coś w moich wartościach jest niewłaściwe. UIButton: jak wyśrodkować obraz i tekst za pomocą imageEdgeInsets i titleEdgeInsets?

Justin Galzic
źródło

Odpowiedzi:

392

Zgadzam się z dokumentacją w sprawie imageEdgeInsetsititleEdgeInsets powinien być lepszy, ale wymyśliłem, jak uzyskać prawidłowe pozycjonowanie bez uciekania się do prób i błędów.

Ogólny pomysł znajduje się w tym pytaniu , ale było tak, jeśli chciałeś, aby tekst i obraz były wyśrodkowane. Nie chcemy, aby obraz i tekst były wyśrodkowane indywidualnie, chcemy, aby obraz i tekst były wyśrodkowane razem jako jedna całość. Tak właśnie robi UIButton, dlatego musimy po prostu dostosować odstępy.

CGFloat spacing = 10; // the amount of spacing to appear between image and title
tabBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
tabBtn.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);

Zmieniłem to również w kategorię dla UIButton, dzięki czemu będzie łatwy w użyciu:

UIButton + Position. H

@interface UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing;

@end

UIButton + Position.m

@implementation UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing {
    self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);
}

@end

Teraz muszę tylko:

[button centerButtonAndImageWithSpacing:10];

I dostaję to, czego potrzebuję za każdym razem. Nigdy więcej ręcznego bałaganu z wypustkami krawędzi.

EDYCJA: Zamiana obrazu i tekstu

W odpowiedzi na @Javal w komentarzach

Korzystając z tego samego mechanizmu, możemy zamienić obraz i tekst. Aby wykonać zamianę, po prostu użyj ujemnych odstępów, ale także uwzględnij szerokość tekstu i obrazu. Będzie to wymagało znajomości ramek i wykonania układu.

[self.view layoutIfNeeded];
CGFloat flippedSpacing = -(desiredSpacing + button.currentImage.size.width + button.titleLabel.frame.size.width);
[button centerButtonAndImageWithSpacing:flippedSpacing];

Oczywiście prawdopodobnie będziesz chciał stworzyć do tego przyjemną metodę, potencjalnie dodając metodę drugiej kategorii, która pozostanie jako ćwiczenie dla czytelnika.

Kekoa
źródło
Jeśli mam różne tytuły dla normalnego i wyróżnionego, jak mogę go ponownie wyśrodkować, gdy użytkownik podświetli i odznaczy przycisk?
user102008
@ user102008 Różne tytuły całkowicie? A może po prostu różne kolory? Pozycjonowanie nie powinno się zmieniać, jeśli używasz [UIButton setTitleColor:forState:], a nawet [UIButton setTitle:forState:].
Kekoa
5
Jak naprawić [przycisk sizeToFit], aby odpowiednio dopasował rozmiar?
RonLugge
@RonLugge Nie jestem pewien, dlaczego potrzebujesz sizeToFit, więc nie mogę odpowiedzieć na twoje pytanie. Działa dla mnie dobrze bez użycia sizeToFit.
Kekoa
Potrzebuję sizeToFit, ponieważ przyciski / tekst są dynamiczne, więc muszę dopasować rozmiar przycisku do etykiety (zdefiniowanej przez użytkownika). Problem polega na tym, że nie kompensuje to dodatkowej przestrzeni.
Skończyłem
397

Jestem trochę spóźniony na tę imprezę, ale myślę, że mam coś przydatnego do dodania.

Odpowiedź Kekoa jest świetna, ale, jak wspomina RonLugge, może sprawić, że przycisk przestanie szanować sizeToFitlub, co ważniejsze, może spowodować przycięcie przycisku, gdy jego rozmiar jest wewnętrzny. Yikes!

Najpierw jednak

Krótkie wyjaśnienie tego, w co wierzę imageEdgeInsetsi titleEdgeInsetsdziałam:

Dokumenty doimageEdgeInsets mają następujące części do powiedzenia:

Ta właściwość służy do zmiany rozmiaru i położenia efektywnego prostokąta rysunkowego dla obrazu przycisku. Możesz określić inną wartość dla każdej z czterech wstawek (górny, lewy, dolny, prawy). Wartość dodatnia zmniejsza się lub wstawia tę krawędź - przesuwając ją bliżej środka przycisku. Wartość ujemna rozszerza lub wyrównuje tę krawędź.

Uważam, że ta dokumentacja została napisana z wyobrażeniem, że przycisk nie ma tytułu, tylko obraz. Takie myślenie jest o wiele bardziej sensowne i zachowuje się jak UIEdgeInsetszwykle. Zasadniczo ramka obrazu (lub tytuł, z titleEdgeInsets) jest przesuwana do wewnątrz w przypadku dodatnich wypustek i na zewnątrz w przypadku ujemnych wypustek.

Ok, i co?

Jadę tam! Oto, co masz domyślnie, ustawiając obraz i tytuł (ramka przycisku jest zielona, ​​aby pokazać, gdzie się znajduje):

Obraz początkowy;  brak spacji między tytułem a obrazem.

Jeśli chcesz wprowadzić odstępy między obrazem a tytułem, nie powodując zmiażdżenia żadnego z nich, musisz ustawić cztery różne wstawki, po dwa na każdym obrazie i tytule. Jest tak, ponieważ nie chcesz zmieniać rozmiarów ramek tych elementów, a jedynie ich pozycje. Kiedy zaczniesz myśleć w ten sposób, konieczna zmiana doskonałej kategorii Kekoa stanie się jasna:

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
}

@end

Ale czekaj , mówisz, kiedy to robię, otrzymuję to:

Odstępy są dobre, ale obraz i tytuł znajdują się poza ramką widoku.

O tak! Zapomniałem, doktorzy ostrzegali mnie przed tym. Mówią częściowo:

Ta właściwość służy tylko do pozycjonowania obrazu podczas układu. Przycisk nie używa tej właściwości do określania intrinsicContentSizei sizeThatFits:.

Ale jest właściwość, która może pomóc, i to jest contentEdgeInsets. Dokumenty do tego mówią częściowo:

Przycisk używa tej właściwości do określenia intrinsicContentSizei sizeThatFits:.

To brzmi dobrze. Ponownie poprawmy kategorię:

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
    self.contentEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, insetAmount);
}

@end

A co dostajesz

Odstępy i ramka są teraz poprawne.

Wygląda mi na zwycięzcę.


Pracujesz w Swift i nie chcesz w ogóle myśleć? Oto ostateczna wersja rozszerzenia w Swift:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
        titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
        contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}
Ravron
źródło
25
Co za świetna odpowiedź! Tak, kilka lat spóźnienie na imprezę, ale to rozwiązało problem intrinsicContentSizebycia niepoprawnym, co jest bardzo ważne w tych dniach z automatycznym układem, odkąd oryginalna odpowiedź została zaakceptowana.
Acey
2
A jeśli chcesz mieć taki sam odstęp między zewnętrzną spacingstroną przycisku a obrazem i etykietą, dodaj do każdej z czterech wartości self.contentEdgeInsets, tak jak:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
Erik van der Neut
1
Świetna odpowiedź, szkoda, że ​​nie działa całkiem dobrze, gdy obraz jest wyrównany do prawej, a długość tekstu może się różnić.
jlpiedrahita
2
Prawie idealna odpowiedź! Jedyną brakującą rzeczą jest to, że wstawki obrazu i tytułu powinny zostać odwrócone, gdy działają na interfejsie od prawej do lewej.
jeeeyul
jak ustawić współczynnik obrazu na 1 do 1
Wczoraj Muratov,
39

W interfejsie Konstruktora. Wybierz przycisk UIB -> Inspektor atrybutów -> Krawędź = Tytuł i zmodyfikuj wstawki krawędzi

Obywatel
źródło
38

Także jeśli chcesz zrobić coś podobnego do

wprowadź opis zdjęcia tutaj

Potrzebujesz

1. Ustaw wyrównanie w poziomie i pionie dla przycisku do

wprowadź opis zdjęcia tutaj

  1. Znajdź wszystkie wymagane wartości i ustaw UIImageEdgeInsets

            CGSize buttonSize = button.frame.size;
            NSString *buttonTitle = button.titleLabel.text;
            CGSize titleSize = [buttonTitle sizeWithAttributes:@{ NSFontAttributeName : [UIFont camFontZonaProBoldWithSize:12.f] }];
            UIImage *buttonImage = button.imageView.image;
            CGSize buttonImageSize = buttonImage.size;
    
            CGFloat offsetBetweenImageAndText = 10; //vertical space between image and text
    
            [button setImageEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 - offsetBetweenImageAndText,
                                                        (buttonSize.width - buttonImageSize.width) / 2,
                                                        0,0)];                
            [button setTitleEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 + buttonImageSize.height + offsetBetweenImageAndText,
                                                        titleSize.width + [button imageEdgeInsets].left > buttonSize.width ? -buttonImage.size.width  +  (buttonSize.width - titleSize.width) / 2 : (buttonSize.width - titleSize.width) / 2 - buttonImage.size.width,
                                                        0,0)];

Umożliwi to ustawienie tytułu i obrazu na przycisku.

Należy również pamiętać o aktualizacji tego przy każdym przekaźniku


Szybki

import UIKit

extension UIButton {
    // MARK: - UIButton+Aligment

    func alignContentVerticallyByCenter(offset:CGFloat = 10) {
        let buttonSize = frame.size

        if let titleLabel = titleLabel,
            let imageView = imageView {

            if let buttonTitle = titleLabel.text,
                let image = imageView.image {
                let titleString:NSString = NSString(string: buttonTitle)
                let titleSize = titleString.sizeWithAttributes([
                    NSFontAttributeName : titleLabel.font
                    ])
                let buttonImageSize = image.size

                let topImageOffset = (buttonSize.height - (titleSize.height + buttonImageSize.height + offset)) / 2
                let leftImageOffset = (buttonSize.width - buttonImageSize.width) / 2
                imageEdgeInsets = UIEdgeInsetsMake(topImageOffset,
                                                   leftImageOffset,
                                                   0,0)

                let titleTopOffset = topImageOffset + offset + buttonImageSize.height
                let leftTitleOffset = (buttonSize.width - titleSize.width) / 2 - image.size.width

                titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset,
                                                   leftTitleOffset,
                                                   0,0)
            }
        }
    }
}
gbk
źródło
29

Dzięki temu możesz uniknąć wielu problemów -

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

Spowoduje to automatyczne wyrównanie wszystkich treści do lewej (lub gdziekolwiek chcesz)

Swift 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
Nishant
źródło
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; // Poprawione
literówki
25

W Xcode 8.0 możesz to po prostu zmienićinsets rozmiar inspektora.

Wybierz przycisk UIB -> Inspektor atrybutów -> przejdź do inspektora rozmiarów i zmodyfikuj wstawki treści, obrazu i tytułu.

wprowadź opis zdjęcia tutaj

A jeśli chcesz zmienić obraz po prawej stronie, możesz po prostu zmienić właściwość semantyczną na Force Right-to-leftw Inspektorze atrybutów.

wprowadź opis zdjęcia tutaj

Sahil
źródło
w moim przypadku obraz przycisku nigdy nie przesuwa się na prawą stronę tekstu za pomocą xcode 10? możesz pomóc?
Satish Mavani,
Cześć Satish, działa to również dobrze z Xcode 10. Mam nadzieję, że ustawiasz obraz, a nie obraz tła, a także możesz zmienić owady obrazu za pomocą Inspektora rozmiarów.
Sahil
18

Jestem również trochę spóźniony na tę imprezę, ale myślę, że mam coś przydatnego do dodania: o).

Utworzyłem UIButtonpodklasę, której celem jest wybranie, gdzie obraz przycisku ma układ, w pionie lub w poziomie.

Oznacza to, że możesz zrobić tego rodzaju przyciski: inny rodzaj przycisków

Oto szczegóły dotyczące tworzenia tych przycisków w mojej klasie:

func makeButton (imageVerticalAlignment:LayoutableButton.VerticalAlignment, imageHorizontalAlignment:LayoutableButton.HorizontalAlignment, title:String) -> LayoutableButton {
    let button = LayoutableButton ()

    button.imageVerticalAlignment = imageVerticalAlignment
    button.imageHorizontalAlignment = imageHorizontalAlignment

    button.setTitle(title, for: .normal)

    // add image, border, ...

    return button
}

let button1 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .left, title: "button1")
let button2 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .right, title: "button2")
let button3 = makeButton(imageVerticalAlignment: .top, imageHorizontalAlignment: .center, title: "button3")
let button4 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button4")
let button5 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button5")
button5.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

Aby to zrobić, dodałem 2 atrybuty: imageVerticalAlignmenti imageHorizontalAlignment. Oczywiście, jeśli twój przycisk ma tylko obrazek lub tytuł ... w ogóle nie używaj tej klasy!

Dodałem również atrybut o nazwie imageToTitleSpacing który pozwala dostosować przestrzeń między tytułem a obrazem.

Ta klasa stara się być jak najlepiej kompatybilnym, jeśli chcesz używać imageEdgeInsets, titleEdgeInsetsi contentEdgeInsetsbezpośrednio lub w połączeniu z nowymi atrybutami układu.

Jak wyjaśnia @ravron, staram się, aby krawędź zawartości przycisku była poprawna (jak widać z czerwonymi ramkami).

Możesz go również użyć w Konstruktorze interfejsów:

  1. Utwórz przycisk UIB
  2. Zmień klasę przycisku
  3. Dostosuj atrybuty układu za pomocą „środkowego”, „górnego”, „dolnego”, „lewego” lub „prawego” atrybuty przycisku

Oto kod ( gist ):

@IBDesignable
class LayoutableButton: UIButton {

    enum VerticalAlignment : String {
        case center, top, bottom, unset
    }


    enum HorizontalAlignment : String {
        case center, left, right, unset
    }


    @IBInspectable
    var imageToTitleSpacing: CGFloat = 8.0 {
        didSet {
            setNeedsLayout()
        }
    }


    var imageVerticalAlignment: VerticalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    var imageHorizontalAlignment: HorizontalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageVerticalAlignment' instead.")
    @IBInspectable
    var imageVerticalAlignmentName: String {
        get {
            return imageVerticalAlignment.rawValue
        }
        set {
            if let value = VerticalAlignment(rawValue: newValue) {
                imageVerticalAlignment = value
            } else {
                imageVerticalAlignment = .unset
            }
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageHorizontalAlignment' instead.")
    @IBInspectable
    var imageHorizontalAlignmentName: String {
        get {
            return imageHorizontalAlignment.rawValue
        }
        set {
            if let value = HorizontalAlignment(rawValue: newValue) {
                imageHorizontalAlignment = value
            } else {
                imageHorizontalAlignment = .unset
            }
        }
    }

    var extraContentEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var contentEdgeInsets: UIEdgeInsets {
        get {
            return super.contentEdgeInsets
        }
        set {
            super.contentEdgeInsets = newValue
            self.extraContentEdgeInsets = newValue
        }
    }

    var extraImageEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var imageEdgeInsets: UIEdgeInsets {
        get {
            return super.imageEdgeInsets
        }
        set {
            super.imageEdgeInsets = newValue
            self.extraImageEdgeInsets = newValue
        }
    }

    var extraTitleEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var titleEdgeInsets: UIEdgeInsets {
        get {
            return super.titleEdgeInsets
        }
        set {
            super.titleEdgeInsets = newValue
            self.extraTitleEdgeInsets = newValue
        }
    }

    //Needed to avoid IB crash during autolayout
    override init(frame: CGRect) {
        super.init(frame: frame)
    }


    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.imageEdgeInsets = super.imageEdgeInsets
        self.titleEdgeInsets = super.titleEdgeInsets
        self.contentEdgeInsets = super.contentEdgeInsets
    }

    override func layoutSubviews() {
        if let imageSize = self.imageView?.image?.size,
            let font = self.titleLabel?.font,
            let textSize = self.titleLabel?.attributedText?.size() ?? self.titleLabel?.text?.size(attributes: [NSFontAttributeName: font]) {

            var _imageEdgeInsets = UIEdgeInsets.zero
            var _titleEdgeInsets = UIEdgeInsets.zero
            var _contentEdgeInsets = UIEdgeInsets.zero

            let halfImageToTitleSpacing = imageToTitleSpacing / 2.0

            switch imageVerticalAlignment {
            case .bottom:
                _imageEdgeInsets.top = (textSize.height + imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (-textSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (-imageSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (imageSize.height + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .top:
                _imageEdgeInsets.top = (-textSize.height - imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (textSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (imageSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (-imageSize.height - imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .center:
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
                break
            case .unset:
                break
            }

            switch imageHorizontalAlignment {
            case .left:
                _imageEdgeInsets.left = -halfImageToTitleSpacing
                _imageEdgeInsets.right = halfImageToTitleSpacing
                _titleEdgeInsets.left = halfImageToTitleSpacing
                _titleEdgeInsets.right = -halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .right:
                _imageEdgeInsets.left = textSize.width + halfImageToTitleSpacing
                _imageEdgeInsets.right = -textSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.left = -imageSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.right = imageSize.width + halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .center:
                _imageEdgeInsets.left = textSize.width / 2.0
                _imageEdgeInsets.right = -textSize.width / 2.0
                _titleEdgeInsets.left = -imageSize.width / 2.0
                _titleEdgeInsets.right = imageSize.width / 2.0
                _contentEdgeInsets.left = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
                _contentEdgeInsets.right = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
            case .unset:
                break
            }

            _contentEdgeInsets.top += extraContentEdgeInsets.top
            _contentEdgeInsets.bottom += extraContentEdgeInsets.bottom
            _contentEdgeInsets.left += extraContentEdgeInsets.left
            _contentEdgeInsets.right += extraContentEdgeInsets.right

            _imageEdgeInsets.top += extraImageEdgeInsets.top
            _imageEdgeInsets.bottom += extraImageEdgeInsets.bottom
            _imageEdgeInsets.left += extraImageEdgeInsets.left
            _imageEdgeInsets.right += extraImageEdgeInsets.right

            _titleEdgeInsets.top += extraTitleEdgeInsets.top
            _titleEdgeInsets.bottom += extraTitleEdgeInsets.bottom
            _titleEdgeInsets.left += extraTitleEdgeInsets.left
            _titleEdgeInsets.right += extraTitleEdgeInsets.right

            super.imageEdgeInsets = _imageEdgeInsets
            super.titleEdgeInsets = _titleEdgeInsets
            super.contentEdgeInsets = _contentEdgeInsets

        } else {
            super.imageEdgeInsets = extraImageEdgeInsets
            super.titleEdgeInsets = extraTitleEdgeInsets
            super.contentEdgeInsets = extraContentEdgeInsets
        }

        super.layoutSubviews()
    }
}
gbitaudeau
źródło
1
Naprawiam kilka rzeczy, aby nie złamać IB error: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
nebiros
@nebiros, czy możesz wyjaśnić, co jest nie tak i jak to naprawić, proszę?
gbitaudeau
@gbitaudeau, kiedy kopiuję i wklejam skrypt, dostałem ten błąd, error: IB Designables: Failed to update auto layout status: The agent crashedponieważ init(frame: CGRect)nie został przesłonięty, dodam też @availableadnotację… możesz, diff -Naurjeśli chcesz, ;-)
nebiros
9

Mały dodatek do odpowiedzi Riley Avron na zmiany ustawień regionalnych konta:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.sharedApplication().userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .LeftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}
orxelm
źródło
6

Swift 4.x

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.shared.userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .leftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}

Zastosowanie :

button.centerTextAndImage(spacing: 10.0)
Hemang
źródło
Jak korzystać z rozmiaru obrazu custiom?
midhun p
2

Piszę kod poniżej. Działa dobrze w wersji produktu. Supprot Swift 4.2 +

extension UIButton{
 enum ImageTitleRelativeLocation {
    case imageUpTitleDown
    case imageDownTitleUp
    case imageLeftTitleRight
    case imageRightTitleLeft
}
 func centerContentRelativeLocation(_ relativeLocation: 
                                      ImageTitleRelativeLocation,
                                   spacing: CGFloat = 0) {
    assert(contentVerticalAlignment == .center,
           "only works with contentVerticalAlignment = .center !!!")

    guard (title(for: .normal) != nil) || (attributedTitle(for: .normal) != nil) else {
        assert(false, "TITLE IS NIL! SET TITTLE FIRST!")
        return
    }

    guard let imageSize = self.currentImage?.size else {
        assert(false, "IMGAGE IS NIL! SET IMAGE FIRST!!!")
        return
    }
    guard let titleSize = titleLabel?
        .systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) else {
            assert(false, "TITLELABEL IS NIL!")
            return
    }

    let horizontalResistent: CGFloat
    // extend contenArea in case of title is shrink
    if frame.width < titleSize.width + imageSize.width {
        horizontalResistent = titleSize.width + imageSize.width - frame.width
        print("horizontalResistent", horizontalResistent)
    } else {
        horizontalResistent = 0
    }

    var adjustImageEdgeInsets: UIEdgeInsets = .zero
    var adjustTitleEdgeInsets: UIEdgeInsets = .zero
    var adjustContentEdgeInsets: UIEdgeInsets = .zero

    let verticalImageAbsOffset = abs((titleSize.height + spacing) / 2)
    let verticalTitleAbsOffset = abs((imageSize.height + spacing) / 2)

    switch relativeLocation {
    case .imageUpTitleDown:

        adjustImageEdgeInsets.top = -verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageDownTitleUp:
        adjustImageEdgeInsets.top = verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = -verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageLeftTitleRight:
        adjustImageEdgeInsets.left = -spacing / 2
        adjustImageEdgeInsets.right = spacing / 2

        adjustTitleEdgeInsets.left = spacing / 2
        adjustTitleEdgeInsets.right = -spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    case .imageRightTitleLeft:
        adjustImageEdgeInsets.left = titleSize.width + spacing / 2
        adjustImageEdgeInsets.right = -titleSize.width - spacing / 2

        adjustTitleEdgeInsets.left = -imageSize.width - spacing / 2
        adjustTitleEdgeInsets.right = imageSize.width + spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    }

    imageEdgeInsets = adjustImageEdgeInsets
    titleEdgeInsets = adjustTitleEdgeInsets
    contentEdgeInsets = adjustContentEdgeInsets

    setNeedsLayout()
}
}
Jules
źródło
1

Oto prosty przykład użycia imageEdgeInsets Spowoduje to, że przycisk 30x30 z obszarem możliwym do powiększenia o 10 pikseli dookoła (50x50)

    var expandHittableAreaAmt : CGFloat = 10
    var buttonWidth : CGFloat = 30
    var button = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
    button.frame = CGRectMake(0, 0, buttonWidth+expandHittableAreaAmt, buttonWidth+expandHittableAreaAmt)
    button.imageEdgeInsets = UIEdgeInsetsMake(expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt)
    button.setImage(UIImage(named: "buttonImage"), forState: .Normal)
    button.addTarget(self, action: "didTouchButton:", forControlEvents:.TouchUpInside)
Harris
źródło
0

Elegancki sposób w Swift 3 i lepiej zrozumieć:

override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 40
    let imgWidth:CGFloat = 24
    let imgHeight:CGFloat = 24
    return CGRect(x: leftMargin, y: (contentRect.size.height-imgHeight) * 0.5, width: imgWidth, height: imgHeight)
}

override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 80
    let rightMargin:CGFloat = 80
    return CGRect(x: leftMargin, y: 0, width: contentRect.size.width-leftMargin-rightMargin, height: contentRect.size.height)
}
override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 10
    let rightMargin:CGFloat = 10
    let topMargin:CGFloat = 10
    let bottomMargin:CGFloat = 10
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}
override func contentRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 5
    let rightMargin:CGFloat = 5
    let topMargin:CGFloat = 5
    let bottomMargin:CGFloat = 5
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}
teonicel
źródło
-1

Szybka wersja 4.2 rozwiązania byłaby następująca:

let spacing: CGFloat = 10 // the amount of spacing to appear between image and title
self.button?.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing)
self.button?.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0)
Soheil Novinfard
źródło