Reputation: 272094
I would like to add a header to my tableView. This header contains 1 UILabel. The header height should be calculated based on the number of lines the label has.
In my code, I'm adding constraints with all the edges of the label <> header. This is my attempt:
//Add header to tableView
header = UIView()
header.backgroundColor = UIColor.yellowColor()
tableView!.tableHeaderView = header
//Create Label and add it to the header
postBody = UILabel()
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont(name: "Lato-Regular", size: 16.0)
postBody.numberOfLines = 0
postBody.backgroundColor = FlatLime()
header.addSubview(postBody)
//Enable constraints for each item
postBody.translatesAutoresizingMaskIntoConstraints = false
header.translatesAutoresizingMaskIntoConstraints = false
//Add constraints to the header and post body
let postBodyLeadingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
postBodyLeadingConstraint.active = true
let postBodyTrailingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
postBodyTrailingConstraint.active = true
let postBodyTopConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
postBodyTopConstraint.active = true
let postBodyBottomConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
postBodyBottomConstraint.active = true
//Calculate header size
let size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
var frame = header.frame
frame.size.height = size.height
header.frame = frame
tableView!.tableHeaderView = header
header.layoutIfNeeded()
This is my table:
let nib = UINib(nibName: "MessagesTableViewCell", bundle: nil)
let nibSimple = UINib(nibName: "SimpleMessagesTableViewCell", bundle: nil)
self.tableView!.registerNib(nib, forCellReuseIdentifier: "MessagesTableViewCell")
self.tableView!.registerNib(nibSimple, forCellReuseIdentifier: "SimpleMessagesTableViewCell")
self.tableView!.dataSource = self
self.tableView!.delegate = self
self.tableView!.rowHeight = UITableViewAutomaticDimension
self.tableView!.estimatedRowHeight = 100.0
self.tableView!.separatorStyle = UITableViewCellSeparatorStyle.None
self.tableView!.separatorColor = UIColor(hex: 0xf5f5f5)
self.tableView!.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0)
self.tableView!.clipsToBounds = true
self.tableView!.allowsSelection = false
self.tableView!.allowsMultipleSelection = false
self.tableView!.keyboardDismissMode = .OnDrag
As you can see, the header does not take into account the height of the label (which I did numberOfLines = 0)
Upvotes: 13
Views: 1596
Reputation: 1206
you can calculate the height of a label by using its string
let labelWidth = label.frame.width
let maxLabelSize = CGSize(width: labelWidth, height: CGFloat.max)
let actualLabelSize = label.text!.boundingRectWithSize(maxLabelSize, options: [.UsesLineFragmentOrigin], attributes: [NSFontAttributeName: label.font], context: nil)
let labelHeight = actualLabelSize.height
Upvotes: -2
Reputation: 1067
//may be it will help for you.
header = UIView(frame: CGRectMake(tableview.frame.origin.x,tableview.frame.origin.y, tableview.frame.size.width, 40))
header.backgroundColor = UIColor.yellowColor()
//Create Label and add it to the header
postBody = UILabel(frame: header.frame)
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont(name: "Lato-Regular", size: 16.0)
postBody.numberOfLines = 0
postBody.backgroundColor = FlatLime()
header.addSubview(postBody)
let maximumLabelSize: CGSize = CGSizeMake(postBody.size.width, CGFloat.max);
let options: NSStringDrawingOptions = NSStringDrawingOptions.UsesLineFragmentOrigin
let context: NSStringDrawingContext = NSStringDrawingContext()
context.minimumScaleFactor = 0.8
let attr: Dictionary = [NSFontAttributeName: postBody.font!]
var size: CGSize? = postBody.text?.boundingRectWithSize(maximumLabelSize, options:options, attributes: attr, context: context).size
let frame = header.frame
frame.size.height = size?.height
header.frame = frame
postBody.frame = frame
tableView!.tableHeaderView = header
Upvotes: -1
Reputation: 476
We use NSLayoutManager to quickly estimate the height for items that need to resize based on the text. This is the basic idea:
override class func estimatedHeightForItem(text: String, atWidth width: CGFloat) -> CGFloat {
let storage = NSTextStorage(string: text!)
let container = NSTextContainer(size: CGSize(width: width, height: CGFloat.max))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(container)
storage.addLayoutManager(layoutManager)
storage.addAttribute(NSFontAttributeName, value: UIFont.Body, range: NSRange(location: 0, length: storage.length))
container.lineFragmentPadding = 0.0
return layoutManager.usedRectForTextContainer(container).size.height
}
Beslan's answer is probably a better fit for your use case, but I find it nice to have more control how the layout is handled.
Upvotes: 2
Reputation: 3131
Implementation using the storyboard
UItableView
add on UITableViewCell
new UIView
and put him UILabel
Connects them via AutolayoutUILabel
put the number of lines to 0.ViewDidLoad
your UILabel
call a method sizeToFit()
and specify a size for UIView
, and that will be your HeaderVew headerView.frame.size.height = headerLabel.frame.size.height
Code
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var headerView: UIView!
@IBOutlet weak var headerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
headerLabel.text = "tableViewdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarning"
headerLabel.sizeToFit()
headerView.frame.size.height = headerLabel.frame.size.height
}
ScreenShot
TestProject
Upvotes: 10
Reputation: 130132
The first problem we have is that the header cannot be resized by autolayout, for details, see Is it possible to use AutoLayout with UITableView's tableHeaderView?
Therefore, we have to calculate the height of the header manually, for example:
@IBOutlet var table: UITableView!
var header: UIView?
var postBody: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
let header = UIView()
// don't forget to set this
header.translatesAutoresizingMaskIntoConstraints = true
header.backgroundColor = UIColor.yellowColor()
let postBody = UILabel()
postBody.translatesAutoresizingMaskIntoConstraints = false
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont.systemFontOfSize(16.0)
// don't forget to set this
postBody.lineBreakMode = .ByWordWrapping
postBody.numberOfLines = 0
header.addSubview(postBody)
let leadingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
header.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
self.table.tableHeaderView = header
self.header = header
self.postBody = postBody
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let text = postBody!.attributedText!
let height = text.boundingRectWithSize(
CGSizeMake(table.bounds.size.width, CGFloat.max),
options: [.UsesLineFragmentOrigin],
context: nil
).height
header!.frame.size.height = height
}
You might also want to use the code in stefandouganhyde's answer. It does not really matter how you calculate the height. The point is that autolayout won't work automatically for tableHeaderView
.
Result:
Upvotes: 9
Reputation: 4554
UILabel
s take advantage of UIView
's intrinsicContentSize()
to tell auto layout what size they should be. For a multiline label, however, the intrinsic content size is ambiguous; the table doesn't know if it should be short and wide, tall and narrow, or anything in between.
To combat this, UILabel
has a property called preferredMaxLayoutWidth
. Setting this tells a multiline label that it should be at most this wide, and allows intrinsicContentSize()
to figure out and return an appropriate height to match. By not setting the preferredMaxLayoutWidth
in your example, the label leaves its width unbounded and therefore calculates the height for a long, single line of text.
The only complication with preferredMaxLayoutWidth
is that you typically don't know what width you want the label to be until auto layout has calculated one for you. For that reason, the place to set it in a view controller subclass (which it looks like your code sample might be from) is in viewDidLayoutSubviews
:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
postBody.preferredMaxLayoutWidth = CGRectGetWidth(postBody.frame)
// then update the table header view
if let header = tableView?.tableHeaderView {
header.frame.size.height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
tableView?.tableHeaderView = header
}
}
Obviously, you'll need to add a property for the postBody
label for this to work.
Let me know if you're not in a UIViewController
subclass here and I'll edit my answer.
Upvotes: 20