iPhone UIButton - posizione dell'immagine


116

Ho un UIButtontesto con il testo "Esplora l'app" e UIImage(>) Interface Builderche appare come:

[ (>) Explore the app ]

Ma devo UIImageinserirlo DOPO il testo:

[ Explore the app (>) ]

Come posso spostare il UIImagea destra?


Puoi anche usare Interface Builder. Scopri la mia risposta qui: stackoverflow.com/questions/2765024/...
n8tr

1
Usa questa sottoclasse con poche righe di codice: github.com/k06a/RTLButton
k06a

Risposte:


161

La mia soluzione a questo è abbastanza semplice

[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
Funziona benissimo! È difficile capire il concetto di edge inset. Qualche idea sul perché dobbiamo impostare sia il bordo sinistro che quello destro? Teoricamente, se sposto il titolo a sinistra e l'immagine a destra, sarebbe sufficiente. Perché devo impostare sia sinistra che destra?
PokerIncome.com

3
LA MIGLIORE SOLUZIONE CHE HO TROVATO
Bimawa

2
Questa risposta funziona perfettamente. La risposta accettata è corretta e punta ai documenti API corretti, ma questa è la soluzione copia e incolla per fare ciò che l'OP ha richiesto.
mclaughlinj

Diventa un po 'complicato quando inizi a cambiare il testo del pulsante. La soluzione sottoclasse ha funzionato meglio per me in questo caso.
Brad Goss

Grazie per avermi mostrato che devi applicare larghezze uguali ai lati sinistro e destro, non capisco al 100% come funziona (perché a volte influenzerà le dimensioni e l'origine) ma sono stato in grado di risolvere problemi simili utilizzando questo metodo.
Toby

128

Su iOS 9 in poi, sembra che un modo semplice per ottenere ciò sia forzare la semantica della vista.

inserisci qui la descrizione dell'immagine

O in modo programmatico, utilizzando:

button.semanticContentAttribute = .ForceRightToLeft

4
Per quanto mi sia piaciuta la tua risposta (+1), odio dire che potrebbe non essere il modo "corretto" per farlo, ma è uno dei più semplici.
farzadshbfn

@farzadshbfn hai ragione. Ho cambiato quella parola per "semplice", sembra più coerente.
Alvivi

@farzadshbfn Perché questo non sarebbe il modo "corretto" di farlo?
Samman Bikram Thapa

3
@SammanBikramThapa IMHO il modo corretto sarebbe quello di sottoclassare il pulsante UIB e sovrascrivere layoutSubviews e rispetto semanticContentAttributenella nostra logica di layout, invece di cambiare semanticContentAttributese stesso. (cambiando approccio semantico, non funzionerà bene con l'internazionalizzazione)
farzadshbfn

@farzadshbfn ha perfettamente ragione. Indipendentemente dal fatto che questa risposta ottenga la maggior parte dei punti, non è corretta: potrebbe interrompere l'UX per l'interfaccia da destra a sinistra.
Pavel Yakimenko

86

Imposta imageEdgeInsete titleEdgeInsetper spostare i componenti all'interno dell'immagine. Potresti anche creare un pulsante usando quella grafica a grandezza naturale e usarla come immagine di sfondo per il pulsante (quindi usarla titleEdgeInsetsper spostare il titolo).


8
È molto meno codice per impostare semplicemente gli inserti che per implementare una sottoclasse. Questo è il punto centrale degli inserti. La manipolazione dei frame per le visualizzazioni secondarie (che non hai creato) sembra più un trucco.
Kim André Sand

6
@kimsnarf, davvero? È molto meno lavoro (e meno di un trucco) per modificare gli inserti ogni volta che apporti una piccola modifica alle dimensioni dell'immagine o alla lunghezza del titolo?
Kirk Woll

54

La risposta di Raymond W è la migliore qui. Sottoclasse UIButton con layout personalizzatoSubviews. Estremamente semplice da fare, ecco un'implementazione di layoutSubviews che ha funzionato per me:

- (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
In questo modo è meglio nel caso in cui sia necessario gestire molti pulsanti, ma devo cambiare solo un pulsante :)
Pavel Yakimenko

2
Se l'immagine del pulsante è nulla, l'etichetta risulta fuori posto, probabilmente perché UIImageView non è inserito (testato su iOS6.0). Dovresti considerare la modifica dei frame solo se imageView.image non è nullo.
Scakko

2
Suggerirei il seguente miglioramento a questa risposta in modo che entrambe le visualizzazioni rimangano centrate: 'CGFloat cumulativeWidth = CGRectGetWidth (imageFrame) + CGRectGetWidth (labelFrame) + 10; CGFloat exclusivWidth = CGRectGetWidth (self.bounds) - cumulativeWidth; labelFrame.origin.x = excludedWidth / 2; imageFrame.origin.x = CGRectGetMaxX (labelFrame) + 10; "
i-konov

Questo si interrompe su iOS 7 per me. Chiunque altro? Funziona bene su iOS 8.
rounak

1
Non supportare affatto iOS7 e il tuo problema sarà risolto. Non dovresti comunque sopprimerlo.
Gil Sand

30

Che dire della sottoclasse UIButtone dell'override layoutSubviews?

Quindi post-elaborazione delle posizioni di self.imageView&self.titleLabel


4
Questo è molto più semplice di tutti gli altri consigli (come quello sopra) sul tentativo di posizionare tutto correttamente usando inserti sintonizzati a mano.
Steven Kramer

13

Un altro modo semplice (che NON è solo iOS 9) è quello di sottoclassare UIButton per sovrascrivere questi due metodi

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 è già preso in considerazione utilizzando super.


Grazie! Mi piace molto di più delle risposte che sottoclasse e sovrascrivono layoutSubviews () :)
Joe Susnick

1
Il modo migliore per farlo a mio modesto parere :)
DrPatience

9

Forzare "da destra a sinistra" per il pulsante non è un'opzione se la tua app supporta sia "da sinistra a destra" che "da destra a sinistra".

La soluzione che ha funzionato per me è una sottoclasse che può essere aggiunta al pulsante nello Storyboard e funziona bene con i vincoli (testata in 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)
        }

    }

}

Dove "padding" sarebbe lo spazio tra il titolo e l'immagine.


Ovviamente .forceRightToLeftè un'opzione! Usa semplicemente il valore opposto ( .forceLeftToRight) if UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft.
manmal

5

In 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

Costruendo la risposta di @split ...

La risposta è fantastica, ma ignora il fatto che il pulsante può avere immagini personalizzate e inserti sul bordo del titolo impostati in anticipo (ad esempio nello storyboard).

Ad esempio, potresti volere che l'immagine abbia un po 'di riempimento dalla parte superiore e inferiore del contenitore, ma sposta comunque l'immagine sul lato destro del pulsante.

Ho esteso il concetto con questo metodo: -

- (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);

Questo crea un pulsante allineato a destra, in questo modo:

[           button label (>)]

Il pulsante non regola la sua larghezza in base al contesto, quindi lo spazio apparirà a sinistra dell'etichetta. È possibile risolvere questo problema calcolando la larghezza della cornice del pulsante da buttonLabelSize.width e buttonImageSize.width.


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

0

Questa soluzione funziona con iOS 7 e versioni successive

Basta sottoclasse 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

Utilizzo (da qualche parte nella tua classe):

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

0

Basandosi sulle risposte precedenti. Se vuoi avere un margine tra l'icona e il titolo del pulsante, il codice deve cambiare leggermente per evitare che l'etichetta e l'icona fluttuino sopra i limiti dei pulsanti di dimensioni intrinseche.

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)

L'ultima riga di codice è importante per il calcolo della dimensione intrinseca del contenuto per il layout automatico.


0

Ecco il mio modo di fare la cosa, (dopo circa 10 anni)

  1. Sottoclasse di UIButton (Button, poiché viviamo nell'era Swift)
  2. Metti un'immagine e un'etichetta in una vista pila.
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

Soluzione a linea singola in Swift:

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

Non è buono in quanto interromperà il layout per tutti gli utenti RTL. Dovrai forzare da sinistra a destra per loro. Faresti meglio a provare un'altra soluzione da qui.
Pavel Yakimenko
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.