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
Related
Im working on a chat app, so the height of the rows varies. I am using separate cells for plain txt msg and msg with image (and maybe text). I allow the user to select an image from the phone. I display that image in a separate VC where he can enter text if he chooses and send it. I have a model for the msg which means I do conversion between base64 and image formats. I have tried to simplify to the max my cell class to understand the following problem: the image inside the image view appears zoomed beyond what the normal phone zoom would allow; and the height of the cell is immense. On the cell class that I need to use I have more items and constraints but the basic logic of interest here is below:
fileprivate func configureMsgsTable() {
tableView.contentInsetAdjustmentBehavior = .automatic
tableView.backgroundColor = .clear
tableView.keyboardDismissMode = .interactive
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
tableView.sectionFooterHeight = 0.0
}
these are the functions I use for encoding/decoding:
fileprivate func convertImageToBase64String (img: UIImage) -> String {
let imageData:NSData = img.jpegData(compressionQuality: 0.50)! as NSData
let imgString = imageData.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters)
return imgString
}
fileprivate func convertBase64StringToImage (imageBase64String:String) -> UIImage {
let imageData = Data.init(base64Encoded: imageBase64String, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)
let image = UIImage(data: imageData!)
return image!
}
and this is the cell class.
class MsgWithImg: UITableViewCell {
//MARK: - Observer.
internal var valuesToDisplay: NewProjectGrpMsgModel! {
didSet {
imgView.image = convertBase64StringToImage(imageBase64String: valuesToDisplay.msgAttachment)
}
}
//MARK: - Properties.
fileprivate let imgView: UIImageView = {
let imgView = UIImageView(frame: .zero)
imgView.clipsToBounds = true
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFill
imgView.layer.cornerRadius = 0
return imgView
}()
//MARK: - Init.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
clipsToBounds = true
selectionStyle = .none
setupViews()
}
required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}
fileprivate func setupViews() {
contentView.addSubview(imgView)
let imgViewConstraints = [
imgView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 3),
imgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -3),
imgView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -45)
]
NSLayoutConstraint.activate(imgViewConstraints)
}
}
I spent some time thinking that this is an auto layout problem; or the fact that the table row height is automatic. that's why I built this test cell class with only the image view. but I think this problem is of a different nature. I did read quite a few answers to what I could find relevant on this website but I cannot determine what the problem is and the console does not print out anything.
The problem is that if you don't give a UIImageView both a width and a height, its intrinsicContentSize becomes the size of the image assigned to it.
With your code as-is, you've given the image view a width by constraining its Leading and Trailing anchors, but you haven't given it a height -- either by itself or by the cell's height (since you want auto-sizing cells).
So, if we use these four images:
The resulting table view looks like this:
And here's what's happening on an iPhone 13 (note: all sizes are rounded)...
For the 100x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 690
For the 100x300 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (300-pts), setting the image view frame 300-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 1035
For the 600x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 600 x 200
For the 800x600 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (600-pts), setting the image view frame 600-pts tall
image view is set to .scaleAspectFill, so the scaled size is 800 x 600
It may be clearer if we set the image view to .scaleAspectFit (with a red background so we can see the frame):
As a general rule, it is common to give the image view a fixed size (or proportional size), and use .scaleAspectFit to show the complete images. Or, also common, to use a pre-processor to generate "thumbnail" sized images for the table view cells.
Im working on a chat app, so the height of the rows varies. I am using separate cells for plain txt msg and msg with image (and maybe text). I allow the user to select an image from the phone. I display that image in a separate VC where he can enter text if he chooses and send it. I have a model for the msg which means I do conversion between base64 and image formats. I have tried to simplify to the max my cell class to understand the following problem: the image inside the image view appears zoomed beyond what the normal phone zoom would allow; and the height of the cell is immense. On the cell class that I need to use I have more items and constraints but the basic logic of interest here is below:
fileprivate func configureMsgsTable() {
tableView.contentInsetAdjustmentBehavior = .automatic
tableView.backgroundColor = .clear
tableView.keyboardDismissMode = .interactive
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 100
tableView.sectionFooterHeight = 0.0
}
these are the functions I use for encoding/decoding:
fileprivate func convertImageToBase64String (img: UIImage) -> String {
let imageData:NSData = img.jpegData(compressionQuality: 0.50)! as NSData
let imgString = imageData.base64EncodedString(options: Data.Base64EncodingOptions.lineLength64Characters)
return imgString
}
fileprivate func convertBase64StringToImage (imageBase64String:String) -> UIImage {
let imageData = Data.init(base64Encoded: imageBase64String, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)
let image = UIImage(data: imageData!)
return image!
}
and this is the cell class.
class MsgWithImg: UITableViewCell {
//MARK: - Observer.
internal var valuesToDisplay: NewProjectGrpMsgModel! {
didSet {
imgView.image = convertBase64StringToImage(imageBase64String: valuesToDisplay.msgAttachment)
}
}
//MARK: - Properties.
fileprivate let imgView: UIImageView = {
let imgView = UIImageView(frame: .zero)
imgView.clipsToBounds = true
imgView.translatesAutoresizingMaskIntoConstraints = false
imgView.contentMode = .scaleAspectFill
imgView.layer.cornerRadius = 0
return imgView
}()
//MARK: - Init.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
clipsToBounds = true
selectionStyle = .none
setupViews()
}
required init?(coder: NSCoder) {fatalError("init(coder:) has not been implemented")}
fileprivate func setupViews() {
contentView.addSubview(imgView)
let imgViewConstraints = [
imgView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 3),
imgView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -3),
imgView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
imgView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -45)
]
NSLayoutConstraint.activate(imgViewConstraints)
}
}
I spent some time thinking that this is an auto layout problem; or the fact that the table row height is automatic. that's why I built this test cell class with only the image view. but I think this problem is of a different nature. I did read quite a few answers to what I could find relevant on this website but I cannot determine what the problem is and the console does not print out anything.
The problem is that if you don't give a UIImageView both a width and a height, its intrinsicContentSize becomes the size of the image assigned to it.
With your code as-is, you've given the image view a width by constraining its Leading and Trailing anchors, but you haven't given it a height -- either by itself or by the cell's height (since you want auto-sizing cells).
So, if we use these four images:
The resulting table view looks like this:
And here's what's happening on an iPhone 13 (note: all sizes are rounded)...
For the 100x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 690
For the 100x300 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (300-pts), setting the image view frame 300-pts tall
image view is set to .scaleAspectFill, so the scaled size is 345 x 1035
For the 600x200 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (200-pts), setting the image view frame 200-pts tall
image view is set to .scaleAspectFill, so the scaled size is 600 x 200
For the 800x600 image:
your Leading/Trailing constraints make the image view frame 345-pts wide
no height set, so auto-layout uses the image size (600-pts), setting the image view frame 600-pts tall
image view is set to .scaleAspectFill, so the scaled size is 800 x 600
It may be clearer if we set the image view to .scaleAspectFit (with a red background so we can see the frame):
As a general rule, it is common to give the image view a fixed size (or proportional size), and use .scaleAspectFit to show the complete images. Or, also common, to use a pre-processor to generate "thumbnail" sized images for the table view cells.
I'm creating a UIStackView with an image and a label in a custom UIControl that will be in a custom UITableViewCell, except that my UIStackView has a height & width of 0 and XCode is complaining about breaking constraints. Only if I explicitly set a height and width does it show properly, which I don't want because the label text varies from cell to cell. (This is all happening programmatically.)
The Setup
In my UITableViewCell, I've got the following:
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(control)
NSLayoutConstraint.activate([
control.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
control.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
control.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
control.topAnchor.constraint(equalTo: contentView.topAnchor),
])
}
// empty coder init as well
private let control: MyControl = {
let control: MyControl = MyControl()
control.translatesAutoresizingMaskIntoConstraints = false
return control
}()
In MyControl, I just have the UIStackView, a UIImageView, and a UILabel. To not bore you with code...only the UIStackView (horizontal axis) is using constraints, pinning it to the four sides. The UIImageView (initiated with an image) is one arranged subview, the UILabel is the other (initiated with default text).
If you want to see the code:
class MyControl: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(stackView)
stackView.addArrangedSubview(icon)
stackView.addArrangedSubview(contentLabel)
NSLayoutConstraint.activate([
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
stackView.topAnchor.constraint(equalTo: topAnchor),
])
}
// empty coder init as well
private let contentLabel: UILabel = {
let label: UILabel = UILabel()
label.text = "Initial text"
return label
}()
private let icon: UIImageView = {
let iv: UIImageView = UIImageView(image: UIImage(named: "placeholder_image")!)
iv.contentMode = .scaleAspectFit
return iv
}()
private let stackView: UIStackView = {
let stackView: UIStackView = UIStackView()
stackView.axis = .horizontal
stackView.translatesAutoresizingMaskIntoConstraints = false
return control
}()
}
What I'm Expecting
I'm expecting the UIControl to be the height of the image (because it's taller than the label text), and the width of the image + the label text. (And then display full table cell width because of that constraint pinning). And because these are set on initialization of these components, I'd expect them to have an intrinsic height and width to pass to the UIStackView.
What I'm Getting
A height and width of zero, and XCode complaints of broken constraints. If I remove all constraints, I get no complaints but nothing appears (as if the height & width are zero but XCode doesn't care because I didn't set any constraints).
What I've Tried
Literally every combination of layout constraints, including none on everything and as many as I can on everything. What I'd like is for the image + label text to set the height and width of the UIStackView which would then set the height and width of the UIControl, which would then set the height in the UITableViewCell (I know I have width 100% - that will change later).
Other Considerations
There's nothing else special about my UITableViewCell that would cause any issue here except that in my actual code, I have a multi-line label above MyControl which should (and does) cause my UITableViewCell to expand in height.
The problem with your question is that so much of the code you've shown is bogus that it's hard to guess what you might actually be doing. You claim that in your table view cell (I presume it's a table view cell subclass) you are saying
override init(frame: CGRect) {
super.init(frame: frame)
But that would never compile, as init(frame:) is not the designated initializer for a UITableViewCell. You have code like this:
private let contentLabel: UILabel = {
let label: UILabel = UILabel()
label.text = "Initial text"
return label
}
But that would never compile, as a function is not a label.
If we make allowances for all of that and fix your code, it's difficult to see what you would be doing wrong. I corrected your code so that it would compile, and I got this in my table view:
That might not be exactly what you were after, but the image view is certainly sizing the cells to its own height.
What you are doing is not at all how one makes and configures a table view cell (you should be doing the work in cellForRowAt, not hard coding the cell's image view and label contents in the cell's initializer), but given what you've shown, the image view does size the stack view which does size the cell (contrary to my own initial expectations).
Another issue is that you can't put an image view and a label into an autolayout situation without resolving the ambiguity as to which should predominate. I added this line to do that:
iv.setContentHuggingPriority(.defaultLow+1, for: .horizontal)
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 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)
}