Custom UITableViewCell - can't seem to add subview - ios

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)

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.

Wrap label around UIbutton

I'm trying to create a view like below using storyboard in Xcode.
For this, I've added a button and a label with constraints but this is the result I get. The text doesn't start below the checkbox. One way to achieve this would be to create 2 labels and add the strings that start after this string to 2 label and place it under the first label.
Is there any better way to do this? Also, when you click on Read more the text can expand as well.
You can make the leading of the button and the label the same ( button above label ) and insert some empty characters at the beginning of the label text
You can do a way. Take the button and the label in a view
then sub divide the view into two views, left one holds the button and right one holds the label. make a gap between left and right
button's constraint will be leading , top and trailing to zero and height as your wish
label's constraint will be leading , top, trailing and bottom.
You can accomplish this by using a UITextView and setting an ExclusionPath.
The ExclusionPath (or paths) defines an area within the text view's textContainer around which the text should wrap.
If you disable scrolling, selection and editing of the UITextView it will behave just like a UILabel but will also give you the benefit of ExclusionPath
Here is a simple example - lots of hard-coded values, so you'd probably want to make it a custom class - but this should get you on your way:
class TextViewLabel: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() -> Void {
isScrollEnabled = false
isEditable = false
isSelectable = false
textContainerInset = UIEdgeInsets.zero
textContainer.lineFragmentPadding = 0
}
}
class ExclusionViewController: UIViewController {
let checkBox: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let theTextViewLabel: TextViewLabel = {
let v = TextViewLabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.text = "I agree to receive information about this application and all the updates related to this..."
v.font = UIFont.systemFont(ofSize: 20.0)
return v
}()
var isChecked: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(theTextViewLabel)
theTextViewLabel.addSubview(checkBox)
let cbWidth = CGFloat(20)
NSLayoutConstraint.activate([
theTextViewLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 100.0),
theTextViewLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 40.0),
theTextViewLabel.widthAnchor.constraint(equalToConstant: 240.0),
checkBox.topAnchor.constraint(equalTo: theTextViewLabel.topAnchor, constant: 2.0),
checkBox.leadingAnchor.constraint(equalTo: theTextViewLabel.leadingAnchor, constant: 0.0),
checkBox.widthAnchor.constraint(equalToConstant: cbWidth),
checkBox.heightAnchor.constraint(equalToConstant: cbWidth),
])
theTextViewLabel.textContainer.exclusionPaths = [
UIBezierPath(rect: CGRect(x: 0, y: 0, width: cbWidth + 8.0, height: cbWidth))
]
updateCheckboxImage()
checkBox.addTarget(self, action: #selector(checkBoxTapped), for: .touchUpInside)
}
func updateCheckboxImage() -> Void {
if isChecked {
checkBox.setImage(UIImage(named: "SmallChecked"), for: .normal)
} else {
checkBox.setImage(UIImage(named: "SmallUnChecked"), for: .normal)
}
}
#objc func checkBoxTapped() -> Void {
isChecked = !isChecked
updateCheckboxImage()
}
}
Result:
(I used these two images for the checkBox):

Unable to satisfy Bottom Constraint of UILabel to ContentView of Dynamic UITableViewCell

All implementations done programmatically(No Storyboards!).
I have created dynamic tableView with UITableView.automaticDimension with all custom Cells. All Cells are working fine and this one too but this one is generating Constraint warnings in debug.
Though, the layout is perfect and displaying as it should.
It has just one label and 3 CAShapeLayers. Below is the implementation Code:
//BadCustomTableViewCell
override func layoutSubviews() {
super.layoutSubviews()
setUpViews()
setUpConstraints()
}
let badLabel:UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 40, weight: UIFont.Weight.light)
label.text = "899"
label.textColor = .black
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setUpViews() {
contentView.addSubview(badLabel)
}
func setUpConstraints() {
badLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
badLabel.layoutIfNeeded()
let safeMargin:CGFloat = badLabel.frame.size.width - 15
badLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: safeMargin).isActive = true
badLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -safeMargin).isActive = true
}
Everything is fine according to me, but I don't what's breaking the constraints!
The log shows this -
(
"<NSLayoutConstraint:0x282729720 V:|-(77.6667)-[UILabel:0x103179a60'65.89 %'] (active, names: '|':UITableViewCellContentView:0x10317a150 )>",
"<NSLayoutConstraint:0x282729950 UILabel:0x103179a60'65.89 %'.bottom == UITableViewCellContentView:0x10317a150.bottom - 77.6667 (active)>",
"<NSLayoutConstraint:0x2827297c0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x10317a150.height == 48.6667 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x282729950 UILabel:0x103179a60'65.89 %'.bottom == UITableViewCellContentView:0x10317a150.bottom - 77.6667 (active)>
Any Idea what I might be missing here?
Lower the bottom constraint
let con = badLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -safeMargin)
con.priority = UILayoutPriority(999)
con.isActive = true
and add the setup & constraints inside
override init(style: UITableViewCell.CellStyle,reuseIdentifier: String?)
as
override func layoutSubviews()
is called multiple times
`
Although it is quite late, I'm posting this answer anyway. Most of the times, you don't need to re-setup your constraints and re-add your subviews to your super/parent view. So your setUpViews() and setUpConstraints() should be indeed inside override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) like so:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
setUpConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Also, you don't need (most of the times, again), at least for me, to setup priorities to your constraints. As much as possible, solve the constraints issues/warnings without lowering priorities.
badLabel.layoutIfNeeded() isn't needed too, unless you're actually modifying some constants of your constraints or re-making your constraint. Well it makes sense to use such line if you REALLY want to setup everything in your layoutSubviews()
This whole class similar to your cell worked for me:
class BadCustomTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
setUpConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let badLabel:UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 40, weight: UIFont.Weight.light)
label.text = "899"
label.textColor = .black
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setUpViews() {
contentView.addSubview(badLabel)
}
func setUpConstraints() {
badLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
let safeMargin:CGFloat = badLabel.frame.size.width - 15
badLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: -safeMargin).isActive = true
badLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: safeMargin).isActive = true
}
}

TextView not resizing properly

I have a tableview cell with a textview inside it:
class DetailsCell: UITableViewCell, UITextViewDelegate {
let detailsTextView: UITextView = {
let tv = UITextView()
tv.font = UIFont(name: "AvenirNext-Medium", size: 24)
tv.isScrollEnabled = false
tv.textColor = .white
tv.backgroundColor = .clear
tv.translatesAutoresizingMaskIntoConstraints = false
return tv
}()
func textViewDidChange(_ textView: UITextView) {
let addTodoViewController = AddTodoViewController()
addTodoViewController.begindEndUpdate()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: "DetailsCell")
addSubview(detailsTextView)
detailsTextView.topAnchor.constraint(equalTo: topAnchor, constant: 10).isActive = true
detailsTextView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
detailsTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
detailsTextView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
backgroundColor = .black
textLabel?.isHidden = true
detailsTextView.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I load the app and the textview already has a text, assigned programmatically, the cell properly get's the right height and the textview is properly resized, but when I try to change the text of the textview, the cell and the textview don't resize. Can someone help me?
override func viewWillAppear(_ animated: Bool) {
toDoTextField.becomeFirstResponder()
addTodoTableView.estimatedRowHeight = 60
addTodoTableView.rowHeight = UITableViewAutomaticDimension
}
Textview with not initial text Image
Textview with initial text Image
UITextView does not resize itself when its content changes. You have to write some code to resize your textView and then accordingly adjust the height of your cell. Probably this can help you resizing textview to its content
or you can use some third party library instead of default UITextView like this one GrowingTextView

Creating Custom UITableViewCell in Swift Programmatically

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.

Resources