I'm creating a chat using a UITable with custom cells for the chat bubbles. Like WhatsApp, I want some bubbles to be on the left, and some to be on the right.
I'm using dequeueReusableCell(withIdentifier identifier: String) -> UITableViewCell?, which calls the cell's init(style:reuseIdentifier:) method so setup my cell with:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
cellBackground = UIView()
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
setupConstraints()
}
func setupConstraints() {
NSLayoutConstraint.activate([
cellBackground.topAnchor.constraint(equalTo: contentView.topAnchor),
cellBackground.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
cellBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
cellBackground.widthAnchor.constraint(equalTo: contentView.widthAnchor, multiplier: 0.5)
])
}
but this isn't satisfactory. The cell is always on the left, due to the leadingAnchor constraint - when the cell is on the right it needs to be a trailingAnchor constraint.
If I use a setup function for the cell, should setupConstraints be called there, or should the init setup the constraints it knows about at that time? Alternatively, should these constraints all be set up in layoutSubviews()?
Add 2 instance variables as a references to the leading and trailing constraints inside the cell custom class
var leadCon,traCon:NSLayoutConstraint!
Then inside init also but out of the activate
leadCon = cellBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor)
leadCon.isActive = true
And
traCon = cellBackground.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
traCon.isActive = true
According to what you need add a func
func toLeft(_ value:Bool) {
if value {
leadCon.isActive = true
traCon.isActive = false
}
else {
leadCon.isActive = false
traCon.isActive = true
}
}
And call it after dequeue line in cellForRowAt
Related
I have a label that I cant add constraints to because it is in a Collection Reusable View, which doesn't have a viewDidLoad method. Is it possible to constrain the header so it is always 15 px from the left?
I tried adding constraints normally, but again, the Reusable View doesnt have a view for some reason. The other solutions I looked at all use a view of sorts.
override func layoutSubviews() {
super.layoutSubviews()
headerTitle.translatesAutoresizingMaskIntoConstraints = false
headerTitle.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 15).isActive = true
headerTitle.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
headerTitle.topAnchor.constraint(equalTo: view.topAnchor, constant: 40).isActive = true
headerTitle.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
headerTitle.frame = bounds
}
This code throws the error Cannot find 'view' in scope which im assuming is a Reusable View thing rather than a non existent view. Here is the rest of my code:
class HeaderCollectionReusableView: UICollectionReusableView {
static let identifier = "homeheader"
private let headerTitle = UILabel()
private let newDataSet = UIButton()
override init(frame: CGRect) {
super.init(frame:frame)
headerTitle.text = "AppTitle"
headerTitle.font = UIFont.systemFont(ofSize: 32.0, weight: .bold)
headerTitle.textAlignment = .left
headerTitle.numberOfLines = 0
addSubview(headerTitle)
}
required init?(coder: NSCoder) {
fatalError()
}
override func layoutSubviews() {
super.layoutSubviews()
headerTitle.frame = bounds //<-- want to replace this line with the constraints
}
}
I'm hoping the solution works with buttons too. This is my desired output:
desired output from constraints
You are trying to put constraints on a label but you don't have a parent view of that label. Constraints need some view to hold onto in order to be functional.
The new UIDatePicker style's doesn't appear to be respecting autolayout. It should be right aligned with the label expanding to take up the majority of the cell. Here is the code for the cell.
class DatePickerTableViewCell: UITableViewCell {
public let label = UILabel()
public let datePicker = UIDatePicker()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// Cell settings
self.label.translatesAutoresizingMaskIntoConstraints = false
self.datePicker.translatesAutoresizingMaskIntoConstraints = false
// Add views
self.contentView.addSubview(self.label)
self.contentView.addSubview(self.datePicker)
// Constrain views
self.label.setContentHuggingPriority(.defaultLow - 1, for: .horizontal)
self.datePicker.setContentHuggingPriority(.defaultLow + 1, for: .horizontal)
NSLayoutConstraint.activate([
self.label.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
self.label.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
self.label.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor),
self.label.trailingAnchor.constraint(equalTo: self.datePicker.leadingAnchor, constant: -8),
self.datePicker.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor),
self.datePicker.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
self.datePicker.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are some pictures showing what it is doing:
This is similar to this stack overflow question but doesn't appear to be fixed by their solution UIDatePicker with .compact style doesn't respect content hugging priority
I am trying to obtain the correct width for a custom cell inside a UITableViewCell Class. I tried the below:
import UIKit
import ChameleonFramework
class IsectionsCustomTableViewCell: UITableViewCell {
let cellContainer: UIView = {
let container = UIView()
container.backgroundColor = UIColor.flatPink()
container.translatesAutoresizingMaskIntoConstraints = false
return container
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addSubview(cellContainer)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func applyAppropriateSizeAndConstraintsForCellItems() {
NSLayoutConstraint.activate([
cellContainer.topAnchor.constraint(equalTo: self.topAnchor),
cellContainer.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -1*((self.frame.size.width)/2)),
cellContainer.bottomAnchor.constraint(equalTo: self.bottomAnchor),
cellContainer.leftAnchor.constraint(equalTo: self.leftAnchor)
])
}
}
However, as it can be seen from the attached image, the below line of code used above did not return the correct width as the total width was not split equally in half:
-1*((self.frame.size.width)/2)
Any idea what did I do wrong here, how can I obtain the true width of the custom Cell, which in my mind should be equal to the width of the Table it is going to be placed inside?
Regards,
Shadi Hammoudeh.
Do base the constraint on the frame. Use the proper constraint.
Change:
cellContainer.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -1*((self.frame.size.width)/2))
to:
cellContainer.rightAnchor.constraint(equalTo: cellContainer.superview.centerXAnchor)
I would like to obtain the width of a UILabel added as a subView inside a custom TableView Cell. The TableView Class I am using is listed below:
import UIKit
class customTableViewCell: UITableViewCell {
let customLabel: UILabel = {
let label = UILabel()
label.translateAutoConstraints = false
label.textAlignment = .left
return label
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(customLabel)
customLabel.topAnchor.constraint(equal: contentView.topAnchor).isActive = true
customLabel.leftAnchor.constraint(equal: contentView.leftAnchor, constant: 10).isActive = true
customLabel.rightAnchor.constraint(equal: contentView.rightAnchor, constant: (contentView.frame.size.width/2)-10-customLabel.frame.size.width).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError(“init(coder:) has not been implemented”)
}
}
However, from the above code when I assigned the rightAnchor constraint for the customLabel UILabel, Xcode did not return the correct width of the UILabel I was looking for.
I understand that I only specified the top and left constraints for the UILabel. I also know that UILabel by default has intrinsic layout on, that it can decide on the required height based on the content of the UILabel. However, I am wondering if I did not set the numberOfLines for the customUILabel defined in the above code as 0 (i.e., I only want my text inside the UILabel to occupy one line only). Can I obtain the width of the customUILabel before the text got truncated.
Let me explain further, if my customLabel has a lot of text, it will occupy the full width of the screen then gets truncated. However, if it does not contain a lot of text, then it’s width will be less than the width of the screen. And this is exactly what I am interested in obtaining, the width of the UILabel used to display the small text inside it?
Regards,
Shadi.
You need
self.contentView.rightAnchor.constraintGreaterThanOrEqualToAnchor(customLabel.rightAnchor, constant: 8.0).active = true
Then print the width inside
override func layoutSubviews() {
super.layoutSubviews()
print(customLabel.frame.width)
}
Don't use frames in constraints calculations
(contentView.frame.size.width/2)-10-customLabel.frame.size.width
Inside cell init they not yet calculated
I have added 2 labels to my cell and setup these constraints with snapkit, issue is I cant get the cell to expand correctly, it stays at its default height:
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(contentView.snp.top)
make.bottom.equalTo(descriptionLabel.snp.top)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.equalTo(contentView.snp.bottom)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
}
I mapped the four edges as you can see, however I know height isnt implied by these, how can I apply a height when the content is by nature, dynamic, and could be various heights...
setup for the labels looks like this:
lazy var titleLabel: UILabel = {
let titleLabel = UILabel()
titleLabel.textColor = .green
titleLabel.textAlignment = .center
contentView.addSubview(titleLabel)
return titleLabel
}()
lazy var descriptionLabel: UILabel = {
let descriptionLabel = UILabel()
descriptionLabel.textColor = .dark
descriptionLabel.textAlignment = .center
descriptionLabel.numberOfLines = 0
contentView.addSubview(descriptionLabel)
return descriptionLabel
}()
Give the table view an estimatedRowHeight, and set its rowHeight to UITableViewAutomaticDimension. Now the cells will be self-sizing. Well, if a label is pinned by all four sides to the content view, and if the cell is self-sizing, then that's all you have to do: the label will automatically change its height to accommodate its text, and the cell will automatically change size to accommodate the label.
First of all I think that you should add subviews to contentView in subclassed UITableViewCell class initializer method.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(titleLabel)
self.contentView.addSubview(descriptionLabel)
}
Secondly, make sure that in your viewDidLoad method (probably in your ViewController) these two lines are added:
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
Of course, you should change estimatedRowHeight to accommodate your needs.
One more thing worth mentioning - you can create these constraints easier (using power of SnapKit):
titleLabel.snp.makeConstraints { (make) -> Void in
make.top.left.right.equalTo(contentView)
}
descriptionLabel.snp.makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp.bottom)
make.bottom.left.right.equalTo(contentView)
}