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