I've created a custom view in swift & am trying to get it to display appropriately. It is essentially a material card, with the ability to expand the view by pressing the more button. My issue comes in when specifying the bottom constraint. It is required, but setting it stretches my custom view.
I have implemented this already in android & I guess I'm trying to find the analog to android:height='wrap_content'
. I've tried setting the aspect ratio constraint, which works to keep my view at the proper size, but prevents the custom view from expanding when its subviews change. Additionally I've tried using the lessThanOrEqualTo constraint, but that is too ambiguous to satisfy the bottom constraint.
This is what my UIExpandableCard view looks like:
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)
])
}
}
Basically I want something like the left. However, I'm getting the right, where one of the views (in blue) is being stretched to fill the constraint.
I'm relatively new to iOS, but have experience with android, so any explanations relating to that would be extra helpful.
Thanks.