Многострочный UIButton и автозапуск
Я создал контроллер вида, который выглядит так:
![enter image description here]()
Я хочу, чтобы две верхние кнопки всегда имели 20 точек между собой и левыми/правыми краями всего изображения. Они должны всегда иметь одинаковую ширину. Я создал ограничения для всего этого, и он работает именно так, как я этого хочу. Проблема заключается в вертикальных ограничениях. Кнопки всегда должны быть на 20 пунктов ниже верхнего края. Они должны иметь одинаковую высоту. Однако автоопределение не учитывает, что левой метке нужны две строки, чтобы соответствовать всему ее тексту, поэтому результат выглядит следующим образом:
![enter image description here]()
Я хочу, чтобы это было на первом рисунке. Я не могу добавить постоянные ограничения высоты для кнопок, потому что, когда приложение работает на iPad, требуется только одна строка, и тогда было бы бесполезно иметь дополнительное пространство.
В viewDidLoad
Я пробовал это:
- (void)viewDidLoad
{
[super viewDidLoad];
self.leftButton.titleLabel.preferredMaxLayoutWidth = (self.view.frame.size.width - 20.0 * 3) / 2.0;
self.rightButton.titleLabel.preferredMaxLayoutWidth = (self.view.frame.size.width - 20.0 * 3) / 2.0;
}
Но это ничего не меняло.
Вопрос: как сделать автозапуск уважением, что левой кнопке нужны две строки?
Ответы
Ответ 1
У меня была такая же проблема, когда я хотел, чтобы моя кнопка развивалась вместе с ее названием. Мне пришлось подстроить UIButton
и его intrinsicContentSize
, чтобы он возвращал внутренний размер метки.
- (CGSize)intrinsicContentSize
{
return self.titleLabel.intrinsicContentSize;
}
Так как UILabel
является многострочным, его intrinsicContentSize
неизвестен, и вы должны установить его preferredMaxLayoutWidth
См. статью objc.io об этом
- (void)layoutSubviews
{
[super layoutSubviews];
self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.size.width;
[super layoutSubviews];
}
Остальная часть макета должна работать. Если вы установили обе кнопки с одинаковой высотой, другая будет расти. Полная кнопка выглядит так:
@implementation TAButton
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
self.titleLabel.numberOfLines = 0;
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
return self;
}
- (CGSize)intrinsicContentSize
{
return self.titleLabel.intrinsicContentSize;
}
- (void)layoutSubviews
{
[super layoutSubviews];
self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.size.width;
[super layoutSubviews];
}
@end
Ответ 2
Swift 4.1.2 Версия основана на ответе @Jan.
import UIKit
class MultiLineButton: UIButton {
// MARK: - Init
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
private func commonInit() {
self.titleLabel?.numberOfLines = 0
self.titleLabel?.lineBreakMode = .byWordWrapping
}
// MARK: - Overrides
override var intrinsicContentSize: CGSize {
get {
return titleLabel?.intrinsicContentSize ?? CGSize.zero
}
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = titleLabel?.frame.size.width ?? 0
super.layoutSubviews()
}
}
Ответ 3
Это касается вложений в области содержимого и работает для меня:
class MultilineButton: UIButton {
func setup() {
self.titleLabel?.numberOfLines = 0
self.setContentHuggingPriority(UILayoutPriorityDefaultLow + 1, for: .vertical)
self.setContentHuggingPriority(UILayoutPriorityDefaultLow + 1, for: .horizontal)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
override var intrinsicContentSize: CGSize {
let size = self.titleLabel!.intrinsicContentSize
return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
Ответ 4
Полный класс в Swift 3 - на основе @Jan, @Quantaliinuxite и @matt bezark:
@IBDesignable
class MultiLineButton:UIButton {
//MARK: -
//MARK: Setup
func setup () {
self.titleLabel?.numberOfLines = 0
//The next two lines are essential in making sure autolayout sizes us correctly
self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, for: .vertical)
self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, for: .horizontal)
}
//MARK:-
//MARK: Method overrides
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
override var intrinsicContentSize: CGSize {
return self.titleLabel!.intrinsicContentSize
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
Ответ 5
ОБНОВЛЕННАЯ версия Swift/Swift 2.0 снова на основе ответа @Jan
@IBDesignable
class MultiLineButton:UIButton {
//MARK: -
//MARK: Setup
func setup () {
self.titleLabel?.numberOfLines = 0
//The next two lines are essential in making sure autolayout sizes us correctly
self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, forAxis: .Vertical)
self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, forAxis: .Horizontal)
}
//MARK:-
//MARK: Method overrides
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
override func intrinsicContentSize() -> CGSize {
return self.titleLabel!.intrinsicContentSize()
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
Ответ 6
для Swift 3.1
intrisicContentSize - это свойство вместо функции
override var intrinsicContentSize: CGSize {
return self.titleLabel!.intrinsicContentSize
}
Ответ 7
добавьте отсутствующие ограничения:
if let label = button.titleLabel {
button.addConstraint(NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: button, attribute: .top, multiplier: 1.0, constant: 0.0))
button.addConstraint(NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: button, attribute: .bottom, multiplier: 1.0, constant: 0.0))
}
Ответ 8
Простое решение, работающее на меня: заставить многострочную кнопку учитывать высоту заголовка в Swift 4.2, добавив ограничение для высоты кнопки на основе высоты надписи заголовка:
let height = NSLayoutConstraint(item: multilineButton,
attribute: .height,
relatedBy: .equal,
toItem: multilineButton.titleLabel,
attribute: .height,
multiplier: 1,
constant: 0)
multilineButton.addConstraint(height)
Ответ 9
Вместо того, чтобы дважды вызывать layoutSubviews, я бы вычислял preferredMaxLayoutWidth
MaxLayoutWidth вручную
@objcMembers class MultilineButton: UIButton {
override var intrinsicContentSize: CGSize {
// override to have the right height with autolayout
get {
var titleContentSize = titleLabel!.intrinsicContentSize
titleContentSize.height += contentEdgeInsets.top + contentEdgeInsets.bottom
return titleContentSize
}
}
override func awakeFromNib() {
super.awakeFromNib()
titleLabel!.numberOfLines = 0
}
override func layoutSubviews() {
let contentWidth = width - contentEdgeInsets.left - contentEdgeInsets.right
let imageWidth = imageView?.width ?? 0 + imageEdgeInsets.left + imageEdgeInsets.right
let titleMaxWidth = contentWidth - imageWidth - titleEdgeInsets.left - titleEdgeInsets.right
titleLabel!.preferredMaxLayoutWidth = titleMaxWidth
super.layoutSubviews()
}
}
Ответ 10
Вы пробовали использовать это:
self.leftButton.titleLabel.textAlignment = NSTextAlignmentCenter;
self.leftButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping | NSLineBreakByTruncatingTail;
self.leftButton.titleLabel.numberOfLines = 0;
Ответ 11
@Ответ для меня не работает (по крайней мере) для iOS 8.1, 9.0 с Xcode 9.1. Проблема: titleLabel
-intrinsicContentSize
возвращает очень большую ширину и небольшую высоту, так как ограничение ширины вообще отсутствует (titleLabel.frame
при вызове имеет нулевой размер, что приводит к проблеме измерений). Более того, он не учитывает возможные вставки и/или изображение.
Итак, вот моя реализация, которая должна исправить все вещи (нужен только один метод):
@implementation PRButton
- (CGSize)intrinsicContentSize
{
CGRect titleFrameMax = UIEdgeInsetsInsetRect(UIEdgeInsetsInsetRect(UIEdgeInsetsInsetRect(
self.bounds, self.alignmentRectInsets), self.contentEdgeInsets), self.titleEdgeInsets
);
CGSize titleSize = [self.titleLabel sizeThatFits:CGSizeMake(titleFrameMax.size.width, CGFLOAT_MAX)];
CGSize superSize = [super intrinsicContentSize];
return CGSizeMake(
titleSize.width + (self.bounds.size.width - titleFrameMax.size.width),
MAX(superSize.height, titleSize.height + (self.bounds.size.height - titleFrameMax.size.height))
);
}
@end
Ответ 12
Существует решение без создания подклассов на iOS11. Просто нужно установить одно дополнительное ограничение в коде, чтобы соответствовать высоте button
и button.titleLabel
.
ObjC:
// In init or overriden updateConstraints method
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self.button
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.button.titleLabel
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0];
[self addConstraint:constraint];
И в некоторых случаях (как сказано ранее):
- (void)layoutSubviews {
[super layoutSubviews];
self.button.titleLabel.preferredMaxLayoutWidth = CGRectGetWidth(self.button.titleLabel.frame);
}
Swift:
let constraint = NSLayoutConstraint(item: button,
attribute: .height,
relatedBy: .equal,
toItem: button.titleLabel,
attribute: .height,
multiplier: 1,
constant: 0)
self.addConstraint(constraint)
+
override func layoutSubviews() {
super.layoutSubviews()
button.titleLabel.preferredMaxLayoutWidth = button.titleLabel.frame.width
}
Ответ 13
//Swift 4 - Create Dynamic Button MultiLine Dynamic
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
/// Add DemoButton 1
let demoButton1 = buildButton("Demo 1")
//demoButton1.addTarget(self, action: #selector(ViewController.onDemo1Tapped), for: .touchUpInside)
view.addSubview(demoButton1)
view.addConstraint(NSLayoutConstraint(item: demoButton1, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: demoButton1, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: -180))
}
func buildButton(_ title: String) -> UIButton {
let button = UIButton(type: .system)
button.backgroundColor = UIColor(red: 80/255, green: 70/255, blue: 66/255, alpha: 1.0)
//--------------------------
//to make the button multiline
//button.titleLabel!.lineBreakMode = .byWordWrapping
button.titleLabel?.textAlignment = .center
button.titleLabel?.numberOfLines = 0
//button.titleLabel?.adjustsFontSizeToFitWidth = true
//button.sizeToFit()
button.titleLabel?.preferredMaxLayoutWidth = self.view.bounds.width//200
button.layer.borderWidth = 2
let height = NSLayoutConstraint(item: button,
attribute: .height,
relatedBy: .equal,
toItem: button.titleLabel,
attribute: .height,
multiplier: 1,
constant: 0)
button.addConstraint(height)
//--------------------------
button.setTitle(title, for: UIControlState())
button.layer.cornerRadius = 4.0
button.setTitleColor(UIColor(red: 233/255, green: 205/255, blue: 193/255, alpha: 1.0), for: UIControlState())
button.translatesAutoresizingMaskIntoConstraints = false
return button
}
}