Creating Custom UITableViewCell in Swift Programmatically - uitableview

I need a cell with a label that takes up the entire contentView. I've done this a bunch of times with a nib but this time I decided to avoid the creating a nib. Below is my code.
class StackLegendCell: UITableViewCell {
var title = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(title)
title.numberOfLines = 1
title.textAlignment = .center
title.setContentCompressionResistancePriority(1000, for: .horizontal)
title.setContentHuggingPriority(1000, for: .horizontal)
title.text = "??"
//contentView.translatesAutoresizingMaskIntoConstraints = false
title.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 1.0).isActive = true
title.heightAnchor.constraint(equalTo: contentView.heightAnchor, multiplier: 1.0).isActive = true
}
The problem is that the label doesn't show anything. A check into view debugger shows that width = height = 0. But I'm setting height and width anchors which should give a size to be the same as the contentView.
What am I missing?
Thanks.

Finally, I found the solution. You missed setting the frame of the titleLabel
title.frame = contentView.frame
add this line of code and build. This should work.

Related

UITableViewCell height not dynamically being calculated even with all the constraints added

I'm trying to create a custom UItableViewCell with three labels. Two of them on the left side (like the built-in title + subtitle layout) and the other one at the right side. I'm laying out the labels programatically.
class CustomCell: UITableViewCell {
static let identifier = String(describing: self)
lazy var primaryLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .body)
return label
}()
lazy var secondaryLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .footnote)
return label
}()
lazy var tertiaryLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .callout)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
contentView.addSubview(primaryLabel)
contentView.addSubview(secondaryLabel)
contentView.addSubview(tertiaryLabel)
NSLayoutConstraint.activate([
tertiaryLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
tertiaryLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
tertiaryLabel.heightAnchor.constraint(equalToConstant: 18),
primaryLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
primaryLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 12),
primaryLabel.heightAnchor.constraint(equalToConstant: 19),
secondaryLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
secondaryLabel.topAnchor.constraint(equalTo: primaryLabel.bottomAnchor, constant: 8),
secondaryLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 12),
// secondaryLabel.heightAnchor.constraint(equalToConstant: 15)
])
}
}
I want the cell's height to be calculated dynamically. I set the rowHeight and estimatedRowHeight property values to UITableView.automaticDimension in the view controller when I instantiate the table view.
private lazy var tableView: UITableView = {
let tableView = UITableView(frame: view.bounds, style: .grouped)
tableView.allowsSelection = false
tableView.dataSource = self
tableView.delegate = self
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension
tableView.register(CustomCell.self, forCellReuseIdentifier: CustomCell.identifier)
return tableView
}()
But the cell still shows up like this. With the default height. Even though I have auto layout constraints laid out from top to bottom. I get no warnings in the console either.
Any idea what I am missing here?
Demo project
Two observations:
Your secondary label needs a negative constant to the bottom of the container.
Also, your estimated row height should be some reasonable fixed value (e.g 44 or something like that ... it doesn't need to be perfect, but just some reasonable value that the table can use for estimating the height of rows that haven't been presented yet). You do not want to use UITableView.automaticDimension for estimatedRowHeight, only for rowHeight.

Custom UITableViewCell - can't seem to add subview

I've created a custom UITableViewCell class as shown below (programmatically - no use of storyboards for this):
import UIKit
class MainGroupCell: UITableViewCell {
var groupLabel : UILabel {
let label = UILabel()
label.textColor = .black
label.text = "Test Group"
label.font = UIFont(name: "candara", size: 20)
return label
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(groupLabel)
groupLabel.snp.makeConstraints({make in
make.center.equalTo(self.contentView)
})
}
required init?(coder aDecoder: NSCoder){
fatalError("init(coder:) has not been implemented")
}
}
And for some reason, I'm hitting the error that contentView and groupLabel are not in the same view hierarchy, but they are - I've added groupLabel as a subview to contentView as you can see. Any reason for hitting this error? I gave it a shot with regular Atuolayout API as well, instead of SnapKit, and no such luck. Feel like this might be a small mistake I'm missing. I've also attempted the equalToSuperview constraint, rather than what I have shown above, but as expected it also throws the same error - groupLabel's superview returns nil.
Error:
Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x280870b80
"UILabel:0x105e79fa0'Test Group'.centerX"> and <NSLayoutXAxisAnchor:0x280870a00
"UITableViewCellContentView:0x105fa16a0.centerX"> because they have no common ancestor.
Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
Try this ,
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
addSubview(groupLabel)
groupLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
groupLabel.leadingAnchor.constraint(equalTo: leadingAnchor,constant: 16),
groupLabel.topAnchor.constraint(equalTo: topAnchor, constant: 16),
groupLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
groupLabel.bottomAnchor.constraint(equalTo: bottomAnchor,constant: -16),
])
}
change your grouplabel like this
let groupLabel : UILabel = {
let label = UILabel()
label.textColor = .black
label.text = "Test Group"
label.font = UIFont(name: "candara", size: 20)
return label
}()
Change to
make.center.equalTo(self.contentView.snp.center)
or
make.center.equalToSuperview()
Instead of
make.center.equalTo(self.contentView)

UITableViewCell subclassed in swift 3 has contentView always of 320 points

I am trying to create a UILabel in my subclassed UITableViewCell to make it the full width of the cell. But the contentView is always 320pt if in iPhone 7 or 7+.
The code is:
import UIKit
import SwipeCellKit
class ReuseableCell: SwipeTableViewCell
{
public var test = UILabel();
var animator: Any?
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier);
// Always 320pt and I want full width of the table cell
var width = self.contentView.frame.width;
test = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: 100));
test.text = "Lorem ipsum...";
...
I have been stuck on this for 2 nights. Thanks.
You can try auto-layout
override init(style: UITableViewCellStyle, reuseIdentifier: String!)
{
super.init(style: style, reuseIdentifier: reuseIdentifier);
test = UILabel(frame: CGRect.zero);
self.contentView.addSubview(test)
test.translatesAutoresizingMaskIntoConstraints = false
test.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
test.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
test.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
test.heightAnchor.constraint(equalToConstant: 200).isActive = true
test.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
You have to add Your label inside override init(style: UITableViewCellStyle, reuseIdentifier: String!) but to read correct frame you have override override func layoutSubviews() and read correct frame
May be you forgot to set constrains to your 'UITableView'. Try set tableview width constrain equal to superview width.
In ViewController:
self.view.addConstraint(NSLayoutConstraint(item: self.tableView, attribute: .width, relatedBy: .equal, toItem: self.view, attribute: .width, multiplier: 1.0, constant: 0))

create UITableViewCell constraint programmatically

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

icon changes the position on selection of UITableViewCell

I have a prototype cell. When the table view controller is opened for the first time. the cell looks like this:
Question #1: I have a constraint that the leading space to superview of calendar icon should be 10 points. but in reality it's 16 points.
Then very strange thing happens. I click on the cell. It goes to the next screen. Then I come back to this screen. And the icon is positioned correctly (10points space):
Then what happens next is: if i click on the cell, for a very short moment (when highlighted) the icon moves to the right for 6 points:
Question #2: how to avoid this 'moving' ?
What I tried to do: added these 2 lines to didSelectRowAtIndexPath and willSelectRowAtIndexPath
cell?.setNeedsLayout()
cell?.setNeedsUpdateConstraints()
But it doesn't help much.. Please let me know if you need more details. Btw, I have no code that changes any constraints. Everything is static.
EDIT: this is how constraints of icon (imageView) looks like:
note: leading space to superview equals to 2 because it has some default of 8 (so 2+8=10).
Please adapt and try my code. Before register this cell
tableView.register(UserCell.self, forCellReuseIdentifier: "myCell")
and initialize
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! UserCell
======
class UserCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
textLabel?.frame = CGRect(x: 56, y: (textLabel?.frame.origin.y)! - 2, width: (textLabel?.frame.width)!, height: (textLabel?.frame.height)!)
detailTextLabel?.frame = CGRect(x: 56, y: (detailTextLabel?.frame.origin.y)! + 2, width: (detailTextLabel?.frame.width)!, height: (detailTextLabel?.frame.height)!)
}
let profileImagView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.layer.cornerRadius = 20
imageView.layer.masksToBounds = true
imageView.image = UIImage(named: "logo")
imageView.contentMode = .scaleAspectFill
return imageView
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
addSubview(profileImagView)
profileImagView.leftAnchor.constraint(equalTo: self.leftAnchor,constant:8).isActive = true
profileImagView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
profileImagView.widthAnchor.constraint(equalToConstant: 40).isActive = true
profileImagView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I have not tried through the  xib, however I think it makes sense

Resources