多行文本的UIButton和自动布局

39

我创建了一个视图控制器,看起来像这样:

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

但那并没有改变任何事情。

问题:我该如何让自动布局考虑到左侧按钮需要两行?


可能是因为您在左侧按钮上设置了“相同高度”约束。这将使它与其他按钮具有相同的高度,从而改变按钮内的内容大小。 - user2277872
我尝试移除“等高”约束,但是没有成功。 - user3124010
你尝试过在标签上添加">="约束条件来建立最小插入吗? - jaggedcow
不,但我现在尝试了一下,但不幸的是它仍然没有起作用。现在在iPad上,按钮是“双高”的,而我不希望它们是这样的。 - user3124010
17个回答

1
你尝试过使用这个吗:

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

1

Swift 3.1的微调

intrisicContentSize是一个属性而不是函数

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

1
版本,还考虑了 titleEdgeInsets,并且只有在 titleLabel?.numberOfLines 设置为 zero 且按钮图像设置为 nil 时才不会覆盖标准按钮行为。
open class Button: UIButton {

   override open var intrinsicContentSize: CGSize {
      if let titleLabel = titleLabel, titleLabel.numberOfLines == 0, image == nil {
         let size = titleLabel.intrinsicContentSize
         let result = CGSize(width: size.width + contentEdgeInsets.horizontal + titleEdgeInsets.horizontal,
                             height: size.height + contentEdgeInsets.vertical + titleEdgeInsets.vertical)
         return result
      } else {
         return super.intrinsicContentSize
      }
   }

   override open func layoutSubviews() {
      super.layoutSubviews()
      if let titleLabel = titleLabel, titleLabel.numberOfLines == 0, image == nil {
         let priority = UILayoutPriority.defaultLow + 1
         if titleLabel.horizontalContentHuggingPriority != priority {
            titleLabel.horizontalContentHuggingPriority = priority
         }
         if titleLabel.verticalContentHuggingPriority != priority {
            titleLabel.verticalContentHuggingPriority = priority
         }
         let rect = titleRect(forContentRect: contentRect(forBounds: bounds))
         titleLabel.preferredMaxLayoutWidth = rect.size.width
         super.layoutSubviews()
      }
   }
}

1

我找不到一个合适的答案,可以考虑以下所有因素:

  1. 仅使用AutoLayout(即不覆盖layoutSubviews
  2. 尊重按钮的contentEdgeInsets
  3. 极简主义(不玩弄按钮的intrinsicContentSize)

所以这是我的方法,它尊重上述所有三个要点。

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,因此不需要对其进行调整。当标签有两行或以上时,按钮的自然intrinsicContentSize高度小于所需高度。我添加的约束(heightCorrectionConstraint)会自动进行修正。只需确保按钮在垂直轴上的contentHuggingPriority小于heightCorrectionConstraint的优先级(这是默认设置)。


0

在(至少)iOS 8.1、9.0和Xcode 9.1中,@Jan的答案对我不起作用。问题是: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

这对我不起作用,而Jan的解决方案却可以。 - Ariel Malka

0

不要调用layoutSubviews两次,我会手动计算preferredMaxLayoutWidth

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接