Adding labels programmatically, aligned with labels from Storyboard - ios

Desired View:
In Storyboard I have two labels with the blue background that I am creating in Autolayout. Their position will never change. Next, I would like to add anywhere from 1 to 10 labels in code in cellForRowAtIndexPath below the blue background labels.
I am struggling to align the the labels added in code (brown background) with the ones created in Autolayout (blue background).
Below is my failed attempt:
Two approaches that failed:
In cellForRowAtIndexPath get the frame of "B AutoLayout Static Label" and use the X position for Dynamic Labels. Did not work.
Adding constraints also did not work -- perhaps I am not adding the constraints correctly.
Here is the code:
class TableViewController: UITableViewController {
var cellHeight = [Int: CGFloat]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 85.0
self.tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
let xLocation = cell.labelBStatic.frame.origin.x
var yLocation = cell.labelBStatic.frame.origin.y
let height = cell.labelBStatic.frame.size.height
var startYLocation = yLocation + height + 20
var i = 0
if indexPath.row % 2 == 0 {
i = 5
} else {
i = 7
}
while i < 10 {
let aLabel = UILabel()
aLabel.backgroundColor = UIColor.orangeColor()
aLabel.text = "Label # \(i)"
cell.contentView.addSubview(aLabel)
addConstraints(aLabel, verticalSpacing: startYLocation)
startYLocation += 20
i++
}
print(startYLocation)
cellHeight[indexPath.row] = startYLocation
return cell
}
func addConstraints(labelView: UILabel, verticalSpacing: CGFloat) {
// set Autoresizing Mask to false
labelView.translatesAutoresizingMaskIntoConstraints = false
//make dictionary for views
let viewsDictionary = ["view1": labelView]
//sizing constraints
let view1_constraint_H:Array = NSLayoutConstraint.constraintsWithVisualFormat("H:[view1(>=50)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
labelView.addConstraints(view1_constraint_H)
//position constraints
let view_constraint_H:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("H:|-15-[view1]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary)
let view_constraint_V:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:|-\(verticalSpacing)-[view1]", options: NSLayoutFormatOptions.AlignAllLeading, metrics: nil, views: viewsDictionary)
view.addConstraints(view_constraint_H as! [NSLayoutConstraint])
view.addConstraints(view_constraint_V as! [NSLayoutConstraint])
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if let height = cellHeight[indexPath.row] {
return height
}
return 0
}
Below is the Storyboard setup (Both labels are centered horizontally):
Question: How can I get my dynamic labels that I am creating in cellForRowAtIndexPath left align with my static labels that were created in Storyboard to match my desired view on top?

Just for demonstration purpose i have added one label programatically, you can add as much as labels you want, just add the constraints properly, also you need to add bottomSpace constraint to the last label inside cell so that your cell will auto resize as per the label height.
Follow the steps i have done to achieve what you want:
Access the B AutoLayout Static Label in cellForRowAtIndexPath: using tag or outlet if you have subclassed UITableViewCell and have created outlet.
let labelBAutolayoutStaticLabel = cell?.viewWithTag(20)
Create the label programatically as below and set translatesAutoresizingMaskIntoConstraints to false,
let labelDynamicLabel = UILabel()
labelDynamicLabel.backgroundColor = UIColor.orangeColor()
labelDynamicLabel.text = "A Dynamic Label"
labelDynamicLabel.translatesAutoresizingMaskIntoConstraints = false
cell?.contentView.addSubview(labelDynamicLabel)
You need to create two constraint, one is for TopSpace and second is LeadingSpace as below,
let leadingSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0);
let topSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 10); //Constant is the spacing between
Add constraint to your cell's contentView as below,
cell?.contentView.addConstraint(leadingSpaceConstraint)
cell?.contentView.addConstraint(topSpaceConstraint)
That's it.
And here is the result,
Here is the full code for cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("cell")
let labelBAutolayoutStaticLabel = cell?.viewWithTag(20)
let labelDynamicLabel = UILabel()
labelDynamicLabel.backgroundColor = UIColor.orangeColor()
labelDynamicLabel.text = "A Dynamic Label"
labelDynamicLabel.translatesAutoresizingMaskIntoConstraints = false
cell?.contentView.addSubview(labelDynamicLabel)
let leadingSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0);
let topSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: labelBAutolayoutStaticLabel, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 10);
cell?.contentView.addConstraint(leadingSpaceConstraint)
cell?.contentView.addConstraint(topSpaceConstraint)
return cell!
}
Edit/Update:
If you have to set bottomSpace constraint to the last label (label which is at bottom of cell), there are two way
Use NSLayoutConstraint as below:
let bottomSpaceConstraint: NSLayoutConstraint = NSLayoutConstraint(item: labelDynamicLabel, attribute: NSLayoutAttribute.BottomMargin, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1, constant: -8)
cell.contentView.addConstraint(bottomSpaceConstraint)
Using Visual Format Language as below,
let views = ["cell": cell, "labelDynamicLabel": labelDynamicLabel, "labelBAutolayoutStaticLabel": labelBAutolayoutStaticLabel]
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:[labelBAutolayoutStaticLabel]-[labelDynamicLabel]-|", options: [], metrics: nil, views: views)
cell.contentView.addConstraints(verticalConstraints)
If you set constraint using VFL make sure you remove topSpaceConstraint
//cell.contentView.addConstraint(topSpaceConstraint)
What this "V:[labelBAutolayoutStaticLabel]-[labelDynamicLabel]-|" string mean is,
labelDynamicLabel should have TopSpace to labelBAutolayoutStaticLabel with standardSpacing (8pts) and labelDynamicLabel should have bottom space to SuperView with standardSpacing. ('-|' indicates the standard space to superView)

Related

How to create "real" UITableView(Cell) margins where cells cannot be selected outside their content frame?

I would like to create a UITableView layout where the table view itself fills the complete screen (-> scrollbars are shown at screen endges) but the cells are horizontally centered with a fixed width. It should only be possible to select a cell / tap on it within this fixed width but not within the margins:
Simply giving the UITableView and fixed with + center alignment does work, but in this case the scrollbars are not at the screen edges and it is not possible to scroll using the complete screen but only within the tableView frame.
I tried different other solutions:
let cellWidth = 200
let widthDiff = (tableView.frame.width - cellWidth) / 2
// Solution 1: Change horizontal content inset
tableView.contentInset.left = widthDiff
tableView.contentInset.rigth = widthDiff
// => Cells still use the complete width but can be scrolled horizontally by widthDiff
// => Does NOT work
// Solution 2: Setting layout margins on tableView
tableView.layoutMargins.left = widthDiff
tableView.layoutMargins.rigth = widthDiff
// Solution 3: Setting layout margins on cells...
...
cell.layoutMargins.left = widthDiff
cell.layoutMargins.rigth = widthDiff
// Solution 4: Manually center the cell content using constraints within the cell layout.
// => Layout looks correct in all three cases, but while the cell content
// has the correct margins, the cells itselfs still use the complete
// screen width and is still possible to tap/select sells outside
// their frame.
// => Does NOT work
So, I was not able to find a solution which fulfills all three requirements:
Fixed width, centered cells with left and right margins
Scrollbars at screen edges / table view can be scrolled using the complete screen
Cells can only be selected / tapped on within their content frame
Is there a solution using UITableView properties and methods?
EDIT: As requested this image shows what it should look like:
Cells are centered in the middle with some margins on both sides
Scrollbars are at the screen endge
But: The cells still occupy the complete width. When tapping in the area of the margins cell is still selected and its selected-background uses the complete width. This should be avoided.
From touches point of view you seem to want that cells are selectable only at specific position but table view can be selected everywhere where table view is (for scrolling).
From views point of view you wish to limit cells to specific location but want to draw table view everywhere (scrollbars at the edge).
Then I would say that table view needs to stretch through whole screen and the cell content should be limited. This would best be done with simply constraining a custom view within your cell. The selection of cells would then need to be custom. Consider something like the following:
(I intentionally did some parts programmatically to show what is being done. But I would put most of this in storyboard).
class ViewController: UIViewController {
#IBOutlet private var tableView: UITableView?
var dataSource: [Bool] = []
var cellWidth: CGFloat = 200.0
override func viewDidLoad() {
super.viewDidLoad()
tableView?.allowsSelection = false
dataSource = .init(repeating: false, count: 100)
tableView?.reloadData()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyCell()
cell.setupIfNeeded(width: cellWidth)
cell.isCellSelected = dataSource[indexPath.row]
cell.onMiddleViewPressed = { [weak self, weak cell] in
self?.dataSource[indexPath.row] = true
cell?.isCellSelected = true
}
return cell
}
}
class MyCell: UITableViewCell {
private var isSetup: Bool = false
private var middleView: UIView?
var onMiddleViewPressed: (() -> Void)?
var isCellSelected: Bool = false {
didSet {
middleView?.backgroundColor = isCellSelected ? .blue : .gray
}
}
func setupIfNeeded(width: CGFloat) {
guard isSetup == false else { return }
isSetup = true
let middleView = UIView(frame: .zero)
middleView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(middleView)
contentView.addConstraint(.init(item: middleView, attribute: .centerX, relatedBy: .equal, toItem: contentView, attribute: .centerX, multiplier: 1.0, constant: 0.0))
middleView.addConstraint(.init(item: middleView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: width))
contentView.addConstraint(.init(item: middleView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1.0, constant: 0.0))
contentView.addConstraint(.init(item: middleView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1.0, constant: 0.0))
middleView.backgroundColor = isCellSelected ? .blue : .gray
middleView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onMiddleViewSelected)))
self.middleView = middleView
}
#objc private func onMiddleViewSelected() {
onMiddleViewPressed?()
}
}
To create a toggle in selection you would then simply do:
cell.onMiddleViewPressed = { [weak self, weak cell] in
guard let self = self else { return }
self.dataSource[indexPath.row].toggle()
cell?.isCellSelected = self.dataSource[indexPath.row]
}
To use a single selection you would do
cell.onMiddleViewPressed = { [weak self, weak cell, weak tableView] in
guard let self = self else { return }
if let currentSelectionIndex = self.dataSource.firstIndex(of: true), currentSelectionIndex != indexPath.row {
self.dataSource[currentSelectionIndex] = false
let targetIndexPath = IndexPath(row: currentSelectionIndex, section: 0)
if tableView?.indexPathsForVisibleRows?.contains(targetIndexPath) == true {
tableView?.reloadRows(at: [targetIndexPath], with: .none)
}
}
self.dataSource[indexPath.row].toggle()
cell?.isCellSelected = self.dataSource[indexPath.row]
}
so this is nothing too heavy. And a pretty standard procedures in cases like having UISwitch (or similar components) for selection where selecting the cell does something completely different (navigate to details for instance).
Perhaps you also need transparent cells and table view. This is just:
tableView?.backgroundColor = .clear
cell.backgroundColor = .clear
cell.contentView.backgroundColor = .clear

Why is layout of collectionView cell diverse when it is supposed to be the same?

When i was creating my collection view, i defined cell to be the same with the same layout
class FriendsController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Recent"
collectionView?.backgroundColor = UIColor.white
collectionView?.register(FriendCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.alwaysBounceVertical = true
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 100)
}
}
class FriendCell: BaseCell {
override func setupViews() {
addSubview(profileImageView)
addSubview(dividerLineView)
profileImageView.image = UIImage(named: "zuckprofile")
setupContainerView()
addConstraintsWithFormat( "H:|-12-[v0(68)]|", views: profileImageView)
addConstraintsWithFormat( "V:[v0(68)]", views: profileImageView)
addConstraint(NSLayoutConstraint(item: profileImageView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
addConstraintsWithFormat( "H:|-82-[v0]|", views: dividerLineView)
addConstraintsWithFormat( "V:[v0(1)]|", views: dividerLineView)
}
func setupContainerView() {
let containerView = UIView()
addSubview(containerView)
addConstraintsWithFormat("H:|-90-[v0]|", views: containerView)
addConstraintsWithFormat( "V:[v0(50)]", views: containerView)
addConstraint(NSLayoutConstraint(item: containerView, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0))
containerView.addSubview(nameLabel)
containerView.addSubview(messageLabel)
containerView.addSubview(timelabel)
containerView.addConstraintsWithFormat( "H:|[v0][v1]|", views: nameLabel, timelabel )
containerView.addConstraintsWithFormat( "V:|[v0][v1(24)]|", views: nameLabel, messageLabel)
containerView.addConstraintsWithFormat( "H:|[v0]-12-|", views: messageLabel )
containerView.addConstraintsWithFormat("V:|[v0(20)]|", views: timelabel)
}
}
extension UIView {
func addConstraintsWithFormat(_ format: String , views: UIView...) {
var viewsDictionary = [String: UIView]()
for (index, view) in views.enumerated() {
let key = "v\(index)"
viewsDictionary[key] = view
view.translatesAutoresizingMaskIntoConstraints = false
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))
}
}
So as it seems from the code everything in cells shout be the same. With the same layout, constraints etc
Then i got this terrible result.
Please maybe someone knows why this behavior take place here?
I believe the problem lies in ambiguity between nameLabel and timeLabel intrinsic size and their content hugging priority and content compression resistance priority (check this article).
You have two options, either set following horizontal compression resistance and hugging priority for both labels in setupContainerView:
nameLabel.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .horizontal)
nameLabel.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: .horizontal)
timeLabel.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: .horizontal)
timeLabel.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: .horizontal)
Which will make the timeLabel to keep the size according to its content and the nameLabel will take the rest of the content (meaning that if the time is not set, the timeLabel will be shrinked to width 0, and the rest will be taken up by nameLabel).
Or set an explicit width for one of those labels, e.g.:
timeLabel.widthAnchor.constraint(equalToConstant: 40).isActive = true
EDIT
According to Apple docs,
When an ambiguous layout occurs at runtime, Auto Layout chooses one of the possible solutions to use. This means the layout may or may not appear as you expect. Furthermore, there are no warnings written to the console, and there is no way to set a breakpoint for ambiguous layouts.
Or other doc:
When stretching a series of views to fill a space, if all the views have an identical content-hugging priority, the layout is ambiguous. Auto Layout doesn’t know which view should be stretched.
None of the docs I was able to find tells us, how does the Auto Layout solve this issues - and I believe the reason is to make sure we won't be relying on this and rather explicitly set the priorities. Therefore, the label that gets stretched (even the size to which it is stretched) can be really chosen by any random algorithm. Based from your result we can at best conclude, that probably it is not based on the order in which labels were added as subviews.

How do I add constraints to a subview loaded from a nib file?

I'm trying to load a sub view on to every single page of my app from a nib file. Right now I'm using a somewhat unusual approach to loading this sub view in that I am doing it through an extension of UIStoryboard (probably not relevant to my problem, but I'm not sure). So this is how the code looks when I load the nib file:
extension UIStoryboard {
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
viewController.view.addSubview(myCustomSubview)
}
}
}
This code does what it's supposed to do and adds "MyCustomSubview" to the view controller (I won't go in to detail on exactly how this method gets called because it works so it doesn't seem important). The problem is I can't for the life of me figure out how to add constraints that effect the size of myCustomSubview. I have tried putting code in the function I showed above as well as in the MyCustomSubview swift file to add constraints but no matter what I do the subview never changes.
Ideally the constraints would pin "MyCustomSubview" to the bottom of the ViewController, with width set to the size of the screen and a hard coded height constraint.
Here are the two main methods I tried (with about 100 minor variations for each) that did NOT work:
Method 1 - Add constraint directly from "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let top = NSLayoutConstraint(item: myCustomSubview, attribute: .top, relatedBy: .equal
, toItem: viewController.view, attribute: .top, multiplier: 1, constant: 50.0)
viewController.view.addSubview(myCustomSubview)
viewController.view.addConstraint(top)
}
}
Method 2 - Add constraint outlets and setter method in MyCustomSubview
class MyCustomSubview: UIView {
#IBOutlet weak var widthConstraint: NSLayoutConstraint!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
func setConstraints(){
self.widthConstraint.constant = UIScreen.main.bounds.size.width
self.heightConstraint.constant = 20
}
}
And call setter method in "appendCustomView"
public func appendCustomView(to viewController: UIViewController) {
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
myCustomSubview.setConstraints()
viewController.view.addSubview(myCustomSubview)
}
}
(*note: the actual constraints of these examples are irrelevant and I wasn't trying to meet the specs I mentioned above, I was just trying to make any sort of change to the view to know that the constraints were updating. They weren't.)
Edit : Changed "MyCustomNib" to "MyCustomSubview" for clarity.
When you add constraints onto a view from a Nib, you have to call yourView.translatesAutoresizingMaskIntoConstraints = false, and you also need to make sure that you have all 4 (unless it's a label or a few other view types which only need 2) constraints in place:
Here's some sample code that makes a view fill it's parent view:
parentView.addSubview(yourView)
yourView.translatesAutoresizingMaskIntoConstraints = false
yourView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor).isActive = true
Edit: I've actually come around to perferring this method of adding NSLayoutConstraints, even though the results are the same
NSLayoutConstraint.activate([
yourView.topAnchor.constraint(equalTo: parentView.topAnchor),
yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
])
For anyone who comes across this in the future, this is the solution I came up with by tweaking this answer a little bit
Add a setConstraints(withRelationshipTo) method in the swift class that corresponds to the nib file:
class MyCustomSubview: UIView {
func setConstraints(withRelationshipTo view: UIView){
self.translatesAutoresizingMaskIntoConstraints = false
// Replace with your own custom constraints
self.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
Then call the setConstraints method after you add the nib file to the view (probably in viewWillAppear or viewDidLoad of a view controller )
class MyViewController: UIViewController {
override func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
let view = self.view // Added for clarity
view.addSubview(myCustomSubview)
myCustomSubview.setConstraints(withRelationshipTo: view)
}
}
}
You can use this extension for anywhere you're going to add a subview to a existing UIView.
extension UIView {
func setConstraintsFor(contentView: UIView, left: Bool = true, top: Bool = true, right: Bool = true, bottom: Bool = true) {
contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(contentView)
var constraints : [NSLayoutConstraint] = []
if left {
let constraintLeft = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
constraints.append(constraintLeft)
}
if top {
let constraintTop = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
constraints.append(constraintTop)
}
if right {
let constraintRight = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
constraints.append(constraintRight)
}
if bottom {
let constraintBottom = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
constraints.append(constraintBottom)
}
self.addConstraints(constraints)
}
}
You can call this method like this:
containerView.setConstraintsFor(contentView: subView!, top: false)
This will add subView to the containerView and constraint to all sides except top side.
You can modify this method to pass left, top, right, bottom Constant value if you want.

How do I create UITableView header whose height is determined by the height of its label?

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)
UILabels 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.
Implementation using the storyboard
In UItableView add on UITableViewCell new UIView and put him UILabel
Connects them via Autolayout
In UILabel put the number of lines to 0.
In 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
test project link
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:
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.
//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
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

UITableView with variable cell height: Working in IB but not programmatically

TL;DR
My programmatically created table view cells are not resizing according to the intrinsic content height of their custom views, even though I am using UITableViewAutomaticDimension and setting both the top and bottom constraints.
The problem probably lies in my implementation of the UITableViewCell subclass. See the code below under Doesn't work programmatically > Code > MyCustomCell.swift.
Goal
I'm trying to make a suggestion bar for a custom Mongolian keyboard. Mongolian is written vertically. In Android it looks like this:
Progress
I've learned that I should use a UITableView with variable cell heights, which is available starting with iOS 8. This requires using auto layout and telling the table view to use automatic dimensions for the cell heights.
Some things I've had to learn along the way are represented in my recent SO questions and answers:
How to make a custom table view cell
Getting variable height to work with in a table view with a standard UILabel
Getting intrinsic content size to work for a custom view
Using a programmatically created UITableViewCell
Set constraints programmatically
So I have come to the point where I have the vertical labels that support intrinsic content size. These labels go in my custom table view cells. And as described in the next section, they work when I do it in the storyboard, but not when I create everything programmatically.
Works in IB
In order to isolate the problem I created two basic projects: one for where I use the storyboard and one where I do everything programmatically. The storyboard project works. As can be seen in the following image, each table view cell resizes to match the height of custom vertical label.
In IB
I set constraints to pin the top and bottom as well as centering the label.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"]
let cellReuseIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.myStrings.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.myStrings[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
MyCustomCell.swift
import UIKit
class MyCustomCell: UITableViewCell {
#IBOutlet weak var myCellLabel: UIMongolSingleLineLabel!
}
Doesn't work programmatically
Since I want the suggestion bar to be a part of the final keyboard, I need to be able to create it programmatically. However, when I try to recreate the above example project programmatically, it isn't working. I get the following result.
The cell heights are not resizing and the custom vertical labels are overlapping each other.
I also get the following error:
Warning once only: Detected a case where constraints ambiguously
suggest a height of zero for a tableview cell's content view. We're
considering the collapse unintentional and using standard height
instead.
This error has been brought up before multiple times on Stack Overflow:
iOS8 - constraints ambiguously suggest a height of zero
Detected a case where constraints ambiguously suggest a height of zero
custom UITableviewcell height not set correctly
ios 8 (UITableViewCell) : Constraints ambiguously suggest a height of zero for a tableview cell's content view
However, the problem for most of those people is that they were not setting both a top and bottom pin constraint. I am, or at least I think I am, as is shown in my code below.
Code
ViewController.swift
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let myStrings: [String] = ["a", "bbbbbbb", "cccc", "dddddddddd", "ee"]
let cellReuseIdentifier = "cell"
var tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
// Suggestion bar
tableView.frame = CGRect(x: 0, y: 20, width: view.bounds.width, height: view.bounds.height)
tableView.registerClass(MyCustomCell.self, forCellReuseIdentifier: cellReuseIdentifier)
tableView.delegate = self
tableView.dataSource = self
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
view.addSubview(tableView)
}
// number of rows in table view
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.myStrings.count
}
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.myStrings[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
MyCustomCell.swift
I think the problem is probably in here since this is the main difference from the IB project.
import UIKit
class MyCustomCell: UITableViewCell {
var myCellLabel = UIMongolSingleLineLabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
self.myCellLabel.translatesAutoresizingMaskIntoConstraints = false
self.myCellLabel.centerText = false
self.myCellLabel.backgroundColor = UIColor.yellowColor()
self.addSubview(myCellLabel)
// Constraints
// pin top
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true
// pin bottom
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true
// center horizontal
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
}
override internal class func requiresConstraintBasedLayout() -> Bool {
return true
}
}
Supplemental Code
I'll also include the code for the custom vertical label that I used in both projects above, but since the IB project works, I don't think the main problem is here.
import UIKit
#IBDesignable
class UIMongolSingleLineLabel: UIView {
private let textLayer = LabelTextLayer()
var useMirroredFont = false
// MARK: Primary input value
#IBInspectable var text: String = "A" {
didSet {
textLayer.displayString = text
updateTextLayerFrame()
}
}
#IBInspectable var fontSize: CGFloat = 17 {
didSet {
updateTextLayerFrame()
}
}
#IBInspectable var centerText: Bool = true {
didSet {
updateTextLayerFrame()
}
}
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
// Text layer
textLayer.backgroundColor = UIColor.yellowColor().CGColor
textLayer.useMirroredFont = useMirroredFont
textLayer.contentsScale = UIScreen.mainScreen().scale
layer.addSublayer(textLayer)
}
override func intrinsicContentSize() -> CGSize {
return textLayer.frame.size
}
func updateTextLayerFrame() {
let myAttribute = [ NSFontAttributeName: UIFont.systemFontOfSize(fontSize) ]
let attrString = NSMutableAttributedString(string: textLayer.displayString, attributes: myAttribute )
let size = dimensionsForAttributedString(attrString)
// This is the frame for the soon-to-be rotated layer
var x: CGFloat = 0
var y: CGFloat = 0
if layer.bounds.width > size.height {
x = (layer.bounds.width - size.height) / 2
}
if centerText {
y = (layer.bounds.height - size.width) / 2
}
textLayer.frame = CGRect(x: x, y: y, width: size.height, height: size.width)
textLayer.string = attrString
invalidateIntrinsicContentSize()
}
func dimensionsForAttributedString(attrString: NSAttributedString) -> CGSize {
var ascent: CGFloat = 0
var descent: CGFloat = 0
var width: CGFloat = 0
let line: CTLineRef = CTLineCreateWithAttributedString(attrString)
width = CGFloat(CTLineGetTypographicBounds(line, &ascent, &descent, nil))
// make width an even integer for better graphics rendering
width = ceil(width)
if Int(width)%2 == 1 {
width += 1.0
}
return CGSize(width: width, height: ceil(ascent+descent))
}
}
// MARK: - Key Text Layer Class
class LabelTextLayer: CATextLayer {
// set this to false if not using a mirrored font
var useMirroredFont = true
var displayString = ""
override func drawInContext(ctx: CGContext) {
// A frame is passed in, in which the frame size is already rotated at the center but the content is not.
CGContextSaveGState(ctx)
if useMirroredFont {
CGContextRotateCTM(ctx, CGFloat(M_PI_2))
CGContextScaleCTM(ctx, 1.0, -1.0)
} else {
CGContextRotateCTM(ctx, CGFloat(M_PI_2))
CGContextTranslateCTM(ctx, 0, -self.bounds.width)
}
super.drawInContext(ctx)
CGContextRestoreGState(ctx)
}
}
Update
The entire code for the project is all here, so if anyone is interested enough to try it out, just make a new project and cut and paste the code above into the following three files:
ViewController.swift
MyCustomCell.swift
UIMongolSingleLineLabel.swift
The error is pretty trivial:
Instead of
self.addSubview(myCellLabel)
use
self.contentView.addSubview(myCellLabel)
Also, I would replace
// pin top
NSLayoutConstraint(...).active = true
// pin bottom
NSLayoutConstraint(...).active = true
// center horizontal
NSLayoutConstraint(...).active = true
with
let topConstraint = NSLayoutConstraint(...)
let bottomConstraint = NSLayoutConstraint(...)
let centerConstraint = NSLayoutConstraint(...)
self.contentView.addConstraints([topConstraint, bottomConstraint, centerConstraint])
which is more explicit (you have to specify the constraint owner) and thus safer.
The problem is that when calling active = true on a constraint, the layout system has to decide to which view it should add the constraints. In your case, because the first common ancestor of contentView and myCellLabel is your UITableViewCell, they were added to your UITableViewCell, so they were not actually constraining the contentView (constraints were between siblings not between superview-subview).
Your code actually triggered a console warning:
Warning once only: Detected a case where constraints ambiguously suggest a height of zero for a tableview cell's content view. We're considering the collapse unintentional and using standard height instead.
Which made me to look immediately at the way the constraints are created for your label.
I have tested your code and found the issue was in setting constraints please use below code part for setting constants in your "MyCustomCell.swift" file setup() function
let topConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0)
let centerConstraint = NSLayoutConstraint(item: myCellLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self, attribute: .CenterX, multiplier: 1, constant: 0)
self.addConstraints([centerConstraint, topConstraint, bottomConstraint])
Also set clips to bound property to your cell lable in "viewcontroller.swift"
// create a cell for each table view row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.myStrings[indexPath.row]
cell.myCellLabel.clipsToBounds=true
return cell
}
For your ease I have uploaded my sample code on GitHub Dynamic Height Sample
Output is looking like this now
The problem seems to come from the vertical constraints in the cell
By putting them relative to self instead of self.contentView in MyCustomCell you can fix your problem
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true
// pin bottom
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true
// center horizontal
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
the full class would be:
import UIKit
class MyCustomCell: UITableViewCell {
var myCellLabel = UIMongolSingleLineLabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setup() {
self.myCellLabel.translatesAutoresizingMaskIntoConstraints = false
self.myCellLabel.centerText = false
self.myCellLabel.backgroundColor = UIColor.yellowColor()
self.addSubview(myCellLabel)
// Constraints
// pin top
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0).active = true
// pin bottom
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0).active = true
// center horizontal
NSLayoutConstraint(item: myCellLabel, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0).active = true
}
override internal class func requiresConstraintBasedLayout() -> Bool {
return true
}
}
The thing you are missing is this function:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return heightValue
}
Im not so sure what you should do exactly, but by the fact that you know your labels you should be able to return an exact height value for each cell in this method
I think you are missing to set constraints for tableView with superview. And try to increase estimated row height also.

Resources