extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.width)
}
}
class CommentCell: UICollectionViewCell {
let profileImageView = UIImageView()
let nameLabel = UILabel()
let titleLabel = UILabel()
let commentLabel = UILabel()
var titleFont: UIFont = .systemFont(ofSize: 16.0)
var commentFont: UIFont = .systemFont(ofSize: 16.0)
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
commentLabel.numberOfLines = 6
[titleLabel, commentLabel].forEach { v in
v.setContentHuggingPriority(.required, for: .vertical)
v.backgroundColor = .yellow
}
[profileImageView, nameLabel, titleLabel, commentLabel].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(v)
}
profileImageView.backgroundColor = .red
nameLabel.text = "Name Label"
titleLabel.text = "Title Label"
let g = contentView
NSLayoutConstraint.activate([
profileImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
profileImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
profileImageView.widthAnchor.constraint(equalToConstant: 80.0),
profileImageView.heightAnchor.constraint(equalTo: profileImageView.widthAnchor),
nameLabel.leadingAnchor.constraint(equalTo: profileImageView.trailingAnchor, constant: 8.0),
nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
nameLabel.centerYAnchor.constraint(equalTo: profileImageView.centerYAnchor),
titleLabel.topAnchor.constraint(equalTo: profileImageView.bottomAnchor, constant: 8.0),
titleLabel.leadingAnchor.constraint(equalTo: profileImageView.leadingAnchor, constant: 0.0),
titleLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
commentLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8.0),
commentLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor, constant: 0.0),
commentLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
commentLabel.bottomAnchor.constraint(lessThanOrEqualTo: g.bottomAnchor, constant: -8.0),
])
}
}
class SampleViewController: UIViewController {
var collectionView: UICollectionView!
var collectionViewHeightConstraint: NSLayoutConstraint!
var flowLayout: UICollectionViewFlowLayout!
var comments: [String] = []
let shortComments: [String] = [
"First example.",
"This is a short set... it should have a max of two lines.",
"String with some text.",
"This is a longer string for the two-line label.",
"This potential solution regarding two-line-labels works better in this case.",
"Last short string.",
]
let longComments: [String] = [
"This is a set of longer strings.",
"Depending on the available view-width, it may not work well if the text is too long.",
"Our final example string will be much longer than the others. This will demonstrate that, unless we also set a max-width, the calculated width will end up extending the label outside the bounds of our view (assuming we're on an iPhone in Portrait orientation).",
"A label can contain an arbitrary amount of text, but UILabel may shrink, wrap, or truncate the text, depending on the size of the bounding rectangle and properties you set. You can control the font, text color, alignment, highlighting, and shadowing of the text in the label.",
"You can set the title, image, and other appearance properties of a button. In addition, you can specify a different appearance for each button state.",
"Displays a rounded rectangle that can contain editable text. When a user taps a text field, a keyboard appears; when a user taps Return in the keyboard, the keyboard disappears and the text field can handle the input in an application-specific way. UITextField supports overlay views to display additional information, such as a bookmarks icon. UITextField also provides a clear text control a user taps to erase the contents of the text field.",
]
var commentToggle: Int = 0
let titleFont: UIFont = .systemFont(ofSize: 18.0)
let commentFont: UIFont = .systemFont(ofSize: 12.0)
override func viewDidLoad() {
super.viewDidLoad()
let toggleButton = UIButton()
toggleButton.setTitle("Toggle Data", for: [])
toggleButton.setTitleColor(.white, for: .normal)
toggleButton.setTitleColor(.gray, for: .highlighted)
toggleButton.backgroundColor = .systemBlue
toggleButton.addTarget(self, action: #selector(toggleTapped(_:)), for: .touchUpInside)
flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
toggleButton.translatesAutoresizingMaskIntoConstraints = false
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(toggleButton)
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
collectionViewHeightConstraint = collectionView.heightAnchor.constraint(equalToConstant: flowLayout.itemSize.height)
NSLayoutConstraint.activate([
toggleButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
toggleButton.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 60.0),
toggleButton.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -60.0),
collectionView.topAnchor.constraint(equalTo: toggleButton.bottomAnchor, constant: 40.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
collectionViewHeightConstraint,
])
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "commentCell")
updateViews()
}
func updateViews() -> Void {
// assuming we want the cell width to be 280-pts,
// with 8-pts "padding" on each side
// the cell's title label and comment label Widths are: 264-pts
let w: CGFloat = 264.0
// Max 6 lines? Then get height of 6 lines
let sixLines: String = "1\n2\n3\n4\n5\n6"
let sixLineHeight: CGFloat = sixLines.height(withConstrainedWidth: w, font: commentFont)
// start with short comments
comments = commentToggle % 2 == 0 ? shortComments : longComments
var maxCommentHeight: CGFloat = 0
for i in 0..<comments.count {
let h: CGFloat = comments[i].height(withConstrainedWidth: w, font: commentFont)
maxCommentHeight = max(h, maxCommentHeight)
if maxCommentHeight >= sixLineHeight {
maxCommentHeight = sixLineHeight
break
}
}
// get height of title label
let th: CGFloat = "A".height(withConstrainedWidth: w, font: titleFont)
// cell height will be
// profileImageView height (80)
// +
// title label height
// +
// maxCommentHeight
// +
// vertical spacing (4 spaces * 8)
let cellHeight: CGFloat = 80.0 + th + maxCommentHeight + 32.0
flowLayout.itemSize = CGSize(width: w + 16.0, height: cellHeight)
collectionViewHeightConstraint.constant = flowLayout.itemSize.height
collectionView.reloadData()
}
@objc func toggleTapped(_ sender: Any?) -> Void {
commentToggle += 1
updateViews()
}
}
extension SampleViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return comments.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "commentCell", for: indexPath) as! CommentCell
c.titleLabel.font = titleFont
c.commentLabel.font = commentFont
c.commentLabel.text = comments[indexPath.item]
return c
}
}
// -------------------
// original paste follows
// -------------------
// assuming comment label Width is 260-pts
let w: CGFloat = 260.0
// assuming font of comment label
let font: UIFont = .systemFont(ofSize: 12.0)
// Max 6 lines? Then get height of 6 lines
let sixLines: String = "1\n2\n3\n4\n5\n6"
let sixLineHeight: CGFloat = sixLines.height(withConstrainedWidth: w, font: font)
var maxCommentHeight: CGFloat = 0
for i in 0..<comments.count {
let h: CGFloat = comments[i].height(withConstrainedWidth: w, font: font)
print("h:", h)
maxCommentHeight = max(h, maxCommentHeight)
if maxCommentHeight > sixLineHeight {
maxCommentHeight = sixLineHeight
break
}
}
print("Max comment height:", maxCommentHeight)
// set itemSize of collection view flow layout to
// CGSize(width: w, height: maxCommentHeight)
Comments
0 B
|👍
/👎