create UITableViewCell constraint programmatically

11,465

Solution 1

A user on Apple's Developer Forums pointed me in the right direction. The constraint was between the label and the cell (the cell being referred to as 'self' in the class for the custom cell) and the answer here was that I had to addConstraint to the cell instead of adding it to the label. Same exact constraint definition, but it only worked as self.addConstraint() and gave the error "The view hierarchy is not prepared for the constraint" when coded as labelName.addConstraint()

Solution 2

you should be adding your constraints in init for the cell, assuming it will be a dequeued reusable cell, remember to use contentView instead of View for the bounds of the cell:

class CustomCell2: UITableViewCell {

//MARK: Subviews
//Add View Programmatically or in xib
let titleLabel: UILabel = {
    let label = UILabel()
    label.text = " Title "
    //…
    label.translatesAutoresizingMaskIntoConstraints = false //Must use
    return label
}()

//…


//MARK: init
//Add Subviews and then layout Contraints to the Cell’s contentView
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    addSubViewsAndlayout()
}



/// Add and sets up subviews with programmically added constraints
func addSubViewsAndlayout() {
    contentView.addSubview(titleLabel) //will crash if not added

    let screenwidth = UIScreen.main.bounds.width //get any other properties you need

    titleLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 12.0).isActive = true
    titleLabel.heightAnchor.constraint(equalToConstant: 30).isActive = true
    titleLabel.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 12).isActive = true
    titleLabel.rightAnchor.constraint(equalTo: otherViewToTheSide.rightAnchor, constant: -24).isActive = true 

//...

I also like to implement the following methods instead of using hard coded values / strings.

/// Get the Height of the cell
/// use this in heightForRowAt indexPath
/// - Returns: CGFloat
class func height() -> CGFloat {
    return 175
}
//CustomCell2.height()


/// Get the string identifier for this class.
///
/// - Retruns: String
class var identifier: String {
    return NSStringFromClass(self).components(separatedBy: ".").last!
}
//use when registering cell:
self.tableView.register(CustomCell2.self, forCellReuseIdentifier: CustomCell2.identifier)

Solution 3

You can create UITableViewCell constraint Programatically like :

class ViewController: UITableViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.register(CustomCell2.self, forCellReuseIdentifier: "cell")
}

override func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 2
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? CustomCell2 else { return UITableViewCell() }
    cell.model = CellModel(labelString: "set constriant by code")
    return cell
}  
}

Define Model :

struct CellModel {
let labelString : String
}

Define custom Cell :

class CustomCell2 : UITableViewCell {
private let label : UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false // enable auto-layout
    label.backgroundColor = .green // to visualize 
    label.textAlignment = .center // text alignment in center
    return label
}()

private func addLabel() {
    addSubview(label)
    NSLayoutConstraint.activate([
        // label width is 70% width of cell
        label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.7),
        // center along horizontally 
        label.centerXAnchor.constraint(equalTo: centerXAnchor)
    ])
}

var model : CellModel? {
    didSet {
        label.text = model?.labelString ?? "Test"
    }
}

// Init 
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    addLabel()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
} 
}

Output

Share:
11,465
RanLearns
Author by

RanLearns

Love to learn. Love to code.

Updated on June 07, 2022

Comments

  • RanLearns
    RanLearns almost 2 years

    This is all taking place within this class:

    class CustomCell2: UITableViewCell {
    

    When I print the value of an existing width constraint (label = 0.7 * width of the cell) that was set in Storyboard, I see this:

    <NSLayoutConstraint:0x36b8750 UILabel:0x3d2aac0'Label'.width == 0.7*appName.CustomCell2:0x3e01880'CustomCell2'.width>
    

    When I try to create the same constraint programmatically, I get the error "The view hierarchy is not prepared for the constraint:"

    <NSLayoutConstraint:0xee08590 UILabel:0x3d2aac0'Label'.width == 0.9*appName.CustomCell2:0x3e01880'CustomCell2'.width>
    

    Seems exactly the same, so why can't that constraint be added?

    Constraint code in awakeFromNib() of CustomCell2:

    let widthConstraint = NSLayoutConstraint(item: labelName, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.width, multiplier: 0.9, constant: 0)
    labelName.addConstraint(widthConstraint)
    

    Rest of the error message:

    When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
    2017-12-03 20:18:51.183 appName[899:684382] View hierarchy unprepared for constraint.
        Constraint: <NSLayoutConstraint:0xee08590 UILabel:0x3d2aac0'Label'.width == 0.9*appName.CustomCell2:0x3e01880'CustomCell2'.width>
        Container hierarchy: 
    <UILabel: 0x3d2aac0; frame = (281 43; 674 54); text = 'Label'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x4233d30>>
        View not found in container hierarchy: <appName.CustomCell2: 0x3e01880; baseClass = UITableViewCell; frame = (0 0; 963 160); layer = <CALayer: 0xee08250>>
        That view's superview: NO SUPERVIEW
    Unable to install constraint on view.  Does the constraint reference something from outside the subtree of the view?  That's illegal. constraint:<NSLayoutConstraint:0xee08590 UILabel:0x3d2aac0'Label'.width == 0.9*appName.CustomCell2:0x3e01880'CustomCell2'.width> view:<UILabel: 0x3d2aac0; frame = (281 43; 674 54); text = 'Label'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x4233d30>>
    

    Thanks!

  • RanLearns
    RanLearns over 6 years
    Thanks so much for chiming in! Adding the override init makes Xcode error that "init(coder:) initializer is required," and after adding the init(coder:), the init function never gets called to run the addSubViewsAndLayout call. You are right that it is a tableName.dequeueReusableCell(withIdentifier: "CustomCell2") as! CustomCell2. awakeFromNib is getting called, but not init.
  • RLoniello
    RLoniello over 6 years
    implement the coder's call to super: public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
  • RLoniello
    RLoniello over 6 years
    Also make sure you are registering the cell in view did load or storyboards.
  • RanLearns
    RanLearns over 6 years
    Thanks! Registering the cell triggered init - do you know which part of this call may be failing as "Unexpectedly found nil while unwrapping an Optional value" in the addSubViewsAndLayout function? titleLabel.widthAnchor.constraint(equalTo: self.contentView.widthAnchor, multiplier: 0.9, constant: 0).isActive = true
  • RanLearns
    RanLearns over 6 years
    I see why it is nil... it is in the storyboard so I am not doing contentView.addSubview for it
  • RanLearns
    RanLearns over 6 years
    Can't find a way to make the UILabel subview of the CustomCell2 UITableViewCell be a non-nil value in init. I disconnected and reconnected the outlet from storyboard.
  • RanLearns
    RanLearns over 6 years
    The whole point of trying to change the constraint was not to create the label programmatically. I actually prefer to add it programmatically but in this case it was a storyboard outlet and I wanted to learn how to change its constraint values... =)
  • RanLearns
    RanLearns over 6 years
    sorry to bother, but I have seen elsewhere that calling register actually makes it so the cell does not get pulled from the storyboard which is causing the IBOutlets to be nil: stackoverflow.com/a/26411483/428981
  • RLoniello
    RLoniello over 6 years
    That is correct! no need to register manually if it is done in storyboards.
  • RanLearns
    RanLearns over 6 years
    the init function on the CustomCell2 UITableViewCell class never runs without that register, so addSubViewsAndLayout never gets called
  • RanLearns
    RanLearns over 6 years
    I placed the translatesAutoresizingMaskIntoConstraints = false in addSubViewsAndLayout since it is registered in storyboard, but init() function is never called
  • RanLearns
    RanLearns over 6 years
    ahh, init(style: reuseIdentifier:) doesn't get called on a storyboard cell but init?(coder) does! Still getting a nil value when trying to access the UILabel outlet though, and I'm no longer registering the cell other than in the storyboard...
  • RLoniello
    RLoniello over 6 years