Scenario
I am using the automaticDimension option on UITableView. I'd like to have a single UILabel in my cell that is self-sizing to fit the text.
Setup
Here you can see how I setup the label. The edges are equal to the contentView margins.
Problem
The height is set to 37.0 points on the phone when the text fits one line. 44.0 should be the minimum.
Question
How do I have to setup the layout to maintain a minimum cell height of 44.0 (default height, fitting the other cells)?
Edit:
Using the built-in 'basic' TableViewCell with numberOfLines = 0 seems to be the most easy and best solution! Suggested by eddwinpaz. Thank you all.
Change the top anchor from constraint(equalTo:) to greaterThanOrEqualTo: and the bottom one to lessThanOrEqualTo:.
Then make the label centered on the Y-Axis.
Then you should set cell. heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
You need to use numberOfLine = 0 in case you are just working with a Single UILabel. Else. You need to use constrains.
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
return cell
}
}
In case you need to use a custom UILabel like your case.
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "reuseIdentifier")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! CustomCell
cell.myLabel.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
return cell
}
}
class CustomCel: UITableViewCell {
let myLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupViews(){
addSubview(myLabel)
myLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
myLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
myLabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
myLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Related
I need a help! How can I calculate the aspect ratio of view which locates in tableView cell, this view should display for different devices (17 : 10) in this aspect ratio. I have a table which contains a certain number of cells. I set an arbitrary table's cell length of 226.0. This is done to show an arbitrary distance of 24 points between cells. (No-Storyboard)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 226.0
}
Inside the cell, I have a view that matches a height of (17:10). I use a SnapKit for constraints.
cellView.snp.makeConstraints { make in
make.top.equalTo(self.contentView.snp.top)
make.leading.equalTo(self.contentView.snp.leading)
make.trailing.equalTo(self.contentView.snp.trailing
make.height.equalTo(self.contentView.snp.width).multipliedBy(10.0 / 17.0)
}
For test I am using iPhone SE (3rd generation), after running the application, the aspect ratio only works for this model which has a distance of 24 points between cells. If you run other simulator models, such as iphone 14 pro max, the cells stick together. How can I calculate so that for different models the distance always remains 24 points between cells?
Edit
SE 3rd Gen Simulator result
As you can see in the screenshot, this simulator corresponds to the aspect ratio of 17:10 and plus there is a distance between other cells of 24 points. But if you run a simulator of a larger model, then the cells stick together.
You want to allow the cells to set their own height, so...
Get rid of this completely:
//func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// return 226.0
//}
Configure your cell class like this:
class AspectCell: UITableViewCell {
let cellView = UIView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
cellView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(cellView)
cellView.snp.makeConstraints { make in
// use Priority of Required-1 to avoid autolayout complaints
make.top.bottom.equalTo(self.contentView).inset(12.0).priority(.init(999.0))
make.leading.trailing.equalTo(self.contentView).inset(12.0)
make.height.equalTo(self.contentView.snp.width).multipliedBy(10.0 / 17.0)
}
cellView.backgroundColor = .systemRed
cellView.layer.cornerRadius = 12
}
}
and here's a basic table view controller to see the results:
class AspectTableViewController: UITableViewController {
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemYellow, .green, .blue,
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorStyle = .none
tableView.register(AspectCell.self, forCellReuseIdentifier: "aspectCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "aspectCell", for: indexPath) as! AspectCell
c.cellView.backgroundColor = colors[indexPath.row % colors.count]
return c
}
}
Output:
Edit - to clarify constraint priority...
A very common situation when using dynamic sizing constraints in cells (and other views) is we end up with a not-quite-valid value.
For example, running this on aniPhone 14 Pro, the cellView with 12-points on each side:
actual width of the cell is 393
actual width of cellView subview is 369 (393 - 24)
369.0 x 10.0 / 17.0 = 217.05882352941177
Auto-layout will print warnings to the debug console because it cannot set a Height of 217.05882352941177 ... it will use 217.
By giving the Bottom constraint a Priority of less-than-required, we're telling auto-layout that we're okay if it uses 217 (i.e. it is not required).
This is a better makeConstraints block (we don't need to lower the priority on the top constraint). And, the code above used the contentView width instead of the cellView width, so it was not quite 17:10 aspect ratio:
cellView.snp.makeConstraints { make in
// 12-points on each side
make.leading.trailing.equalTo(self.contentView).inset(12.0)
// 12-points from the top
make.top.equalTo(self.contentView).inset(12.0)
// proportional height (17:10)
make.height.equalTo(cellView.snp.width).multipliedBy(10.0 / 17.0)
// use Priority of Required-1 to avoid autolayout complaints
make.bottom.equalTo(self.contentView).inset(12.0).priority(.init(999.0))
}
I have created a custom cell below like this.
Issue is when I first launch then it takes height (which is from top and bottom).But when scroll up & down then extra height is removed. I am not able to understand why this issue is happening.
Top label has line number as fixed.
Second label has line number as 0.
Code for controller
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 15
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:SampleCell = tableView.dequeueReusableCell(withIdentifier: "SampleCell") as! SampleCell
if indexPath.row == 0 {
cell.labelDescription.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.f Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.f Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.f Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like "
} else {
cell.labelDescription.text = "skdfjksj gkfjg"
}
tableView.sizeToFit()
return cell
}
Constraint for UIImage
Constraint for Top UILabel
Constraints for bottom UILabel
Set UITableViewAutomaticDimension as heightForRowAt
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
set numberOfLine of UILabel to 0
& set leading trailing top bottom constraint
I have a table with some customizations.
Here is my code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
var exercises : [String] = ["Swimming", "Running", "Weight Lifting", "Biking", "Climbing"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
//Necessary for basic tableView setup. Defines number of rows in a specific section.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
//Setting the amount of rows to the number of elements in exercises. This function returns that.
tableView.backgroundColor = UIColor.clearColor()
return exercises.count
}
//Necessary for basic tableView setup. Helps us out content for every cell in the index path. Runs = rows
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
tableView.separatorColor = UIColor.clearColor()
//Setting the footer to default so the extra junk does not show
tableView.tableFooterView = UIView()
//This will be returned. This automatically creates a prototype cell
var cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
//Setting every cell to the respective item in exercises
cell.textLabel?.text = exercises[indexPath.row]
cell.textLabel?.font = UIFont(name: "Avenir-Light", size: 17)
cell.textLabel?.textColor = UIColor.whiteColor()
cell.textLabel?.textAlignment = .Center
//Border Code
cell.layer.borderWidth = 2.0
cell.layer.borderColor = UIColor.whiteColor().CGColor
//Round Corners
cell.layer.cornerRadius = 20
return cell
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
cell.backgroundColor = UIColor.clearColor()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I want to have some spacing between each UITableViewCell. I have already tried the following:
Change the height of each row. This option does not work because I have borders. Adding more height just makes each row look larger.
Convert each row into a section and then use heightForHeader in section.The post. I want to avoid this option because I would have to convert all my rows to sections.
Add a transparent UIView within each row. Again, this option does not work because I have borders.
Is there any other alternative?
Thanks
First of all, you should move tableView related code out of tableView:cellForRowAtIndexPath, preferably to viewDidLoad:
override func viewDidLoad {
super.viewDidLoad()
tableView.separatorColor = UIColor.clearColor()
tableView.tableFooterView = UIView()
}
Secondly, UITableViewCells are reusable objects so they are dequeued by the tableView when required:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
}
...
}
As for your problem, you should either set rowHeight on tableView
override func viewDidLoad {
super.viewDidLoad()
...
tableView.rowHeight = 100.0
}
or implement tableView:heightForRowAtIndexPath: instead:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100.0
}
You should also update textLabel's border and corner radius value instead of the cell:
//Border Code
cell.textLabel.layer.borderWidth = 2.0
cell.textLabel.layer.borderColor = UIColor.whiteColor().CGColor
//Round Corners
cell.textLabel.layer.cornerRadius = 20
I tried ozgur's method but it didn't work because I had borders between my table view cells. Eventually, I used the answer from this post. Hope it helps
You can add spacing by creating an extra view inside the cell that contains the content of the cell that has spacing between the top and the bottom. Make the background color of the cell translucent and it'll appear as though the cell has spacing above and below it
I'm creating a 2 sections UITableView. The first section uses UITableViewCells with the Default style, the second uses Subtitle style. Both cells may have a multiline text label.
I've set the table's rowHeight to UITableViewAutomaticDimension.
As you can see from the screenshot below, UITableViewAutomaticDimension is respected only partially when using the Subtitle style: the height seems to fit the textLabel's height but not the detailLabel's one.
How can i make a Subtitle cell getting the right height?
Some code:
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.estimatedRowHeight = 80
tableView.rowHeight = UITableViewAutomaticDimension
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "defaultCell")
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "subtitleCell")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section === 1 {
var cell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "subtitleCell")
cell.textLabel?.numberOfLines = 0
cell.textLabel?.text = "A Long text..."
cell.detailTextLabel?.numberOfLines = 0
cell.detailTextLabel?.text = "A Long text..."
return cell
}
else {
var cell = tableView.dequeueReusableCellWithIdentifier("defaultCell") as! UITableViewCell
cell.textLabel!.text = "A long text..."
cell.textLabel?.numberOfLines = 0
return cell
}
}
I've tried to implement a subclass of UITableViewCell but I many problems with autolayout and section titles (a long story), so I wonder if I could fix the issue in a simpler way.
Just tried on Xcode 9 and iPhone 8 simulator, works very well.
code is here https://github.com/williamhqs/testSubtitleCellLayout
Currently I am messing around with swift and dynamic table cell heights. I developed a simple app for iOS8.1 on xcode6.1: https://github.com/ArtworkAD/DynamicCellTest
So to achieve a cell height that stretches with the cell's content I do the following:
in storyboard set label lines to 0
set labels font to system
set constraints for label in cell
add self.tableView.rowHeight = UITableViewAutomaticDimension
don't override heightForRowAtIndex method
Minimal code is needed:
class MyTableViewController: UITableViewController {
var entries:Array<String> = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//create dummy content
var i = 0
while i < 10 {
entries.append("\(i) Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor")
entries.append("\(i+1) Lorem ipsum dolor sit amet")
i = i + 2;
}
self.tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.entries.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("basic_cell", forIndexPath: indexPath) as UITableViewCell
var label = cell.viewWithTag(13)
if let unwrappedLabel = label as? UILabel {
unwrappedLabel.text = self.entries[indexPath.row]
}
return cell
}
}
The left image shows the result of the above code. The cell height grows with the content of the label, all nice. However when you click on the disclosure indicator to the detail view and move back again, you get the right image. Why is this happening??
A bad solution for this problem is to override this methods:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.tableView.reloadData()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.tableView.reloadData()
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
self.tableView.reloadData()
}
This way the above problem is solved, but this solution seems not right? A side effect of it is, that when self.tableView.reloadData() is called the table view port jumps to the first cell which doesn't look nice.
Does anyone has an idea what I am doing wrong? Feel free to clone my repo https://github.com/ArtworkAD/DynamicCellTest and test it out.
Adding this seems that it is able to fix rotation problem.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
self.tableView.estimatedRowHeight = 36.5
}
However, I have another case that there are 4 labels in the cell, which has the same rotation problem, adding this is not enough and I ended up replacing the self.tableView.estimatedRowHeight = 36.5 with reloading visible cells.
I've just solved exactly that problem by overriding tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) in UITableViewDelegate object.
func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath)
-> CGFloat {
return 200
}
Changing the exact returned value apparently have no effect. The cells are always properly self-sizing. I've got no idea why providing estimated height causes the autolayout to kick in, but it does. iOS 8.1.2, Xcode 6.1.
The link provides a good explanation of what needs to be done.
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
For your specific problem you are missing two lines of code.
In viewDidLoad under your tableView.rowHeight add self.tableView.estimatedRowHeight = 64
The above will provides allow the tableView to assign estimated height values for offscreen cells to enhance performance and it also calculates the scroll bar height.
The main problem is you are missing unwrappedLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds) in cellForRowAtIndexPath this tells the label its preferred maximum width so it can calculate the height it requires for multi-line text. Usually you would do this in the table view cell subclass.
I have tested this in your project and it works
You have to set estimatedRowHeight:
// setup automatic row height
self.tableView.rowHeight = UITableViewAutomaticDimension
// whatever you think is the approximate row height
self.tableView.estimatedRowHeight = 44
Also came across an alternative here http://useyourloaf.com/blog/2014/08/07/self-sizing-table-view-cells.html. A comment mentions -
Simply make the cell layout its subviews before returning it from cellForRowAtIndexPath:
[cell setNeedsDisplay];
[cell layoutIfNeeded];