Ios 多行按钮和自动布局

Ios 多行按钮和自动布局,ios,uibutton,autolayout,Ios,Uibutton,Autolayout,我创建了一个视图控制器,如下所示: 我希望顶部的两个按钮与整个视图的左/右边缘之间始终有20个点。它们也应该始终具有相同的宽度。我已经为所有这些创建了约束,并且它正是我想要的工作方式。问题在于垂直约束。按钮应始终位于顶部边缘下方20点处。他们应该有相同的高度。但是,autolayout不考虑左侧标签需要两行来容纳其所有文本,因此结果如下所示: 我想让它看起来像第一张照片。我不能给按钮添加恒定的高度限制,因为当应用程序在iPad上运行时,只需要一行,如果有额外的空间,那将是浪费 在viewDi

我创建了一个视图控制器,如下所示:

我希望顶部的两个按钮与整个视图的左/右边缘之间始终有20个点。它们也应该始终具有相同的宽度。我已经为所有这些创建了约束,并且它正是我想要的工作方式。问题在于垂直约束。按钮应始终位于顶部边缘下方20点处。他们应该有相同的高度。但是,autolayout不考虑左侧标签需要两行来容纳其所有文本,因此结果如下所示:

我想让它看起来像第一张照片。我不能给按钮添加恒定的高度限制,因为当应用程序在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;
}
但这丝毫没有改变任何事情


问题:我如何使自动布局尊重左侧按钮需要两行?

您是否尝试过使用此选项:

self.leftButton.titleLabel.textAlignment = NSTextAlignmentCenter;
self.leftButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping | NSLineBreakByTruncatingTail;
self.leftButton.titleLabel.numberOfLines = 0;

我也有同样的问题,我希望我的按钮与标题一起成长。我必须对
ui按钮及其
intrinsicContentSize
进行子分类,以便它返回标签的固有大小

- (CGSize)intrinsicContentSize
{
    return self.titleLabel.intrinsicContentSize;
}
由于
UILabel
是多行的,因此其
intrinsicContentSize
未知,您必须设置其
preferredMaxLayoutWidth

布局的其余部分应该可以工作。如果将两个按钮的高度设置为相等,则另一个按钮的高度将增加到。完整按钮如下所示

@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

Swift 4.1.2基于@Jan answer的版本

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

}

根据@Jan的回答再次更新了Swift/Swift 2.0版本

@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
  }
}
Swift 3.1的调整

intrisicContentSize是属性而不是函数

override var intrinsicContentSize: CGSize {
    return self.titleLabel!.intrinsicContentSize
}

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

@Jan的答案在(至少)iOS 8.1、9.0和Xcode 9.1中对我不起作用。问题是:
titleLabel
-intrinsicContentSize
返回非常大的宽度和很小的高度,因为根本没有宽度限制(
titleLabel.frame
调用时大小为零,导致测量问题)。此外,它没有考虑可能的插入和/或图像

因此,下面是我的实现,它应该解决所有问题(只需要一种方法):


这尊重内容边缘插图,并为我工作:

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

在iOS11上有一个没有子类的解决方案。只需在代码中设置一个附加约束,以匹配
按钮
按钮的高度。标题标签

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];
let constraint = NSLayoutConstraint(item: button,
                                    attribute: .height,
                                    relatedBy: .equal,
                                    toItem: button.titleLabel,
                                    attribute: .height,
                                    multiplier: 1,
                                    constant: 0)

self.addConstraint(constraint)
在某些情况下(如前所述):

Swift:

// 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];
let constraint = NSLayoutConstraint(item: button,
                                    attribute: .height,
                                    relatedBy: .equal,
                                    toItem: button.titleLabel,
                                    attribute: .height,
                                    multiplier: 1,
                                    constant: 0)

self.addConstraint(constraint)
+


添加缺少的约束:

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

对我来说,一个简单的解决方案是:在Swift 4.2中,通过添加基于标题标签高度的按钮高度约束,使多行按钮尊重其标题高度:

let height=NSLayoutConstraint(项目:multilineButton,
属性:。高度,
关系人:。相等,
toItem:multileButton.titleLabel,
属性:。高度,
乘数:1,
常数:0)
multilineButton.addConstraint(高度)

我将手动计算
首选MaxLayoutWidth
而不是调用LayoutSubView两次

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

其他答案中没有一个都对我有用。以下是我的答案:

class MultilineButton: UIButton {
    func setup() {
        titleLabel?.textAlignment = .center
        titleLabel?.numberOfLines = 0
        titleLabel?.lineBreakMode = .byWordWrapping
    }

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

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    override var intrinsicContentSize: CGSize {
        var titleContentSize = titleLabel?.intrinsicContentSize ?? CGSize.zero
        titleContentSize.height += contentEdgeInsets.top + contentEdgeInsets.bottom
        titleContentSize.width += contentEdgeInsets.left + contentEdgeInsets.right
        return titleContentSize
    }

    override func layoutSubviews() {
        titleLabel?.preferredMaxLayoutWidth = 300 // Or whatever your maximum is
        super.layoutSubviews()
    }
}

然而,这并不符合图像的要求。

这里有很多答案,但@Yevhenia Zelenska的简单答案对我来说很好。简化Swift 5版本:


版本,它还考虑了
标题插入
,不覆盖标准按钮行为,除非
标题标签?.numberOfLines
设置为
,按钮图像设置为

打开类按钮:UIButton{
覆盖打开变量intrinsicContentSize:CGSize{
如果让titleLabel=titleLabel,则titleLabel.numberOfLines==0,image==nil{
let size=titleLabel.intrinsicContentSize
让结果=CGSize(宽度:size.width+contentEdgeInsets.horizontal+titleEdgeInsets.horizontal,
高度:size.height+contentEdgeInsets.vertical+titleEdgeInsets.vertical)
返回结果
}否则{
返回super.intrinsicContentSize
}
}
覆盖打开的func布局子视图(){
super.layoutSubviews()
如果让titleLabel=titleLabel,则titleLabel.numberOfLines==0,image==nil{
让优先级=UILayoutPriority.defaultLow+1
如果titleLabel.horizontalContentHuggingPriority!=优先级{
titleLabel.horizontalContentHuggingPriority=优先级
}
如果titleLabel.verticalContentHuggingPriority!=优先级{
titleLabel.verticalContentHuggingPriority=优先级
}
设rect=titleRect(forContentRect:contentRect(forBounds:bounds))
titleLabel.preferredMaxLayoutWidth=rect.size.width
super.layoutSubviews()
}
}
}

我找不到一个考虑了所有这些因素的正确答案:

  • 仅使用自动布局(意味着不覆盖
    layoutSubviews
  • 尊重按钮的
    contentEdgeInsets
  • 极简主义(不玩按钮的内在内容大小)
  • 这是我对它的看法,它尊重上面的三点

    final class MultilineButton: UIButton {
    
        /// Buttons don't have built-in layout support for multiline labels. 
        /// This constraint is here to provide proper button's height given titleLabel's height and contentEdgeInset.
        private var heightCorrectionConstraint: NSLayoutConstraint?
               
        override var contentEdgeInsets: UIEdgeInsets {
            didSet {
                heightCorrectionConstraint?.constant = -(contentEdgeInsets.top + contentEdgeInsets.bottom)
            }
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupLayout()
        }
          
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupLayout()
        }
        
        private func setupLayout() {  
            titleLabel?.numberOfLines = 0
          
            heightCorrectionConstraint = titleLabel?.heightAnchor.constraint(equalTo: heightAnchor, constant: 0)
            heightCorrectionConstraint?.priority = .defaultHigh
            heightCorrectionConstraint?.isActive = true
        }
    }
    
    注 我没有修改按钮的intrinsicContentSize,没有必要玩它。当标签为2+行时,按钮的自然intrinsicContentSize高度小于所需高度。我添加的约束(
    heightCorrectionConstraint
    )会自动更正这一点。只需确保按钮的
    contentHuggingPriority@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()
    }
    }
    
    class MultilineButton: UIButton {
        func setup() {
            titleLabel?.textAlignment = .center
            titleLabel?.numberOfLines = 0
            titleLabel?.lineBreakMode = .byWordWrapping
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setup()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        override var intrinsicContentSize: CGSize {
            var titleContentSize = titleLabel?.intrinsicContentSize ?? CGSize.zero
            titleContentSize.height += contentEdgeInsets.top + contentEdgeInsets.bottom
            titleContentSize.width += contentEdgeInsets.left + contentEdgeInsets.right
            return titleContentSize
        }
    
        override func layoutSubviews() {
            titleLabel?.preferredMaxLayoutWidth = 300 // Or whatever your maximum is
            super.layoutSubviews()
        }
    }
    
    @IBOutlet private weak var button: UIButton! {
        didSet {
            guard let titleHeightAnchor = button.titleLabel?.heightAnchor else { return }
            button.heightAnchor.constraint(equalTo: titleHeightAnchor).isActive = true
        }
    }
    
    final class MultilineButton: UIButton {
    
        /// Buttons don't have built-in layout support for multiline labels. 
        /// This constraint is here to provide proper button's height given titleLabel's height and contentEdgeInset.
        private var heightCorrectionConstraint: NSLayoutConstraint?
               
        override var contentEdgeInsets: UIEdgeInsets {
            didSet {
                heightCorrectionConstraint?.constant = -(contentEdgeInsets.top + contentEdgeInsets.bottom)
            }
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            setupLayout()
        }
          
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setupLayout()
        }
        
        private func setupLayout() {  
            titleLabel?.numberOfLines = 0
          
            heightCorrectionConstraint = titleLabel?.heightAnchor.constraint(equalTo: heightAnchor, constant: 0)
            heightCorrectionConstraint?.priority = .defaultHigh
            heightCorrectionConstraint?.isActive = true
        }
    }