Autolayout - внутренний размер UIButton не включает титульные вставки

196

Если у меня UIButton организован с использованием autolayout, его размер корректно подстраивается под его содержимое.

Если я установлю изображение как button.image, внутренний размер снова, кажется, объясняет это.

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

Как я могу гарантировать, что внутренняя ширина кнопки составляет вкладку?

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

Редактировать:

Я использую следующее:

[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];

Цель состоит в том, чтобы добавить некоторое разделение между изображением и текстом.

Бен Паккард
источник
3
Вы подали это как радар? Это, безусловно, ошибка в собственных расчетах размера UIButton.
Райан Пулос
1
Я был готов подать радар, но на самом деле это, кажется, ожидаемое поведение. Это задокументировано в свойствах * EdgeInsets UIButton : « Заданные вами вставки применяются к прямоугольнику заголовка после того, как размер этого прямоугольника соответствует размеру текста кнопки. Таким образом, положительные значения вставки могут фактически обрезать текст заголовка. [...] Кнопка не использует это свойство для определения intrinsicContentSize и sizeThatFits:. "
Гийом Альгис,
7
@GuillaumeAlgis Я бы сказал, что, хотя это и заявленное поведение, это совсем не то, чего можно ожидать при использовании автопоставки. Я подал ошибку и хотел бы, чтобы другие тоже ее подали.
меммон
Если вы можете сделать ссылку на ошибку радара здесь, мы можем нажать на нее и +1 на нее?
17
1
Из titleEdgeInsetдокументации: The insets you specify are applied to the title rectangle after that rectangle has been sized to fit the button’s text. Thus, positive inset values may actually clip the title text. Итак, добавив вкладку, вы заставляете кнопку
Marco

Ответы:

192

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

  • Внутренняя ширина кнопки определяется на основе ширины заголовка и ширины значка, а также вставок левого и правого края содержимого .

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

  • Если вы добавляете левую вставку содержимого, она рассчитывается относительно текста, а не текста + значка.

  • Если вы установите отрицательную левую вставку изображения, изображение будет вытянуто влево, но общая ширина кнопки не изменится.

  • Если вы установите отрицательную левую вставку изображения, фактический макет использует половину этого значения. Таким образом, чтобы получить левую вставку -20, вы должны использовать значение левой вставки -40 в Interface Builder.

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

Некоторые примеры значений:

// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)
jaredsinclair
источник
почему фактический макет использует половину отрицательного значения левой вставки ?? Я столкнулся с той же проблемой!
Тони Лин
1
Это здорово, что есть обходной путь, но я надеюсь, что он не используется для оправдания странного поведения UIButton.
funct7
205

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

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

Обновление : в Xcode 7 есть ошибка, из-за которой нельзя вводить отрицательные значения вRight поле Inset, но вы можете использовать шаговый регулятор рядом с ним, чтобы уменьшить значение. (Спасибо, Стюарт)

Делая это, вы добавите 8pt промежутка между изображением и заголовком и увеличите внутреннюю ширину кнопки на ту же величину. Как это:

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

n.Drake
источник
2
Он использует contentEdgeInsets (который не содержит ошибок), чтобы позволить autolayout увеличить ширину кнопки. И переместите метку в пустое место справа. Умный способ обойти ошибку вставки края заголовка.
Угур
7
Этот трюк больше не работает. Построитель интерфейса больше не принимает отрицательные значения в Rightполе.
Йорис Манс
7
@JorisMans Вы не можете вводить отрицательные значения, но это сработало для меня, используя шаговый регулятор справа от текстового поля, чтобы перейти к требуемому отрицательному значению ... иди!
Стюарт
3
Это должен быть первый ответ, почему это здесь? Я попробовал другие 5, прежде чем нашел это ...
Лорд Жолт
2
Я сделал вкладку «Право на содержимое» 16 для центрирования текста в UIButton
coolcool1994
96

Почему бы не переопределить intrinsicContentSizeметод в UIView? Например:

- (CGSize) intrinsicContentSize
{
    CGSize s = [super intrinsicContentSize];

    return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
                      s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}

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

Маартен
источник
1
Кнопки не должны быть переопределены, насколько я знаю. Проблема в том, что каждый тип кнопки реализован в своем подклассе.
Султан
2
intrinsicContentSizeэто метод на UIView, а не UIButton, так что вы не будете возиться ни с какими методами UIButton. Apple не считает, что это проблема: «Переопределение этого метода позволяет настраиваемому представлению сообщать системе макетов, какой размер он хотел бы основывать на своем контенте». И ОП ничего не сказал о разных кнопках, кроме одной.
Мартен
1
Это определенно работает, и это решение, которое я выбрал. intrinsicContentSizeэто действительно метод в UIView, а UIButton является подклассом UIView, поэтому, конечно, вы можете переопределить этот метод; ничто в документации Apple не говорит, что вы не должны. Просто создайте подкласс UIButton, используя переопределенный метод Maarten, и измените UIButton в Интерфейсном Разработчике на тип YourUIButtonSubclass, и он будет отлично работать.
n8tr
37
Мне кажется, что intrinsicContentSizeUIButton должен добавить в titleEdgeInsets, я собираюсь подать ошибку в Apple.
progrmr
6
Я согласен, и то же самое для imageEdgeInsets.
Рикардо Санчес-Саез
87

Вы не указали, как вы устанавливаете вставки, поэтому я предполагаю, что вы используете titleEdgeInsets, потому что я вижу тот же эффект, который вы получаете. Если я использую contentEdgeInsets вместо этого, он работает правильно.

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}
rdelmar
источник
Я действительно использую titleEdgeInsets. Мне нужно удалить заголовок от изображения, а не изображение от края кнопки. Может быть, я должен просто использовать изображение с некоторыми отступами? Кажется, хаки
Бен Паккард
Это отлично работает в сочетании с autolayout, спасибо!
Cal S
3
Это лучшее решение, так как оно делает именно то, что вы хотите, не касаясь intrinsicContentSize.
RyJ
28
Это НЕ отвечает на вопрос при использовании изображения и необходимости корректировать вставку между изображением и заголовком!
Броуди Робертсон
23

И для Свифта это сработало:

extension UIButton {
    override open var intrinsicContentSize: CGSize {
        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)
    }
}

Любовь У Свифт

pegpeg
источник
1
Даже если вы и не должны этого делать, в этом случае лучше создать подкласс, потому что в документах Apple прямо указано, что внутренний размер не включает titleEdgeInsets в свои вычисления, и поэтому, используя расширение, вы нарушаете не только ожидания Apple, но и всех других разработчиков, которые читают документы.
Сирены
18

Этот поток немного староват, но я сам столкнулся с этим и смог решить его с помощью отрицательной вставки. Например, замените желаемые значения заполнения здесь:

UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
                                            -desiredRightPadding,
                                            -desiredTopPadding,
                                            -desiredLeftPadding);

В сочетании с правильными ограничениями автоматической разметки вы получаете кнопку автоматического изменения размера, которая содержит изображение и текст! Видно ниже с desiredLeftPaddingнабором 10.

Кнопка с изображением и кратким текстом

Кнопка с изображением и длинным текстом

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

Брайан Герстле
источник
1
Это решение, которое я использовал, так как оно не требует создания подклассов. Не будет работать, если у вашей кнопки есть фон, но это обычно не проблема для iOS 7
Хосе Мануэль Санчес
Это будет работать с фоновым изображением, если вы также установите смещение содержимого кнопки (положительное значение> = заголовок вставки).
Бен Флинн
9

Для Swift 3 на основе ответа pegpeg :

extension UIButton {

    override open var intrinsicContentSize: CGSize {

        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)

    }

}
TheoK
источник
Привет. я хочу использовать пользовательскую кнопку расширения в interfacebuilder.
Пожалуйста,
6

Все вышеперечисленное не работает для iOS 9+ , что я сделал:

  • Добавьте ограничение ширины (для минимальной ширины, когда у кнопки нет текста. Кнопка будет автоматически масштабироваться, если предоставляется текст)
  • установить отношение больше или равно

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

Теперь, чтобы добавить рамку вокруг кнопки, просто используйте метод:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
Oritm
источник
Почему нет? Он автоматически масштабируется с содержимым, вам просто нужно установить минимальную ширину (которая может быть меньше, чем отображаемый текст)
Oritm
Потому что вы определяете минимальную ширину. Вся идея autolayout состоит в том, чтобы сделать это без установки какой-либо явной (минимальной) ширины.
Йорис Манс
Речь идет не о ширине, вы можете установить ширину в 1, если хотите, но для автоматического размещения необходимо знать, что ширина может быть равна или больше . Я обновил свой ответ
Oritm
Ограничение ширины вообще не требуется, ключом является contentEdgeInset, а затем автоматически используется макет для внутреннего размера контента.
Крис Коновер
5

Я хотел добавить 5pt пробел между моей иконкой UIButton и надписью. Вот как я этого добился:

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);

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

При добавлении правильной вставки содержимого в соответствии со смещением вставок заголовка мой текст не выходит за пределы кнопки.

ORJ
источник
3

Опция также доступна в конструкторе интерфейсов. Смотрите вставку. Я установил влево и вправо на 3. Работает как шарм.

Скриншот строителя интерфейса

zeiteisen
источник
1
Да, как объясняет этот ответ , причина, по которой он работает, заключается в том, что вы настраиваете Edge: Content здесь вместо Edge: Title или Edge: Image .
смайлиборг
1

Решение, которое я использую, заключается в добавлении ограничения ширины на кнопку. Затем где-нибудь в инициализации, после того, как ваш текст установлен, обновите ограничение ширины следующим образом:

self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;

Где 8, какой бы ни была ваша вставка.

Боб Сприн
источник
Что такое buttonWidthConstraint?
Алексей Голиков
@AlexeyGolikov An NSLayoutConstraint - developer.apple.com/library/mac/documentation/AppKit/Reference/…
1in9ui5t
1
Это не очень хорошее решение, потому что, если изменяется размер внутреннего содержимого кнопки, вам нужно вручную обновить constantограничение до нового значения ... и знать, когда изменяется размер внутреннего содержимого кнопки, сложно без подкласса кнопки.
смайлиборг
Аюп. Я больше не использую этот метод. Удивило, что это было достойно отрицательного голоса, но ¯_ (ツ) _ / ¯
Боб Сприн
Вызов setNeedsUpdateConstraintsможет быть сделан "вручную" после обновления заголовка кнопки или изображения. После этого вы можете переопределить updateConstraintsи пересчитать buttonWidthConstraintконстанту. Это не обязательно лучший подход, но он работает достаточно хорошо для меня. YMMV;)
Оливье
1

Вызов sizeToFit () гарантирует, что contentEdgeInset вступит в силу

extension UIButton {

   open override func draw(_ rect: CGRect) { 
       self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40)
       self.sizeToFit()
   }
}
Али
источник
Вопрос по поводуtitleEdgeInsets
androidguy