UIButton: jak wyśrodkować obraz i tekst za pomocą imageEdgeInsets i titleEdgeInsets?

159

Jeśli wstawię tylko obraz do przycisku i ustawię element imageEdgeInsets bliżej góry, obraz pozostaje wyśrodkowany i wszystko działa zgodnie z oczekiwaniami:

[button setImage:image forState:UIControlStateNormal];
[button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, 0.0)];

Jeśli umieszczę tylko tekst w przycisku i ustawię titleEdgeInsets bliżej dołu, tekst pozostanie wyśrodkowany i wszystko będzie działać zgodnie z oczekiwaniami:

[button setTitle:title forState:UIControlStateNormal];
[button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, 0.0, -30, 0.0)];

Ale jeśli połączę 4 wiersze razem, tekst zakłóci obraz i oba utracą wyrównanie do środka.

Wszystkie moje obrazy mają szerokość 30 pikseli, a jeśli wstawię 30 w lewym parametrze UIEdgeInsetMake dla setTitleEdgeInsets, tekst jest ponownie wyśrodkowany. Problem polega na tym, że obraz nigdy nie jest wyśrodkowany, ponieważ wygląda na to, że jest zależny od rozmiaru button.titleLabel. Próbowałem już wielu obliczeń z rozmiarem przycisku, rozmiarem obrazu, rozmiarem tytułu etykiety i nigdy nie udało mi się uzyskać obu idealnie wyśrodkowanych.

Ktoś miał już ten sam problem?

reinaldoluckman
źródło

Odpowiedzi:

412

Oto ogólne rozwiązanie pozwalające na umieszczanie obrazu pośrodku tekstu bez używania magicznych liczb. Zwróć uwagę, że poniższy kod jest nieaktualny i prawdopodobnie powinieneś użyć jednej ze zaktualizowanych wersji poniżej :

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.frame.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = button.titleLabel.frame.size;
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

Poniższa wersja zawiera zmiany obsługujące iOS 7+ , które zostały zalecane w komentarzach poniżej. Sam nie testowałem tego kodu, więc nie jestem pewien, jak dobrze działa i czy zepsułby się, gdyby był używany w poprzednich wersjach iOS.

// the space between the image and text
CGFloat spacing = 6.0;

// lower the text and push it left so it appears centered 
//  below the image
CGSize imageSize = button.imageView.image.size;
button.titleEdgeInsets = UIEdgeInsetsMake(
  0.0, - imageSize.width, - (imageSize.height + spacing), 0.0);

// raise the image and push it right so it appears centered
//  above the text
CGSize titleSize = [button.titleLabel.text sizeWithAttributes:@{NSFontAttributeName: button.titleLabel.font}];
button.imageEdgeInsets = UIEdgeInsetsMake(
  - (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

// increase the content height to avoid clipping
CGFloat edgeOffset = fabsf(titleSize.height - imageSize.height) / 2.0;
button.contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0);

Wersja Swift 5.0

extension UIButton {
  func alignVertical(spacing: CGFloat = 6.0) {
    guard let imageSize = imageView?.image?.size,
      let text = titleLabel?.text,
      let font = titleLabel?.font
    else { return }

    titleEdgeInsets = UIEdgeInsets(
      top: 0.0,
      left: -imageSize.width,
      bottom: -(imageSize.height + spacing),
      right: 0.0
    )

    let titleSize = text.size(withAttributes: [.font: font])
    imageEdgeInsets = UIEdgeInsets(
      top: -(titleSize.height + spacing),
      left: 0.0,
      bottom: 0.0, right: -titleSize.width
    )

    let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0
    contentEdgeInsets = UIEdgeInsets(
      top: edgeOffset,
      left: 0.0,
      bottom: edgeOffset,
      right: 0.0
    )
  }
}
Jesse Crossen
źródło
5
Cudownie - dzięki! Myślę, że wiele innych odpowiedzi odnosi się do tego „na własnej skórze” - wygląda to znacznie lepiej.
Joe D'Andrea
3
Znalazłem powyższe do działania, ale nie mam absolutnie żadnego mentalnego modelu tego, jak. Czy ktoś ma link do obrazkowego wyjaśnienia, na co wpływają poszczególne parametry EdgeInsets? A dlaczego zmieniłaby się szerokość tekstu?
Robert Atkins
3
To nie działa, gdy obraz jest zmniejszany, aby pasował do przycisku. Wygląda na to, że UIButton (przynajmniej w iOS 7) używa image.size, a nie imageView.frame.size do swoich obliczeń centrowania.
Dan Jackson
5
@Hemang i Dan Jackson, uwzględniam wasze sugestie bez ich testowania. Uważam za śmieszne, że pierwotnie napisałem to dla iOS 4, a po tak wielu wersjach nadal musimy zasadniczo przeprojektować algorytm układu Apple, aby uzyskać tak oczywistą funkcję. A przynajmniej zakładam, że nie ma jeszcze lepszego rozwiązania z konsekwentnego strumienia głosów za i równie hakerskich odpowiedzi poniżej (bez obrażania).
Jesse Crossen
5
W iOS8 stwierdziłem, że lepiej jest go używać, button.currentTitlea nie button.titleLabel.textzwłaszcza, jeśli tekst przycisku kiedykolwiek się zmieni. currentTitlejest wypełniany od razu, titleLabel.textale jego zmiana może być powolna, co może prowadzić do nieprawidłowego wyrównania wstawek.
mjangda
59

Znalazłem jak.

Najpierw skonfiguruj tekst titleLabel(ze względu na style, tj. Pogrubienie, kursywa itp.). Następnie użyj setTitleEdgeInsetsbiorąc pod uwagę szerokość obrazu:

[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button setTitle:title forState:UIControlStateNormal];
[button.titleLabel setFont:[UIFont boldSystemFontOfSize:10.0]];

// Left inset is the negative of image width.
[button setTitleEdgeInsets:UIEdgeInsetsMake(0.0, -image.size.width, -25.0, 0.0)]; 

Następnie użyj setTitleEdgeInsetsbiorąc pod uwagę szerokość granic tekstu:

[button setImage:image forState:UIControlStateNormal];

// Right inset is the negative of text bounds width.
[button setImageEdgeInsets:UIEdgeInsetsMake(-15.0, 0.0, 0.0, -button.titleLabel.bounds.size.width)];

Teraz obraz i tekst zostaną wyśrodkowane (w tym przykładzie obraz pojawi się nad tekstem).

Twoje zdrowie.

reinaldoluckman
źródło
3
7 lat człowieku. Naprawdę nie pamiętam. Kiedy zapytałem, odpowiedziałem sobie (moja odpowiedź była wtedy jedyna). Zmieniłem wybraną odpowiedź, gdy autor aktualnie wybranej odpowiedzi skupił się na wyeliminowaniu tych magicznych liczb. Tak więc w wybranej odpowiedzi możesz dowiedzieć się, co oznaczają.
reinaldoluckman
20

Możesz to zrobić za pomocą tego rozszerzenia Swift, które zostało częściowo oparte na odpowiedzi Jesse Crossena:

extension UIButton {
  func centerLabelVerticallyWithPadding(spacing:CGFloat) {
    // update positioning of image and title
    let imageSize = self.imageView.frame.size
    self.titleEdgeInsets = UIEdgeInsets(top:0,
                                        left:-imageSize.width,
                                        bottom:-(imageSize.height + spacing),
                                        right:0)
    let titleSize = self.titleLabel.frame.size
    self.imageEdgeInsets = UIEdgeInsets(top:-(titleSize.height + spacing),
                                        left:0,
                                        bottom: 0,
                                        right:-titleSize.width)

    // reset contentInset, so intrinsicContentSize() is still accurate
    let trueContentSize = CGRectUnion(self.titleLabel.frame, self.imageView.frame).size
    let oldContentSize = self.intrinsicContentSize()
    let heightDelta = trueContentSize.height - oldContentSize.height
    let widthDelta = trueContentSize.width - oldContentSize.width
    self.contentEdgeInsets = UIEdgeInsets(top:heightDelta/2.0,
                                          left:widthDelta/2.0,
                                          bottom:heightDelta/2.0,
                                          right:widthDelta/2.0)
  }
}

Definiuje funkcję, centerLabelVerticallyWithPaddingktóra odpowiednio ustawia wstawki tytułu i obrazu.

Ustawia również contentEdgeInsets, co moim zdaniem jest konieczne, aby zapewnić, że intrinsicContentSizenadal działa poprawnie, co wymagałoby użycia automatycznego układu.

Uważam, że wszystkie rozwiązania podklasy UIButton są technicznie nieuzasadnione, ponieważ nie powinno się tworzyć podklasy kontrolek UIKit. To znaczy teoretycznie mogą się zepsuć w przyszłych wydaniach.

glony
źródło
Testowanie na iOS9. Obraz wydaje się wyśrodkowany, ale tekst pojawia się po lewej stronie :(
endavid
13

Edycja: zaktualizowano dla Swift 3

Jeśli szukasz szybkiego rozwiązania odpowiedzi Jesse Crossena, możesz dodać to do podklasy UIButton:

override func layoutSubviews() {

    let spacing: CGFloat = 6.0

    // lower the text and push it left so it appears centered
    //  below the image
    var titleEdgeInsets = UIEdgeInsets.zero
    if let image = self.imageView?.image {
        titleEdgeInsets.left = -image.size.width
        titleEdgeInsets.bottom = -(image.size.height + spacing)
    }
    self.titleEdgeInsets = titleEdgeInsets

    // raise the image and push it right so it appears centered
    //  above the text
    var imageEdgeInsets = UIEdgeInsets.zero
    if let text = self.titleLabel?.text, let font = self.titleLabel?.font {
        let attributes = [NSFontAttributeName: font]
        let titleSize = text.size(attributes: attributes)
        imageEdgeInsets.top = -(titleSize.height + spacing)
        imageEdgeInsets.right = -titleSize.width
    }
    self.imageEdgeInsets = imageEdgeInsets

    super.layoutSubviews()
}
Thomas Verbeek
źródło
9

Jest tutaj kilka świetnych przykładów, ale nie mogłem tego uruchomić we wszystkich przypadkach, gdy mam do czynienia z wieloma wierszami tekstu (zawijanie tekstu). Aby w końcu zadziałało, połączyłem kilka technik:

  1. Użyłem powyższego przykładu Jesse Crossena. Jednak naprawiłem problem z wysokością tekstu i dodałem możliwość określenia poziomego marginesu tekstu. Margines jest przydatny, gdy pozwala się na zawijanie tekstu, aby nie uderzał o krawędź przycisku:

    // the space between the image and text
    CGFloat spacing = 10.0;
    float   textMargin = 6;
    
    // get the size of the elements here for readability
    CGSize  imageSize   = picImage.size;
    CGSize  titleSize   = button.titleLabel.frame.size;
    CGFloat totalHeight = (imageSize.height + titleSize.height + spacing);      // get the height they will take up as a unit
    
    // lower the text and push it left to center it
    button.titleEdgeInsets = UIEdgeInsetsMake( 0.0, -imageSize.width +textMargin, - (totalHeight - titleSize.height), +textMargin );   // top, left, bottom, right
    
    // the text width might have changed (in case it was shortened before due to 
    // lack of space and isn't anymore now), so we get the frame size again
    titleSize = button.titleLabel.bounds.size;
    
    button.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + spacing), 0.0, 0.0, -titleSize.width );     // top, left, bottom, right        
  2. Upewnij się, że ustawiłeś etykietę tekstową do zawijania

    button.titleLabel.numberOfLines = 2; 
    button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
    button.titleLabel.textAlignment = UITextAlignmentCenter;
  3. To zadziała głównie teraz. Jednak miałem kilka przycisków, które nie renderowały poprawnie swojego obrazu. Obraz został przesunięty zbyt daleko w prawo lub w lewo (nie był wyśrodkowany). Więc użyłem techniki zastępowania układu UIButton, aby wymusić wyśrodkowanie imageView.

    @interface CategoryButton : UIButton
    @end
    
    @implementation CategoryButton
    
    - (void)layoutSubviews
    {
        // Allow default layout, then center imageView
        [super layoutSubviews];
    
        UIImageView *imageView = [self imageView];
        CGRect imageFrame = imageView.frame;
        imageFrame.origin.x = (int)((self.frame.size.width - imageFrame.size.width)/ 2);
        imageView.frame = imageFrame;
    }
    @end
Tod Cunningham
źródło
Wygląda na to, że jest to dobre rozwiązanie, ale czy button.titleLabel.numberOfLines nie powinno mieć wartości 0, tak że może to być dowolna liczba wierszy?
Ben Lachman
W moim przypadku chciałem maksymalnie dwie linie. W przeciwnym razie obraz miałby problemy z ogólnym rozmiarem przycisku.
Tod Cunningham,
9

Stworzyłem metodę odpowiedzi @ TodCunningham

 -(void) AlignTextAndImageOfButton:(UIButton *)button
 {
   CGFloat spacing = 2; // the amount of spacing to appear between image and title
   button.imageView.backgroundColor=[UIColor clearColor];
   button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
   button.titleLabel.textAlignment = UITextAlignmentCenter;
   // get the size of the elements here for readability
   CGSize imageSize = button.imageView.frame.size;
   CGSize titleSize = button.titleLabel.frame.size;

  // lower the text and push it left to center it
  button.titleEdgeInsets = UIEdgeInsetsMake(0.0, - imageSize.width, - (imageSize.height   + spacing), 0.0);

  // the text width might have changed (in case it was shortened before due to 
  // lack of space and isn't anymore now), so we get the frame size again
   titleSize = button.titleLabel.frame.size;

  // raise the image and push it right to center it
  button.imageEdgeInsets = UIEdgeInsetsMake(- (titleSize.height + spacing), 0.0, 0.0, -     titleSize.width);
 }
Bekir Onat Akin
źródło
7

Zaktualizowano dla Xcode 11+

Wstawki opisane w mojej oryginalnej odpowiedzi zostały przeniesione do inspektora rozmiaru w nowszych wersjach Xcode. Nie jestem w 100% pewien, kiedy nastąpiła zmiana, ale czytelnicy powinni sprawdzić inspektora rozmiaru, jeśli informacje wstawione nie zostaną znalezione w inspektorze atrybutów. Poniżej znajduje się przykład nowego wstawionego ekranu (znajdującego się u góry inspektora atrybutów rozmiaru od 11.5).

insets_moved_to_size_inspector

Oryginalna odpowiedź

Nie ma nic złego w innych odpowiedziach, jednak chciałem tylko zauważyć, że to samo zachowanie można osiągnąć wizualnie w Xcode przy użyciu zerowych linii kodu. To rozwiązanie jest przydatne, jeśli nie potrzebujesz obliczonej wartości lub budujesz za pomocą storyboardu / xib (w przeciwnym razie mają zastosowanie inne rozwiązania).

Uwaga - rozumiem, że pytanie OP wymaga podania kodu. Podaję tylko tę odpowiedź dla kompletności i jako logiczną alternatywę dla tych, którzy używają storyboardów / xibs.

Aby zmodyfikować odstępy w widokach obrazu, tytułu i zawartości przycisku za pomocą wstawek krawędziowych, można zaznaczyć przycisk / element sterujący i otworzyć inspektora atrybutów. Przewiń w dół w kierunku środka inspektora i znajdź sekcję dotyczącą wstawek krawędzi.

wstawki krawędziowe

Można również uzyskać dostęp i zmodyfikować określone wstawki krawędzi dla widoku tytułu, obrazu lub treści.

opcje menu

Tommie C.
źródło
Nie rozumiem, dlaczego nie mogę wprowadzić liczb ujemnych w scenorysie dla niektórych wartości.
Daniel T.
Czy to działa w przypadku nowszej szybkiej wersji? Nigdzie nie mogę znaleźć atrybutu Edge
brockhampton
@brockhampton - zobacz zaktualizowaną odpowiedź dla nowej lokalizacji
Tommie C.
6

Nie walcz z systemem. Jeśli układy stają się zbyt złożone, aby można było nimi zarządzać za pomocą programu Interface Builder +, być może jakiś prosty kod konfiguracyjny, wykonaj układy ręcznie w prostszy sposób, używająclayoutSubviews - do tego ! Wszystko inne sprowadzi się do hacków.

Utwórz podklasę UIButton i zastąp jej layoutSubviewsmetodę, aby programowo wyrównać tekst i obraz. Lub użyj czegoś takiego jak https://github.com/nickpaulson/BlockKit/blob/master/Source/UIView-BKAdditions.h, aby zaimplementować layoutSubviews za pomocą bloku.

Steven Kramer
źródło
6

Podklasa UIButton

- (void)layoutSubviews {
    [super layoutSubviews];
    CGFloat spacing = 6.0;
    CGSize imageSize = self.imageView.image.size;
    CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(self.frame.size.width, self.frame.size.height - (imageSize.height + spacing))];
    self.imageView.frame = CGRectMake((self.frame.size.width - imageSize.width)/2, (self.frame.size.height - (imageSize.height+spacing+titleSize.height))/2, imageSize.width, imageSize.height);
    self.titleLabel.frame = CGRectMake((self.frame.size.width - titleSize.width)/2, CGRectGetMaxY(self.imageView.frame)+spacing, titleSize.width, titleSize.height);
}
Alex
źródło
6

Zaktualizowana odpowiedź Jesse Crossena dla Swift 4 :

extension UIButton {
    func alignVertical(spacing: CGFloat = 6.0) {
        guard let imageSize = self.imageView?.image?.size,
            let text = self.titleLabel?.text,
            let font = self.titleLabel?.font
            else { return }
        self.titleEdgeInsets = UIEdgeInsets(top: 0.0, left: -imageSize.width, bottom: -(imageSize.height + spacing), right: 0.0)
        let labelString = NSString(string: text)
        let titleSize = labelString.size(withAttributes: [kCTFontAttributeName as NSAttributedStringKey: font])
        self.imageEdgeInsets = UIEdgeInsets(top: -(titleSize.height + spacing), left: 0.0, bottom: 0.0, right: -titleSize.width)
        let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
        self.contentEdgeInsets = UIEdgeInsets(top: edgeOffset, left: 0.0, bottom: edgeOffset, right: 0.0)
    }
}

Użyj w ten sposób:

override func viewDidLayoutSubviews() {
    button.alignVertical()
}
Doci
źródło
Po wielu, wielu godzinach. To była jedyna rzecz, która zadziałała :)
Harry Blue
4

Z tym fragmentem kodu otrzymasz coś takiego wyrównanie tytułu i obrazu

extension UIButton {
    func alignTextUnderImage() {
        guard let imageView = imageView else {
                return
        }
        self.contentVerticalAlignment = .Top
        self.contentHorizontalAlignment = .Center
        let imageLeftOffset = (CGRectGetWidth(self.bounds) - CGRectGetWidth(imageView.bounds)) / 2//put image in center
        let titleTopOffset = CGRectGetHeight(imageView.bounds) + 5
        self.imageEdgeInsets = UIEdgeInsetsMake(0, imageLeftOffset, 0, 0)
        self.titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset, -CGRectGetWidth(imageView.bounds), 0, 0)
    }
}
Bohdan Savych
źródło
2
Napisałem małe rozszerzenie do pozycjonowania obrazu i tekstu wewnątrz przycisku. Jeśli jesteś zainteresowany, oto kod źródłowy. github.com/sssbohdan/ButtonAlignmentExtension/blob/master/…
Bohdan Savych
4

Rozszerzenie UIButton ze składnią Swift 3+ :

extension UIButton {
    func alignImageAndTitleVertically(padding: CGFloat = 6.0) {
        let imageSize: CGSize = imageView!.image!.size
        titleEdgeInsets = UIEdgeInsetsMake(0.0, -imageSize.width, -(imageSize.height + padding), 0.0)
        let labelString = NSString(string: titleLabel!.text!)
        let titleSize = labelString.size(attributes: [NSFontAttributeName: titleLabel!.font])
        self.imageEdgeInsets = UIEdgeInsetsMake(-(titleSize.height + padding), 0.0, 0.0, -titleSize.width)
        let edgeOffset = abs(titleSize.height - imageSize.height) / 2.0;
        self.contentEdgeInsets = UIEdgeInsetsMake(edgeOffset, 0.0, edgeOffset, 0.0)
    }
}

Oryginalna odpowiedź: https://stackoverflow.com/a/7199529/3659227

Politycznie niezależny
źródło
3

Tylko niewielka zmiana w odpowiedzi Jessego Crossena, która sprawiła, że ​​działała idealnie:

zamiast:

CGSize titleSize = button.titleLabel.frame.size;

Użyłem tego:

CGSize titleSize = [button.titleLabel.text sizeWithAttributes: @{NSFontAttributeName:button.titleLabel.font}];
Cesar
źródło
Witamy w SO! Zamiast dodawać osobną odpowiedź (dla drobnej zmiany), możesz napisać to bezpośrednio do Jessego, aby mógł sprawdzić i poprawnie zaktualizować swoją [zaakceptowaną] odpowiedź (w razie potrzeby).
Hemang
3

Używanie button.titleLabel.frame.size.widthdziała dobrze tylko wtedy, gdy etykieta jest wystarczająco krótka, aby nie została skrócona. Jednak gdy tekst etykiety zostaje obcięty, pozycjonowanie nie działa. Nabierający

CGSize titleSize = [[[button titleLabel] text] sizeWithFont:[[button titleLabel] font]];

działa u mnie nawet wtedy, gdy tekst etykiety jest obcięty.

grudnia
źródło
masz literówkę w przycisku.
Bhimbim
2

Przyjrzałem się istniejącym odpowiedziom, ale odkryłem również, że ustawienie ramki przycisku to ważny pierwszy krok.

Oto funkcja, której używam, która to załatwia:

const CGFloat kImageTopOffset   = -15;
const CGFloat kTextBottomOffset = -25;

+ (void) centerButtonImageTopAndTextBottom: (UIButton*)         button 
                                     frame: (CGRect)            buttonFrame
                                      text: (NSString*)         textString
                                 textColor: (UIColor*)          textColor
                                      font: (UIFont*)           textFont
                                     image: (UIImage*)          image
                                  forState: (UIControlState)    buttonState
{
    button.frame = buttonFrame;

    [button setTitleColor: (UIColor*)       textColor
                 forState: (UIControlState) buttonState];

    [button setTitle: (NSString*) textString
            forState: (UIControlState) buttonState ];


    [button.titleLabel setFont: (UIFont*) textFont ];

    [button setTitleEdgeInsets: UIEdgeInsetsMake( 0.0, -image.size.width, kTextBottomOffset,  0.0)]; 

    [button setImage: (UIImage*)       image 
            forState: (UIControlState) buttonState ];

    [button setImageEdgeInsets: UIEdgeInsetsMake( kImageTopOffset, 0.0, 0.0,- button.titleLabel.bounds.size.width)];
}
Bamaco
źródło
2

Lub możesz po prostu skorzystać z tej kategorii:

@interface UIButton (VerticalLayout)  

- (void)centerVerticallyWithPadding:(float)padding;  
- (void)centerVertically;  

@end  


@implementation UIButton (VerticalLayout)  

- (void)centerVerticallyWithPadding:(float)padding 
{      
    CGSize imageSize = self.imageView.frame.size;  
    CGSize titleSize = self.titleLabel.frame.size;  

    CGFloat totalHeight = (imageSize.height + titleSize.height + padding);  

    self.imageEdgeInsets = UIEdgeInsetsMake(- (totalHeight - imageSize.height),
                                            0.0f,
                                            0.0f,
                                            - titleSize.width);

    self.titleEdgeInsets = UIEdgeInsetsMake(0.0f,
                                            - imageSize.width,
                                            - (totalHeight - titleSize.height),
                                            0.0f);

}


- (void)centerVertically
{  
    const CGFloat kDefaultPadding = 6.0f;

    [self centerVerticallyWithPadding:kDefaultPadding];  
}  


@end
RaffAl
źródło
1
To nie zadziała z niestandardową czcionką. Z iOS7 +,CGSize titleSize = [button.titleLabel.text sizeWithAttributes: @{NSFontAttributeName: button.titleLabel.font}];
Hemang
1

Mój przypadek użycia uniemożliwił zarządzanie wstawkami:

  1. obraz tła na przycisku pozostaje spójny
  2. dynamiczne zmiany tekstu i obrazu w przypadku zmiany długości ciągu i rozmiaru obrazu

Oto, co ostatecznie zrobiłem i jestem z tego całkiem zadowolony:

  • Utwórz przycisk w serii ujęć z obrazem tła (okrągłe koło z rozmyciem i kolorem).

  • Zadeklaruj UIImageView w mojej klasie:

    @implementation BlahViewController {
        UIImageView *_imageView;
    }
  • Utwórz instancję widoku obrazu na init:

    -(id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            _imageView = [[UIImageView alloc] initWithCoder:aDecoder];
         }
         return self;
     }
  • W viewDidLoad dodaj nową warstwę do przycisku naszego widoku obrazu i ustaw wyrównanie tekstu:

    [self.btn addSubview:_imageView];
    [self.btn.titleLabel setTextAlignment:NSTextAlignmentCenter];
  • W metodzie kliknięcia przycisku dodaj wybrany przeze mnie obraz nakładki do widoku obrazu, dopasuj go do obrazu i wyśrodkuj go w przycisku, ale przesuń go w górę o 15, abym mógł umieścić przesunięcie tekstu poniżej:

    [_imageView setImage:[UIImage imageNamed:@"blahImageBlah]];
    [_imageView sizeToFit];
    _imageView.center = CGPointMake(ceilf(self.btn.bounds.size.width / 2.0f),
             ceilf((self.btn.bounds.size.height / 2.0f) - 15));
    [self.btn setTitle:@"Some new text" forState:UIControlStateNormal];

Uwaga: funkcja ceilf () jest ważna, aby upewnić się, że znajduje się na granicy pikseli dla jakości obrazu.

Oliver Dungey
źródło
1
Zdecydowanie lepsze podejście do mojego przypadku użycia, ponieważ dodaję przycisk do widoku stosu.
valeCocoa
0

Zakładając, że chcesz, aby zarówno tekst, jak i obraz były wyśrodkowane w poziomie, obraz nad tekstem: Wyśrodkuj tekst z narzędzia do tworzenia interfejsu i dodaj górną wstawkę (robiąc miejsce na obraz). (pozostaw lewą wstawkę na 0). Użyj kreatora interfejsu, aby wybrać obrazek - jego aktualna pozycja zostanie ustawiona z kodu, więc nie martw się, że w IB nie będzie dobrze. W przeciwieństwie do innych odpowiedzi powyżej, to faktycznie działa na wszystkich obecnie obsługiwanych wersjach iOS (5,6 i 7).

W kodzie po prostu odrzuć ImageView przycisku (ustawiając obraz przycisku na null) po przechwyceniu obrazu (spowoduje to również automatyczne wyśrodkowanie tekstu, w razie potrzeby zawijane). Następnie utwórz wystąpienie własnego ImageView z tym samym rozmiarem ramki i obrazem i umieść go pośrodku.

W ten sposób nadal możesz wybrać obraz z konstruktora interfejsu (choć nie zostanie on wyrównany w IB tak jak w symulatorze, ale z drugiej strony inne rozwiązania nie są kompatybilne we wszystkich obsługiwanych wersjach iOS)

Radu Simionescu
źródło
0

Trudno mi było to zrobić, ponieważ nie mogłem uzyskać rozmiaru obrazu i szerokości tekstu w konstruktorze mojego widoku. Dwie drobne zmiany w odpowiedzi Jessego zadziałały dla mnie:

CGFloat spacing = 3;
self.titleEdgeInsets = UIEdgeInsetsMake(0.0, - image.size.width, - (image.size.height + spacing), 0.0);
CGSize titleSize = [name sizeWithAttributes:@{NSFontAttributeName:self.titleLabel.font}];
self.imageEdgeInsets = UIEdgeInsetsMake(- (titleSize.height + spacing), 0.0, 0.0, - titleSize.width);

Zmiana to:

  • Za pomocą [NSString sizeWithAttributes] do uzyskania szerokości tekstu;
  • Uzyskaj rozmiar obrazu bezpośrednio na UIImagezamiastUIImageView
saulobrito
źródło
0

To działa dobrze w przypadku kilku przycisków, z różną szerokością obrazu i różną długością tytułu:

Podklasa UIButton

override func layoutSubviews() {
    super.layoutSubviews()

    if let image = imageView?.image {

        let margin = 30 - image.size.width / 2
        let titleRect = titleRectForContentRect(bounds)
        let titleOffset = (bounds.width - titleRect.width - image.size.width - margin) / 2


        contentHorizontalAlignment = UIControlContentHorizontalAlignment.Left
            imageEdgeInsets = UIEdgeInsetsMake(0, margin, 0, 0)
            titleEdgeInsets = UIEdgeInsetsMake(0, (bounds.width - titleRect.width -  image.size.width - margin) / 2, 0, 0)
    }

}
Rémy Virin
źródło
0

Działa dobrze dla rozmiaru przycisku 80x80 pikseli.

[self.leftButton setImageEdgeInsets:UIEdgeInsetsMake(0, 10.0, 20.0, 10.0)];    
[self.leftButton setTitleEdgeInsets:UIEdgeInsetsMake(60, -75.0, 0.0, 0.0)];
BADRI
źródło
0

Dokonałem pewnych zmian, aby obraz był wyrównany na środku w poziomie:

// the space between the image and text
        let spacing = CGFloat(36.0);

        // lower the text and push it left so it appears centered
        //  below the image
        let imageSize = tutorialButton.imageView!.frame.size;
        tutorialButton.titleEdgeInsets = UIEdgeInsetsMake(
            0, -CGFloat(imageSize.width), -CGFloat(imageSize.height + spacing), 0.0);

        // raise the image and push it right so it appears centered
        //  above the text
        let titleSize = tutorialButton.titleLabel!.frame.size;
        tutorialButton.imageEdgeInsets = UIEdgeInsetsMake(
            -CGFloat(titleSize.height + spacing), CGFloat((tutorialButton.frame.width - imageSize.width) / 2), 0.0, -CGFloat(titleSize.width));
Nguyễn Thanh Khiêm
źródło
0

czy stosowanie wypustek krawędziowych jest obowiązkowe? Jeśli nie, możesz spróbować ustawić szacunek wobec centralnego poglądu rodzica

extension UIButton 
{
    func centerImageAndTextVerticaAlignment(spacing: CGFloat) 
    {
        var titlePoint : CGPoint = convertPoint(center, fromView:superview)
        var imageViewPoint : CGPoint = convertPoint(center, fromView:superview)
        titlePoint.y += ((titleLabel?.size.height)! + spacing)/2
        imageViewPoint.y -= ((imageView?.size.height)! + spacing)/2
        titleLabel?.center = titlePoint
        imageView?.center = imageViewPoint

    }
}
Rodrigo Birriel
źródło
Pytanie wyraźnie dotyczy użycia imageEdgeInsets i titleEdgeInsets, więc jest prawdopodobne, że jest to obowiązkowe
Tibrogargan
0

Musisz przesunąć obraz w prawo o szerokość tekstu. Następnie przesuń tekst w lewo o szerokość obrazu.

UIEdgeInsets imageEdgeInsets = self.remoteCommandsButtonLights.imageEdgeInsets;
imageEdgeInsets.left = [button.titleLabel.text sizeWithAttributes:@{NSFontAttributeName:[button.titleLabel font]}].width;
imageEdgeInsets.bottom = 14.0;
button.imageEdgeInsets = imageEdgeInsets;

UIEdgeInsets titleEdgeInsets = self.remoteCommandsButtonLights.titleEdgeInsets;
titleEdgeInsets.left = -button.currentImage.size.width;
titleEdgeInsets.top = 20.0;
button.titleEdgeInsets = titleEdgeInsets;

Następnie wyreguluj górne i dolne wstawki, aby wyregulować oś Y. Prawdopodobnie można to zrobić również programowo, ale powinno być stałe dla rozmiaru obrazu. Podczas gdy wstawki osi X będą musiały się zmieniać w zależności od rozmiaru etykiety tekstowej na każdym przycisku.

pkamb
źródło
0

Dodaj ten kod w rozszerzeniu Swift 4.2

 func moveImageLeftTextCenter(imagePadding: CGFloat = 30.0){
    guard let imageViewWidth = self.imageView?.frame.width else{return}
    guard let titleLabelWidth = self.titleLabel?.intrinsicContentSize.width else{return}
    self.contentHorizontalAlignment = .left
    imageEdgeInsets = UIEdgeInsets(top: 0.0, left: imagePadding - imageViewWidth / 2, bottom: 0.0, right: 0.0)
    titleEdgeInsets = UIEdgeInsets(top: 0.0, left: (bounds.width - titleLabelWidth) / 2 - imageViewWidth, bottom: 0.0, right: 0.0)
}
func moveImageRIghtTextCenter(imagePadding: CGFloat = 30.0){
    guard let imageViewWidth = self.imageView?.frame.width else{return}
    guard let titleLabelWidth = self.titleLabel?.intrinsicContentSize.width else{return}
    self.contentHorizontalAlignment = .right
    imageEdgeInsets = UIEdgeInsets(top: 0.0, left:0.0 , bottom: 0.0, right: imagePadding - imageViewWidth / 2)
    titleEdgeInsets = UIEdgeInsets(top: 0.0, left:0.0 , bottom: 0.0, right:(bounds.width - titleLabelWidth) / 2 - imageViewWidth)
}
Shahzaib Maqbool
źródło
0

Żeby dorzucić moje 2 centy, to zadziałało dla mnie:

extension UIButton {
  public func centerImageAndTextVertically(spacing: CGFloat) {
    layoutIfNeeded()
    let contentFrame = contentRect(forBounds: bounds)
    let imageFrame = imageRect(forContentRect: contentFrame)
    let imageLeftInset = bounds.size.width * 0.5 - imageFrame.size.width * 0.5
    let imageTopInset = -(imageFrame.size.height + spacing * 0.5)
    let titleFrame = titleRect(forContentRect: contentFrame)
    let titleLeftInset = ((bounds.size.width - titleFrame.size.width) * 0.5) - imageFrame.size.width
    let titleTopInmset = titleFrame.size.height + spacing * 0.5
    imageEdgeInsets = UIEdgeInsets(top: imageTopInset, left: imageLeftInset, bottom: 0, right: 0)
    titleEdgeInsets = UIEdgeInsets(top: titleTopInmset, left: titleLeftInset, bottom: 0, right: 0)
  }
}
Chris Wooden
źródło