Allineamento di testo e immagine su UIButton con imageEdgeInsets e titleEdgeInsets


248

Vorrei posizionare un'icona a sinistra delle due righe di testo in modo tale che ci siano circa 2-3 pixel di spazio tra l'immagine e l'inizio del testo. Il controllo stesso è allineato al centro in orizzontale (impostato tramite Interface Builder)

Il pulsante sarebbe simile a questo:

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

Sto provando a configurarlo con contentEdgeInset, imageEdgeInsets e titleEdgeInsets senza alcun risultato. Capisco che un valore negativo espande il bordo mentre un valore positivo lo restringe per avvicinarlo al centro.

Provai:

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

ma questo non lo visualizza correttamente. Ho modificato i valori, ma passando da -5 a -10 sul valore dell'inserto sinistro non sembra spostarlo nel modo previsto. -10 sposterà il testo completamente a sinistra, quindi mi aspettavo -5 scartandolo a metà strada dal lato sinistro, ma non lo fa.

Qual è la logica dietro le inserzioni? Non ho familiarità con i posizionamenti di immagini e la terminologia correlata.

Ho usato questa domanda SO come riferimento, ma qualcosa sui miei valori non è giusto. UIButton: come centrare un'immagine e un testo usando imageEdgeInsets e titleEdgeInsets?

Risposte:


392

Concordo sulla documentazione imageEdgeInsetse titleEdgeInsetsdovrei essere migliore, ma ho capito come ottenere il posizionamento corretto senza ricorrere a tentativi ed errori.

L'idea generale è qui a questa domanda , ma era se volevi centrare sia il testo che l'immagine. Non vogliamo che l'immagine e il testo siano centrati individualmente, vogliamo che l'immagine e il testo siano centrati insieme come un'unica entità. Questo è in realtà ciò che UIButton fa già, quindi dobbiamo semplicemente regolare la spaziatura.

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

Ho anche trasformato questo in una categoria per UIButton, quindi sarà facile da usare:

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

Quindi ora tutto ciò che devo fare è:

[button centerButtonAndImageWithSpacing:10];

E ottengo ciò di cui ho bisogno ogni volta. Niente più problemi con gli inserti dei bordi manualmente.

EDIT: scambio di immagini e testo

In risposta a @Javal nei commenti

Usando questo stesso meccanismo, possiamo scambiare l'immagine e il testo. Per eseguire lo scambio, usa semplicemente una spaziatura negativa ma includi anche la larghezza del testo e dell'immagine. Ciò richiederà la conoscenza dei frame e il layout già eseguito.

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

Ovviamente probabilmente vorrai creare un buon metodo per questo, potenzialmente aggiungendo un metodo di seconda categoria, questo viene lasciato come esercizio al lettore.


Se ho titoli diversi per normale ed evidenziato, come posso ricentrarlo quando l'utente evidenzia e deseleziona il pulsante?
user102008

@ user102008 Titoli completamente diversi? O solo colori diversi? Il posizionamento non dovrebbe cambiare se si sta utilizzando [UIButton setTitleColor:forState:]o addirittura [UIButton setTitle:forState:].
Kekoa,

5
Come si corregge [button sizeToFit] in modo che si adatti correttamente?
RonLugge,

@RonLugge Non sono sicuro del motivo per cui hai bisogno di sizeToFit, quindi non posso rispondere alla tua domanda. Funziona bene per me senza usare sizeToFit.
Kekoa,

Ho bisogno di sizeToFit perché i pulsanti / testo sono dinamici, quindi ho bisogno di ridimensionare il pulsante per adattarlo all'etichetta (definita dall'utente). Il problema è che non compensa lo spazio aggiunto. Ho finito per annullarlo e aumentare manualmente la larghezza del fotogramma di 10.
RonLugge,

395

Sono un po 'in ritardo a questa festa, ma penso di avere qualcosa di utile da aggiungere.

La risposta di Kekoa è ottima ma, come menziona RonLugge, può fare in modo che il pulsante non rispetti più sizeToFito, cosa ancora più importante, può fare in modo che il pulsante ritagli il contenuto quando è intrinsecamente dimensionato. Yikes!

Prima però

Una breve spiegazione di come credo imageEdgeInsetse titleEdgeInsetslavoro:

I documenti perimageEdgeInsets hanno quanto segue da dire, in parte:

Utilizzare questa proprietà per ridimensionare e riposizionare il rettangolo di disegno effettivo per l'immagine del pulsante. È possibile specificare un valore diverso per ciascuno dei quattro inserti (in alto, a sinistra, in basso, a destra). Un valore positivo si restringe o inserisce quel bordo, avvicinandolo al centro del pulsante. Un valore negativo si espande, o supera, quel bordo.

Credo che questa documentazione sia stata scritta immaginando che il pulsante non abbia titolo, solo un'immagine. Ha molto più senso pensare in questo modo e si comporta come al UIEdgeInsetssolito. Fondamentalmente, la cornice dell'immagine (o il titolo, con titleEdgeInsets) viene spostata verso l'interno per inserti positivi e verso l'esterno per gli inserti negativi.

OK, e allora?

Ci sto arrivando! Ecco cosa hai di default, impostando un'immagine e un titolo (il bordo del pulsante è verde solo per mostrare dove si trova):

Immagine iniziale;  nessuno spazio tra titolo e immagine.

Quando si desidera spaziare tra un'immagine e un titolo, senza causare la compressione di uno dei due, è necessario impostare quattro diversi inserti, due su ciascuna immagine e titolo. Questo perché non vuoi cambiare le dimensioni dei frame di quegli elementi, ma solo le loro posizioni. Quando inizi a pensare in questo modo, diventa evidente la modifica necessaria all'eccellente categoria di Kekoa:

@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

Ma aspetta , dici, quando lo faccio, ottengo questo:

La spaziatura è buona, ma l'immagine e il titolo sono al di fuori della cornice della vista.

O si! Ho dimenticato, i documenti mi hanno avvertito di questo. Dicono, in parte:

Questa proprietà viene utilizzata solo per posizionare l'immagine durante il layout. Il pulsante non utilizza questa proprietà per determinare intrinsicContentSizee sizeThatFits:.

Ma v'è una proprietà che può aiutare, e questo è contentEdgeInsets. I documenti per questo dicono, in parte:

Il pulsante utilizza questa proprietà per determinare intrinsicContentSizee sizeThatFits:.

Suona bene. Quindi ottimizziamo ancora una volta la categoria:

@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

E cosa ottieni?

Spaziatura e cornice ora sono corretti.

A me sembra un vincitore.


Lavorare in Swift e non vuoi assolutamente pensare? Ecco la versione finale dell'estensione in 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)
    }
}

25
Che grande risposta! Sì, qualche anno in ritardo alla festa, ma questo ha risolto il problema intrinsicContentSizedell'essere errato, che è molto importante in questi giorni di auto-layout da quando è stata accettata la risposta originale.
Acey,

2
E se volevi la stessa spaziatura tra l'esterno del pulsante, l'immagine e l'etichetta, aggiungi spacinga ciascuno dei quattro valori di self.contentEdgeInsets, in questo modo:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
Erik van der Neut

1
Ottima risposta, peccato che non funzioni abbastanza bene quando l'immagine è allineata a destra e la lunghezza del testo può variare.
jlpiedrahita,

2
Risposta quasi perfetta! L'unica cosa che manca è che gli inserti di immagine e titolo dovrebbero essere invertiti quando viene eseguito sull'interfaccia da destra a sinistra.
jeeeyul,

come impostare il rapporto immagine su 1 a 1
Yestay Muratov

39

In Interface Builder. Seleziona il pulsante UIB -> Impostazioni attributi -> Bordo = Titolo e modifica le inserzioni del bordo


38

Anche se vuoi fare qualcosa di simile a

inserisci qui la descrizione dell'immagine

Hai bisogno

1.Impostare l'allineamento orizzontale e verticale per il pulsante su

inserisci qui la descrizione dell'immagine

  1. Trova tutti i valori richiesti e impostali 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)];

Ciò organizzerà il titolo e l'immagine sul pulsante.

Si prega di notare che aggiornare questo su ogni relè


veloce

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)
            }
        }
    }
}

29

Puoi evitare molti problemi usando questo -

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

Ciò allineerà automaticamente tutti i tuoi contenuti a sinistra (o dove lo desideri)

Swift 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;

myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; //
Errore di battitura

25

In Xcode 8.0 puoi semplicemente farlo cambiando insetsnella finestra di ispezione delle dimensioni.

Seleziona il pulsante UIB -> Impostazioni attributi -> vai su Impostazioni dimensioni e modifica il contenuto, l'immagine e le inserzioni del titolo.

inserisci qui la descrizione dell'immagine

E se vuoi cambiare l'immagine sul lato destro puoi semplicemente cambiare la proprietà semantica Force Right-to-leftin "Impostazioni attributi".

inserisci qui la descrizione dell'immagine


nel mio caso l'immagine del pulsante non si sposta mai sul lato destro del testo con xcode 10? Puoi aiutare?
Satish Mavani,

Ciao Satish, anche questo funziona bene con xcode 10. Spero che tu stia impostando l'immagine non l'immagine di sfondo e che tu possa anche cambiare gli insetti immagine usando nella finestra di ispezione delle dimensioni.
Sahil,

18

Sono un po 'in ritardo anche a questa festa, ma penso di avere qualcosa di utile da aggiungere: o).

Ho creato una UIButtonsottoclasse il cui scopo è essere in grado di scegliere la posizione dell'immagine del pulsante, sia in verticale che in orizzontale.

Significa che puoi creare questo tipo di pulsanti: diversi tipi di pulsanti

Ecco i dettagli su come creare questi pulsanti con la mia classe:

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)

Per fare ciò, ho aggiunto 2 attributi: imageVerticalAlignmente imageHorizontalAlignment. Ovviamente, se il tuo pulsante ha solo un'immagine o un titolo ... non usare affatto questa classe!

Ho anche aggiunto un attributo chiamato imageToTitleSpacingche ti consente di regolare lo spazio tra titolo e immagine.

Questa classe del suo meglio per essere compatibile se si desidera utilizzare imageEdgeInsets, titleEdgeInsetse contentEdgeInsetsdirettamente o in combinaison con i nuovi attributi di layout.

Come ci spiega @ravron, faccio del mio meglio per rendere corretto il bordo del contenuto del pulsante (come puoi vedere con i bordi rossi).

Puoi anche usarlo in Interface Builder:

  1. Crea un UIButton
  2. Cambia la classe del pulsante
  3. Regola gli attributi layoutable usando "center", "top", "bottom", "left" o "right" attributi del pulsante

Ecco il codice ( 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()
    }
}

1
Risolvo alcune cose, per non rompere l'IB error: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
nebiros,

@nebiros puoi spiegare cosa c'è che non va e come lo risolvi, per favore?
gbitaudeau,

@gbitaudeau quando copio e incollo lo script, ho ricevuto quell'errore error: IB Designables: Failed to update auto layout status: The agent crashed, perché init(frame: CGRect)non è stato ignorato, inoltre, aggiungo @availableannotazione ..., puoi diff -Naurse vuoi, ;-)
nebiros

9

Una piccola aggiunta alla risposta di Riley Avron alle modifiche alle impostazioni locali dell'account:

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)
    }
}

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)
    }
}

Utilizzo :

button.centerTextAndImage(spacing: 10.0)

Come si può usare con dimensioni dell'immagine custiom?
Midhun p

2

Scrivo il codice bewlow. Funziona bene nella versione del prodotto. 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()
}
}

1

Ecco un semplice esempio di come usare imageEdgeInsets Questo renderà un pulsante 30x30 con un'area hittable 10 pixel più grande tutt'intorno (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)

0

Un modo elegante in Swift 3 e meglio capire:

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)
}

-1

La rapida versione 4.2 della soluzione sarebbe la seguente:

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)
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.