防止自动布局拉伸自定义视图

Preventing Auto Layout from stretching custom view

我已经在 swift 中创建了一个自定义视图,并且正在尝试让它正确显示。它本质上是一张 material 卡片,可以通过按更多按钮来扩展视图。指定底部约束时出现我的问题。它是必需的,但设置它会拉伸我的自定义视图。

我已经在 android 中实现了它,我想我正在尝试找到与 android:height='wrap_content' 类似的东西。我已经尝试设置宽高比约束,这可以使我的视图保持适当的大小,但可以防止自定义视图在其子视图更改时扩展。此外,我尝试使用 lessThanOrEqualTo 约束,但这太模糊,无法满足底部约束。

这是我的 UIExpandableCard 视图的样子:

import Foundation
import MaterialComponents

@IBDesignable
public class UIExpandableCard: UIView {

    // attributes
    @IBInspectable var overlineText: String? {
        didSet {
            overlineLabel.text = overlineText?.uppercased()
        }
    }

    @IBInspectable var headlineText: String? {
        didSet {
            headlineLabel.text = headlineText
        }
    }

    @IBInspectable var bodyText: String? {
        didSet {
            bodyLabel.text = bodyText
        }
    }

    @IBInspectable var logoImage: UIImage? {
        didSet {
            logoImageView.image = logoImage
        }
    }

    var cardView: MDCCard!
    var overlineLabel: UILabel!
    var headlineLabel: UILabel!
    var bodyLabel: UILabel!
    var moreButton: UIButton!
    var logoImageView: UIImageView!

    var isCardExpanded = false


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

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

    private func setup() {
        self.translatesAutoresizingMaskIntoConstraints = false
        setupViews()
        setupConstraints()
    }

    private func setupViews() {
        self.clipsToBounds = true
        cardView = MDCCard(frame: CGRect.zero)
        cardView.translatesAutoresizingMaskIntoConstraints = false
        cardView.isInteractable = false
        self.addSubview(cardView)


        overlineLabel = UILabel(frame: CGRect.zero)
        overlineLabel.translatesAutoresizingMaskIntoConstraints = false
        overlineLabel.font = UIFont.preferredFont(forTextStyle: .footnote)

        overlineLabel.text = overlineText?.uppercased()
        self.addSubview(overlineLabel)

        headlineLabel = UILabel(frame: CGRect.zero)
        headlineLabel.translatesAutoresizingMaskIntoConstraints = false
        headlineLabel.font = UIFont.preferredFont(forTextStyle: .title1)
        headlineLabel.text = headlineText
        self.addSubview(headlineLabel)

        bodyLabel = UILabel(frame: CGRect.zero)
        bodyLabel.translatesAutoresizingMaskIntoConstraints = false
        bodyLabel.font = UIFont.preferredFont(forTextStyle: .body)
        bodyLabel.numberOfLines = 1
        bodyLabel.text = bodyText
        self.addSubview(bodyLabel)

        logoImageView = UIImageView(image: logoImage)
        logoImageView.translatesAutoresizingMaskIntoConstraints = false
        logoImageView.contentMode = .scaleAspectFit
        self.addSubview(logoImageView)

        moreButton = UIButton(type: .roundedRect)
        moreButton.isUserInteractionEnabled = true
        moreButton.translatesAutoresizingMaskIntoConstraints = false

        moreButton.setTitle("More", for: .normal)
        moreButton.addTarget(self, action: #selector(buttonClicked), for: .touchUpInside)

        self.addSubview(moreButton)

    }

    @objc func buttonClicked(_ sender: UIButton) {
        if !isCardExpanded {
            moreButton.setTitle("Less", for: .normal)
            bodyLabel.numberOfLines =  0
        } else {
            moreButton.setTitle("More", for: .normal)
            bodyLabel.numberOfLines = 1
        }

        isCardExpanded = !isCardExpanded
    }

    private func setupConstraints() {
        NSLayoutConstraint.activate([
            cardView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8),
            cardView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8),
            cardView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
            cardView.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: -8),

            overlineLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
            overlineLabel.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16),

            headlineLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
            headlineLabel.topAnchor.constraint(equalTo: overlineLabel.bottomAnchor, constant: 8),


            NSLayoutConstraint(item: logoImageView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 48),
            NSLayoutConstraint(item: logoImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 48),
            logoImageView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -16),
            logoImageView.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16),


            bodyLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
            bodyLabel.topAnchor.constraint(equalTo: logoImageView.bottomAnchor, constant: 16),
            bodyLabel.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -8),

            moreButton.topAnchor.constraint(greaterThanOrEqualTo: bodyLabel.bottomAnchor, constant: 8),
            moreButton.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
            moreButton.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -8)

        ])
    }
}

基本上我想要左边的东西。但是,我得到了正确的结果,其中一个视图(蓝色)被拉伸以填充约束。

left right

我对 iOS 比较陌生,但对 android 有经验,所以任何相关的解释都会非常有帮助。

谢谢。

你不能用 lessThanOrEqualTo 做到这一点。虽然我不能完全理解你的问题,但希望这种方法有所帮助。

首先定义一个流约束,如:

var heightConstraint: NSLayoutConstraint?

并在 setupConstraint 中将其替换为底部约束:

private func setupConstraints() {

        heightConstraint = cardView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 1, constant: 0)
        NSLayoutConstraint.activate([
            cardView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 8),
            cardView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -8),
            cardView.topAnchor.constraint(equalTo: self.topAnchor, constant: 8),
            heightConstraint,

            overlineLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),
            overlineLabel.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 16),

.
.
.

最后当按下按钮时:

private func expandViewWithAnimation(_ isExpand: Bool) {
    UIView.animate(withDuration: 0.8) {
            self.heightConstraint?.constant = isExpand ? 80:0
            self.layoutIfNeeded()
        }
}

@objc func buttonClicked(_ sender: UIButton) {
        if !isCardExpanded {
            moreButton.setTitle("Less", for: .normal)
            bodyLabel.numberOfLines =  0
        } else {
            moreButton.setTitle("More", for: .normal)
            bodyLabel.numberOfLines = 1
        }

        expandViewWithAnimation(isCardExpanded)
        isCardExpanded = !isCardExpanded
}

我无法评论你的 post 因为没有足够的声誉:)

所以我找到了一个似乎可以满足我需要的解决方案。我仍在努力思考我实际想要实现的目标。基本上我想要的是我的视图高度由其子视图及其约束定义,而不是必须在约束中指定它。同时我需要在我的界面中满足高度限制。

我的解决方案如下:在我的界面中的卡片视图中添加一个 低优先级 0 高度限制。这满足了场景中的高度要求,同时还允许我的视图扩展和收缩而不被拉伸。

... a low priority constraint of height zero for the view as a whole. The low priority constraint will try to shrink the assembly, while the other constraints stop it from shrinking so far that it clips its subviews.

我在 another stack overflow question 上找到了这个解决方案。

之后,我用这个阻止了自定义视图的拉伸:

let width = widthAnchor.constraint(equalToConstant: 0)
width.priority = .defaultLow
width.isActive = true