Coded auto-layout not working properly for UITableView - ios

I am working on a feeds page using a UITableView, and I am trying to set a dynamic height fro the cells (to adapt according to the content.)
I have created a custom class for the cells, here's the code:
class cellTableViewCell: UITableViewCell, UINavigationControllerDelegate {
//OUTLETS
#IBOutlet weak var usernameLbl: UILabel!
#IBOutlet var profileImg: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
usernameLbl.text = "Username"
usernameLbl.textColor = UIColor.grayColor()
profileImg.layer.cornerRadius = profileImg.frame.size.width/2
profileImg.layer.masksToBounds = true
profileImg.addConstraint(NSLayoutConstraint(item: profileImg, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: .Height, multiplier: 1.0, constant: 100.0))
contentView.addSubview(profileImg)
contentView.addConstraint(NSLayoutConstraint(item: profileImg, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 100.0))
}
And then, in the ViewDidLoad() method of the View Controller that controls the table view, I added the following:
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 160.0
However, the cells are not adapting at all since the height is still smaller than the content.

I think your Constraint is complete correct but the problem is that you can't build a UITableView with dynamic TableViewCells with different heights for every cell. I tried that myself but in a dynamic UITableViewCell the attribute rowHeight of the tableView (tableView.rowHeight). So this rowHeight will override each different height of your cells which are defined by your Constraints.

I found an answer for the problem:
You can use the function 'heightForRowAtIndexPath' of the UITableViewDelegate like this:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return CGFloat(100)
}

Related

Add UIView to ContentView in a TableViewCell using constraints

I am trying to add cells to UITableView using Constraints. Do you know how. The following just gives:
And says: Height is ambiguous for UIView
Do you know how to add a UIView to ContextView using constraints - note the fixed height in the constraints.
import UIKit
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier:"Cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
}
let view = UIView(frame: cell!.frame)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.orange
let constraintTop = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.top,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.top,
multiplier: 1,
constant: 0)
let constraintLeading = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.leading,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.leading,
multiplier: 1,
constant: 0)
let constraintTrailing = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.trailing,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.trailing,
multiplier: 1,
constant: 0)
let constraintHeight = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.height,
relatedBy: NSLayoutRelation.equal,
toItem: nil,
attribute: NSLayoutAttribute.height,
multiplier: 1, constant: 50) // << Note fixed height
view.translatesAutoresizingMaskIntoConstraints = false
cell?.contentView.addSubview(view)
cell?.contentView.addConstraints([constraintTop, constraintLeading, constraintTrailing, constraintHeight])
return cell!
}
}
Tried to change constraints to include bottom of the contentView:
import UIKit
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier:"Cell")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
if cell == nil {
cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
}
let view = UIView(frame: cell!.frame)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.orange
let constraintTop = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.top,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.top,
multiplier: 1,
constant: 0)
let constraintLeading = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.leading,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.leading,
multiplier: 1,
constant: 0)
let constraintTrailing = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.trailing,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.trailing,
multiplier: 1,
constant: 0)
let constraintBottom = NSLayoutConstraint(item: cell!.contentView,
attribute: NSLayoutAttribute.bottom,
relatedBy: NSLayoutRelation.equal,
toItem: view,
attribute: NSLayoutAttribute.bottom,
multiplier: 1, constant: 0)
view.translatesAutoresizingMaskIntoConstraints = false
cell?.contentView.translatesAutoresizingMaskIntoConstraints = false
cell?.contentView.addSubview(view)
cell?.contentView.addConstraints([constraintTop, constraintLeading, constraintTrailing, constraintBottom])
return cell!
}
}
You're doing a couple things wrong.
First, you are setting the constraints backward. You want to constrain your new view to the contentView:
let constraintTop = NSLayoutConstraint(item: view, // constrain this view
attribute: NSLayoutAttribute.top,
relatedBy: NSLayoutRelation.equal,
toItem: cell?.contentView, // to this view
attribute: NSLayoutAttribute.top,
multiplier: 1,
constant: 0)
Second, don't do this:
cell?.contentView.translatesAutoresizingMaskIntoConstraints = false
Third, your code is (well, will be) adding a new "orange view" every time the cell is reused. Much better to add subviews in the init portion of a custom cell class, but if you're going to do it in cellForRow, check if it's already there first:
if cell.contentView.subviews.count == 0 {
// no, so add it here
let view = UIView()
// continue with view setup
Fourth, you may find it easier / more logical / cleaner / etc to add constraints this way:
cell.contentView.addSubview(view)
view.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 0.0).isActive = true
view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: 0.0).isActive = true
view.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 0.0).isActive = true
view.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: 0.0).isActive = true
And... since you have registered a cell class for reuse, this format will give you a valid cell:
// instead of this
//var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
//if cell == nil {
// cell = UITableViewCell(style: .default, reuseIdentifier: "Cell")
//}
// better method
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
So, here is the full function:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
// have we already added the subview?
if cell.contentView.subviews.count == 0 {
// no, so add it here
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.orange
cell.contentView.addSubview(view)
view.topAnchor.constraint(equalTo: cell.contentView.topAnchor, constant: 0.0).isActive = true
view.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor, constant: 0.0).isActive = true
view.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 0.0).isActive = true
view.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: 0.0).isActive = true
}
return cell
}
Your fourth constraint is applying a fixed height to the contentView. What you want instead is to pin the bottom edges of the contentView and your custom view (like you did with leading/top/trailing) and apply the constant height constraint to view, not contentView. contentView simply adapts to its subviews, you don't tell it its height directly.
Additionally, in your viewDidLoad, you'll want to set your tableView.rowHeight = UITableViewAutomaticDimension, since you're calculating the height via constraints.
Also, you will run into problems because this code is in cellForRow. This function is called every time a new cell comes onscreen, which means as you scroll, you're going to reuse the same views and have duplicate extra views added. I recommend you subclass UITableViewCell and put this code in its init.
Well, the solutions is: Read the "Output"!
Changing the translatesAutoresizingMaskIntoConstraints property of the contentView of a UITableViewCell is not supported and will result in undefined behavior, as this property is managed by the owning UITableViewCell
Having the following messed it all up:
cell?.contentView.translatesAutoresizingMaskIntoConstraints = false
Remove this line and it will work.

Setting up height of label constraint in collectionview cell programatically

I am trying to setup height constraint for a label in collectionview cell
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
override func layoutSubviews() {
super.layoutSubviews()
constraint()
}
func constraint() {
label.addConstraint(NSLayoutConstraint(item:label, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 25))
}
}
I did the above and it is not working. Does the layoutSubviews declaration work here.
There is a convenience way to use constraint and change it in code.
First, declare a constraint property:
#IBOutlet weak var labelHeight: NSLayoutConstraint!
Second, bind it in XIB or Stroyboard:
Finally, you are able to change it in programming way:
self.labelHeight.constant = 130
NSLayoutConstraint(item: label, attribute: .Height, relatedBy: .Equal, toItem: label, attribute:.Height, multiplier: 1.0, constant:25.0)
or
NSLayoutConstraint.constraintsWithVisualFormat(#"V:[label(==24)]", options: nil , metrics: nil, views: NSDictionaryOfVariableBindings(label))
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/ProgrammaticallyCreatingConstraints.html
(...)you must specify a value for each parameter, even if it doesn’t affect the layout. The end result is a considerable amount of boilerplate code, which is usually harder to read. (...)

How do I adjust a label constraint within a custom cell?

I have a table that has custom cells within it. These cells contain an image view and two labels. I have constraints in place to position everything for a typical cell.
Each cell represents either a file or a folder. The layout I have set up is for a file view (two labels are name and detail). When I create the custom cell I change the icon to be a folder and the details label becomes hidden. I then center the name label to make it prettier.
My issue occurs from the reusing of cells. I cannot seem to revert back from the centering of the name label. I have tried a couple different methods of adding this constraint and always seem to be able to have the constraint work the first time, but once a cell is reused I run into issues.
First creation of cell
Issue after cell is reused
One thing I noticed is I only have the issue when a cell is trying to remove the new center constraint (cell goes from folder cell to file cell)
Directory Cell Class
class DirectoryCell: UITableViewCell {
#IBOutlet weak var directoryTypeImage: UIImageView!
#IBOutlet weak var directoryNameLabel: UILabel!
#IBOutlet weak var directoryDetailsLabel: UILabel!
var directoryItem: DirectoryItem! {
didSet {
self.updateUI()
}
}
func updateUI() {
let centerConstraint = NSLayoutConstraint(item: directoryNameLabel, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: directoryNameLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 7.0)
directoryNameLabel.text = directoryItem.name
directoryTypeImage.image = directoryItem.typeIcon
if (directoryItem.type == DirectoryItem.types.FOLDER) {
self.removeConstraint(topConstraint)
self.addConstraint(centerConstraint)
directoryDetailsLabel.isHidden = true
} else {
self.removeConstraint(centerConstraint)
self.addConstraint(topConstraint)
directoryDetailsLabel.text = directoryItem.details
directoryDetailsLabel.isHidden = false
}
}
}
Am I simply applying/removing the constraints wrong or maybe applying/removing them in the incorrect place?
When I walk through the debugger and look at the self.constraints expression, I get no constraints. Where am I misunderstanding the constraints of my custom cell?
TL;DR
Cannot seem remove centering constraint and apply top constraint when a custom cell is reused
EDIT/SOLUTION
For any future people running into this issue, dan's answer below was exactly right. I needed to create a property for each constraint I wanted to apply. Then I remove all of the constraints and apply only the one that I want.
Added to DirectoryCell class
var topConstraint: NSLayoutConstraint {
get {
return NSLayoutConstraint(item: self.directoryNameLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.top, multiplier: 1.0, constant: 7.0)
}
}
var centerConstraint: NSLayoutConstraint {
get {
return NSLayoutConstraint(item: self.directoryNameLabel, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self.contentView, attribute: NSLayoutAttribute.centerY, multiplier: 1.0, constant: 0.0)
}
}
New updateUI()
func updateUI() {
directoryNameLabel.text = directoryItem.name
directoryTypeImage.image = directoryItem.typeIcon
if (directoryItem.type == DirectoryItem.types.FOLDER) {
self.removeConstraints(self.constraints) // Remove all constraints
self.addConstraint(centerConstraint) // Add constraint I want for this "cell type"
directoryDetailsLabel.isHidden = true
} else {
self.removeConstraints(self.constraints)
self.addConstraint(topConstraint)
directoryDetailsLabel.text = directoryItem.details
directoryDetailsLabel.isHidden = false
}
}
You aren't actually removing the constraint that you added the first time updateUI ran, you're creating a new centering constraint which is never added and removing that one. So you have both the center and top constraint on your cell when it is reused and the centering constraint apparently wins the conflict.
You need to create centerConstraint and topConstraint once and store them in properties on your cell and then just add or remove those ones in updateUI.

iOS + Swift 2: Dynamic height in UITableViewCell doesn't work with an empty view

This is my problem. I'm using a custom UITableViewCell as a Cell prototype with identifier "cellidentifier" into the UITableView. In this UITableViewCell I have added (in Interface Builder) an empty UIView into contentView's cell that I call "bubbleView", It has a connection #IBOutlet and I added constraint top = 0, bottom = 0, leading = 0 and trailing = 0.
In the controller (which inherit from UITableViewDataSource and UITableViewDelegate), into the viewDidLoad function I added this lines:
override func viewDidLoad() {
...
self.mytable.delegate = self
self.mytable.dataSource = self
self.mytable.estimatedRowHeight = 100.0
self.mytable.rowHeight = UITableViewAutomaticDimension;
...
}
Then, I implemented methods of UITableViewDelegate in this way:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cellidentifier") as! myCustomCell!
if (cell == nil) {
cell = myCustomCell(style:UITableViewCellStyle.Subtitle, reuseIdentifier:"cellidentifier")
}
let myNewView = UIView(frame: CGRectMake(0, 10, randomWidth, random Height))
cell.bubbleView.addSubview(myNewView)
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
With that code, I can add myNewView into the cell, but the cell not adjust to the real height according the myNewView height, for example if myNewView height is 134, my custom view cell always return 44 in height. I added the layoutIfNeeded property as some tutorials says but It doesn't work. Anyone have a solution of this?
Does your empty view have a height constraint? If you are just pinning the bubble view to the superview's edges, I don't think that is enough for Auto Layout to figure out how tall the cell should be.
Also, for your prototype cell, go to the Size inspector. How is your Table View Cell configured there? Having it configured as "default" vs Custom may or may not resolve the problem.
If you want the cell to truly autosize itself, you will definitely need to provide enough constraints so that a height can actually be calculated.
SOLUTION: this is a topic about constraints. If you notice in Interface Builder, you need to be very clear with constraints, so assign top, bottom, leading and trailing are ambiguos for empty view. Then, the solution is add constraints to myNewView according your needs (in my case on top, width and height) and set height constraint to view container (cell.bubbleView) in reference with myNewView:
//Create my view
let myNewView = UIView(frame: CGRectMake(0, 10, randomWidth, random Height))
myNewView.translatesAutoresizingMaskIntoConstraints = true
//Assign top constraint according with container (cell.bubbleView)
let pinTop = NSLayoutConstraint(item: myNewView, attribute: .Top, relatedBy: .Equal, toItem: cell.bubbleView, attribute: .Top, multiplier: 1.0, constant: 10)
cell.bubbleView.addConstraint(pinTop)
//set width constraint of myNewView
let pinWidth = NSLayoutConstraint(item: myNewView, attribute: .Width, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: myNewView.frame.size.width)
cell.bubbleView.addConstraint(pinWidth)
//set height constraint of myNewView
let pinHeight = NSLayoutConstraint(item: myNewView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: myNewView.frame.size.height)
cell.bubbleView.addConstraint(pinHeight)
//set height constraint of container according the myNewView plus top and bottom space
let pinHeightView = NSLayoutConstraint(item: cell.bubbleView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: myNewView.frame.size.height+20)
cell.bubbleView.addConstraint(pinHeightView)
cell.bubbleView.addSubview(myNewView)
and finally don't forget configure UITableView to make height cell dynamically.
self.mytable.estimatedRowHeight = 100.0
self.mytable.rowHeight = UITableViewAutomaticDimension;
And it works perfectly

UITableViewCell with different layouts of IBOutlets and different number of IBOutlets

I am trying to do tableview with different cell styles. My first row's image is the largest one and has three labels. I need to set such constraints only for the first cell. The second, third, fourth rows have smaller image and the same quantity of labels. Other cells have only only labels with no image. So far I have this code:
class MainPageTableViewCell: UITableViewCell {
#IBOutlet var labelDateOfPublication: UILabel!
#IBOutlet var labelTitle: UILabel!
#IBOutlet var labelIntroText: UILabel!
#IBOutlet var imageMainPage: UIImageView!
#IBOutlet var contentViewMainCell: UIView!
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: UITableViewCellStyle.Value1, reuseIdentifier: reuseIdentifier)
imageMainPage.setTranslatesAutoresizingMaskIntoConstraints(false)
contentView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.setTranslatesAutoresizingMaskIntoConstraints(false)
var constX = NSLayoutConstraint(item: imageMainPage, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0)
imageMainPage.addConstraint(constX)
var constY = NSLayoutConstraint(item: imageMainPage, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: contentView, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0)
imageMainPage.addConstraint(constY)
var constW = NSLayoutConstraint(item: imageMainPage, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 300)
imageMainPage.addConstraint(constW)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I am using this main cell here:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mainPage", forIndexPath: indexPath) as MainPageTableViewCell
println(indexPath.row)
cell.labelTitle.text = arrayMainPage[indexPath.row].pageTitle
cell.labelIntroText.text = arrayMainPage[indexPath.row].introText
cell.labelDateOfPublication.text = arrayMainPage[indexPath.row].pubDate
var link = arrayMainPage[indexPath.row].linkToImage as String
var imgCache = arrayMainPage[indexPath.row].imageDictionary
//var imgCache = imageDictionary
//println("print image aluew \(img?.)")
if let img = imgCache[link] {
let image = CommonFunctions.vignettePhoto(img, imageViewLocal:cell.imageMainPage)
// cell.image.image = image
println("it is not blank. index of array \(indexPath.row)")
}else{
CommonFunctions.loadImageAsync(link, imageView: cell.imageMainPage!, article: arrayMainPage[indexPath.row], viewContr: self)
println("it is blank index of array \(indexPath.row)")
}
return cell
}
However, the constrains that I am adding are not working. I am planning to create three UITableViewCells with different constraints. Somehow I need to programatically set cell identity to the cell that I am using. Please , somebody tell me why my constraints are not working?

Resources