Выравнивание текста и изображения на UIButton с помощью imageEdgeInsets и titleEdgeInsets

Я хотел бы поместить значок слева от двух строк текста таким образом, чтобы между изображением и началом текста было около 2-3 пикселей пробела. Сам элемент управления выравнивается по центру горизонтально (устанавливается через Interface Builder)

Кнопка будет напоминать что-то вроде этого:

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

Я пытаюсь настроить это с помощью contentEdgeInset, imageEdgeInsets и titleEdgeInsets безрезультатно. Я понимаю, что отрицательное значение расширяет край, а положительное значение сжимает его, чтобы приблизить его к центру.

Я пробовал:

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

но это не отображает его правильно. Я настраивал значения, но, скажем, от -5 до -10 по левому вложенному значению, похоже, не двигает его ожидаемым образом. -10 будет scoot текст весь путь влево, так что я ожидал -5, чтобы scoot его на полпути с левой стороны, но это не так.

Какая логика за вставками? Я не знаком с местами размещения изображений и связанной с ними терминологией.

Я использовал этот вопрос SO как ссылку, но что-то о моих значениях неверно. UIButton: как центрировать изображение и текст с помощью imageEdgeInsets и titleEdgeInsets?

Ответ 1

Я согласен, что документация на imageEdgeInsets и titleEdgeInsets должна быть лучше, но я понял, как получить правильное позиционирование, не прибегая к проб и ошибок.

Общая идея здесь находится в этом вопросе, но это было, если бы вы хотели, чтобы текст и изображение были центрированы. Мы не хотим, чтобы изображение и текст были центрированы по отдельности, мы хотим, чтобы изображение и текст были центрированы вместе как единое целое. На самом деле это то, что уже делает UIButton, поэтому нам просто нужно настроить интервал.

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

Я также превратил это в категорию для UIButton, поэтому он будет прост в использовании:

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

Итак, теперь все, что мне нужно сделать, это:

[button centerButtonAndImageWithSpacing:10];

И я получаю то, что мне нужно каждый раз. Больше не вмешиваться в кросс-вставки вручную.

РЕДАКТИРОВАТЬ: замена изображения и текста

В ответ на @Javal в комментариях

Используя этот же механизм, мы можем поменять изображение и текст. Чтобы выполнить обмен, просто используйте отрицательный интервал, но также укажите ширину текста и изображения. Это потребует, чтобы фреймы были известны, а макет уже выполнен.

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

Конечно, вы, вероятно, захотите сделать хороший метод для этого, потенциально добавив метод второй категории, это останется как упражнение для читателя.

Ответ 2

Я немного опаздываю на эту вечеринку, но я думаю, что у меня есть что-то полезное для добавления.

Ответ Kekoa велик, но, как отмечает RonLugge, он может заставить кнопку больше не уважать sizeToFit или, что более важно, может заставить кнопку обрезать ее содержимое, когда оно по размеру. Хлоп!

Во-первых,

Краткое объяснение того, как я считаю, что imageEdgeInsets и titleEdgeInsets работают:

Документы для imageEdgeInsets должны сказать следующее:

Используйте это свойство для изменения размера и изменения правильного прямоугольника рисования для изображения кнопки. Вы можете указать другое значение для каждой из четырех вложений (верхний, левый, нижний, правый). Положительное значение сжимается, или вставки, которые пересекают его ближе к центру кнопки. Отрицательное значение расширяет или завершает этот край.

Я считаю, что эта документация была написана, воображая, что кнопка не имеет названия, просто изображение. Это намного больше UIEdgeInsets об этом и ведет себя так, как обычно делают UIEdgeInsets. В принципе, кадр изображения (или заголовок, с titleEdgeInsets) перемещается внутрь для позитивных вставок и наружу для отрицательных вставок.

Хорошо, ну и что?

Я добираюсь туда! Здесь у вас есть по умолчанию, установка изображения и заголовка (граница кнопки зеленая, чтобы показать, где она):

Starting image; no space between title and image.

Если вам нужно расстояние между изображением и заголовком, не вызывая ни дробления, вам нужно установить четыре разные вставки, по два на каждом изображении и названии. Это потому, что вы не хотите изменять размеры кадров этих элементов, а только свои позиции. Когда вы начинаете думать так, необходимое изменение в отличной категории Кекоа становится ясным:

@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

Но подождите, как вы говорите, когда я это сделаю, я понимаю:

Spacing is good, but the image and title are outside the view's frame.

О, да! Я забыл, что документы предупредили меня об этом. Говорят, в частности:

Это свойство используется только для позиционирования изображения во время компоновки. Кнопка не использует это свойство для определения intrinsicContentSize и sizeThatFits:

Но есть свойство, которое может помочь, и что contentEdgeInsets. Документы для этого говорят, в частности:

Кнопка использует это свойство для определения intrinsicContentSize и sizeThatFits:

Это звучит неплохо. Так что позвольте еще раз настроить категорию:

@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

И что вы получаете?

Spacing and frame are now correct.

Мне кажется победителем.


Работаете в Swift и не хотите вообще думать? Здесь окончательная версия расширения в 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)
    }
}

Ответ 3

В интерфейсе Builder. Выберите UIButton → Attributes Inspector → Edge = Title и измените вставки кромок

Ответ 4

Также, если вы хотите сделать что-то похожее на

enter image description here

Вам нужно

1. Настройте горизонтальное и вертикальное выравнивание для кнопки

enter image description here

  1. Найдите все требуемые значения и установите 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)];
    

Это упорядочит ваше название и изображение на кнопке.

Также обратите внимание на это обновление на каждом ретрансляторе


Свифта

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

Ответ 5

Вы можете избежать больших проблем, используя это -

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

Это автоматически выровняет ваш контент по левому краю (или там, где вы хотите)

Свифт 3:

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

Ответ 6

В Xcode 8.0 вы можете просто сделать это, изменив insets в инспекторе размеров.

Выберите UIButton → Attributes Inspector → перейдите к инспектору размера и измените вставки содержимого, изображения и названия.

введите описание изображения здесь

И если вы хотите изменить изображение с правой стороны, вы можете просто изменить семантическое свойство на Force Right-to-left в инспекторе атрибутов.

введите описание изображения здесь

Ответ 7

Я тоже немного опаздываю на эту вечеринку, но я думаю, что мне есть что добавить: o).

Я создал подкласс UIButton, целью которого является возможность выбирать, где изображение кнопки расположено по вертикали или по горизонтали.

Это означает, что вы можете создавать такие кнопки: разные типы кнопок

Вот подробности о том, как создавать эти кнопки с моим классом:

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)

Для этого я добавил 2 атрибута: imageVerticalAlignment и imageHorizontalAlignment. Конечно, если ваша кнопка имеет только изображение или заголовок... не используйте этот класс вообще!

Я также добавил атрибут с именем imageToTitleSpacing, который позволяет вам регулировать пространство между заголовком и изображением.

Этот класс постарается изо всех сил быть совместимым, если вы хотите использовать imageEdgeInsets, titleEdgeInsets и contentEdgeInsets напрямую или в сочетании с новыми атрибутами макета.

Как поясняет нам @ravron, я стараюсь сделать правильную краску содержимого кнопки (как вы можете видеть с красными границами).

Вы также можете использовать его в Interface Builder:

  • Создать UIButton
  • Измените класс кнопки
  • Отрегулируйте макеты с использованием "center", "top", "bottom", "left" или "right" атрибуты кнопки

Здесь код (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()
    }
}

Ответ 8

Небольшое дополнение к Riley Avron отвечает на изменения локализации аккаунта:

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

Ответ 9

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

Использование:

button.centerTextAndImage(spacing: 10.0)

Ответ 10

Вот простой пример использования imageEdgeInsets Это сделает кнопку 30x30 с матовой площадью на 10 пикселей больше (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)

Ответ 11

Элегантный способ в Swift 3 и лучше понять:

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

Ответ 12

Я пишу код ниже. Хорошо работает в версии продукта. 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()
}
}

Ответ 13

Версия решения Swift 4.2 будет иметь следующий вид:

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)