create UITableViewCell constraint programmatically
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")
}
}
Comments
-
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 over 6 yearsThanks 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 over 6 yearsimplement the coder's call to super: public required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
-
RLoniello over 6 yearsAlso make sure you are registering the cell in view did load or storyboards.
-
RanLearns over 6 yearsThanks! 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 over 6 yearsI see why it is nil... it is in the storyboard so I am not doing contentView.addSubview for it
-
RanLearns over 6 yearsCan'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 over 6 yearsThe 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 over 6 yearssorry 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 over 6 yearsThat is correct! no need to register manually if it is done in storyboards.
-
RanLearns over 6 yearsthe init function on the CustomCell2 UITableViewCell class never runs without that register, so addSubViewsAndLayout never gets called
-
RanLearns over 6 yearsI placed the translatesAutoresizingMaskIntoConstraints = false in addSubViewsAndLayout since it is registered in storyboard, but init() function is never called
-
RanLearns over 6 yearsahh, 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 over 6 yearsLet us continue this discussion in chat.