4

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!

3 Answers 3

7

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)
Sign up to request clarification or add additional context in comments.

13 Comments

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.
implement the coder's call to super: public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
Also make sure you are registering the cell in view did load or storyboards.
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
I see why it is nil... it is in the storyboard so I am not doing contentView.addSubview for it
|
1

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

Comments

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()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.