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

251

Я хотел бы разместить значок слева от двух строк текста так, чтобы между изображением и началом текста было около 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 будет перемещать текст полностью влево, поэтому я ожидал -5, чтобы сдвинуть его наполовину с левой стороны, но это не так.

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

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

Джастин Гальзич
источник

Ответы:

393

Я согласен с документацией 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];

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

Kekoa
источник
Если у меня есть разные заголовки для обычного и выделенного текста, как я могу перецентрировать его, когда пользователь выделяет и снимает выделение с кнопки?
user102008
@ user102008 Разные названия полностью? Или просто разные цвета? Позиционирование не должно меняться, если вы используете [UIButton setTitleColor:forState:], или даже [UIButton setTitle:forState:].
Кекоа
5
Как вы исправляете [button sizeToFit], чтобы он правильно масштабировал?
RonLugge
@RonLugge Я не уверен, зачем вам sizeToFit, поэтому я не могу ответить на ваш вопрос. Это прекрасно работает для меня без использования sizeToFit.
Кекоа
Мне нужен sizeToFit, потому что кнопки / текст динамические, поэтому мне нужно изменить размер кнопки, чтобы она соответствовала (определенной пользователем) метке. Проблема в том, что это не компенсирует добавленное пространство. Я переписал его и вручную увеличил ширину кадра на 10.
RonLugge
397

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

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

Во-первых, хотя,

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

В документах дляimageEdgeInsets имеют следующее сказать, в частности , говорится :

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

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

Ок, и что?

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

Начальное изображение;  нет пробела между заголовком и изображением.

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

@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

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

Интервал хорош, но изображение и заголовок находятся за пределами рамки представления.

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

Это свойство используется только для позиционирования изображения во время макета. Кнопка не использует это свойство для определения 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

А что ты получаешь?

Интервал и рамка теперь правильные.

Выглядит как победитель для меня.


Работаете в 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)
    }
}
ravron
источник
25
Какой отличный ответ! Да, на вечеринку опоздало на несколько лет, но это решило проблему intrinsicContentSizeнеправильности, что очень важно в эти дни автоматической разметки, так как первоначальный ответ был принят.
Эйси
2
И если вам нужно одинаковое расстояние между внешней spacingстороной кнопки, изображением и надписью, добавьте к каждому из четырех значений self.contentEdgeInsets, например, так:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
Эрик ван дер Нойт,
1
Отличный ответ, жаль, что он не очень хорошо работает, когда изображение выровнено по правому краю, а длина текста может отличаться.
jlpiedrahita
2
Почти идеальный ответ! Единственное, чего не хватает, так это того, что вставки изображения и заголовка должны быть инвертированы, когда они работают на интерфейсе справа налево.
Jeeeyul
как установить соотношение изображения к 1 - 1
Естай Муратов
39

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

почетный гражданин
источник
38

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

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

Тебе нужно

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

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

  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)
            }
        }
    }
}
GBK
источник
29

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

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

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

Свифт 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;
Nishant
источник
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; //
Опечатка
25

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

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

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

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

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

Сахил
источник
в моем случае изображение кнопки никогда не перемещается к правой стороне текста с Xcode 10? вы можете помочь?
Сатиш Мавани
Привет, Сатиш, с XCode 10 это также работает нормально. Надеюсь, вы устанавливаете изображение, а не фоновое изображение, и вы также можете изменить изображение насекомых с помощью инспектора размера.
Сахил
18

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

Я создал 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, я изо всех сил стараюсь сделать правильный край содержимого кнопки (как вы можете видеть с красными полями).

Вы также можете использовать его в Интерфейсном Разработчике:

  1. Создать UIButton
  2. Изменить класс кнопки
  3. Настройте атрибуты Layoutable, используя «center», «top», «bottom», «left» или «right» атрибуты кнопки

Вот код ( сущность ):

@IBDesignable
class LayoutableButton: UIButton {

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


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


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


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

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

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

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

    var extraContentEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

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

    var extraImageEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

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

    var extraTitleEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

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

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


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

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

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

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

            let halfImageToTitleSpacing = imageToTitleSpacing / 2.0

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

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

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

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

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

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

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

        super.layoutSubviews()
    }
}
gbitaudeau
источник
1
Исправить некоторые вещи, чтобы не нарушать IB с error: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
nebiros
@nebiros Можете ли вы объяснить, что не так и как это исправить, пожалуйста?
gbitaudeau
@gbitaudeau , когда я скопировать и вставить сценарий, я получил эту ошибку, error: IB Designables: Failed to update auto layout status: The agent crashed, потому что init(frame: CGRect)не было преодолено, и я добавить @availableаннотацию ..., вы можете , diff -Naurесли вы хотите, ;-)
nebiros
9

Небольшое дополнение к ответу Райли Аврон на изменения локали аккаунта:

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

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}
orxelm
источник
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)
    }
}

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

button.centerTextAndImage(spacing: 10.0)
Hemang
источник
Как можно использовать custiom размер изображения?
Midhun р
2

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

Вот простой пример того, как использовать 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)
Харрис
источник
0

Элегантный способ в 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)
}
teonicel
источник
-1

Версия решения 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)
Сохейл Новинфард
источник