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
Related
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")
}
}
I have 3 custom cells in Static TableView. All cells have different heights. But I need to make height of newsDetail TextView dynamic according to its content size. It was easy to make dynamic for label in tableview. But it didn't work out for TextView in Static TableView. I searched but couldn't find proper solution for this, anyone please could help me in swift 3? My codes:
import UIKit
class DetailTableView: UITableViewController {
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var newsHeadline: UILabel!
#IBOutlet weak var newsDetail: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
newsDetail.text = "Barack Hussein Obama II (US: /bəˈrɑːk huːˈseɪn oʊˈbɑːmə/ (About this sound listen) bə-RAHK hoo-SAYN oh-BAH-mə;[1][2] born August 4, 1961) is an American politician who served as the 44th President of the United States from 2009 to 2017. He is the first African American to have served as president. He previously served in the U.S. Senate representing Illinois from 2005 to 2008 and in the Illinois State Senate. Obama was born in 1961 in Honolulu, Hawaii, two years after the territory was admitted to the Union as the 50th state. Raised largely in Hawaii, Obama also spent one year of his childhood in Washington State and four years in Indonesia. After graduating from Columbia University in 1983, he worked as a community organizer in Chicago. In 1988 Obama enrolled in Harvard Law School, where he was the first black president of the Harvard Law Review. After graduation, he became a civil rights attorney and professor, and taught constitutional law at the University of Chicago Law School from 1992 to 2004. Obama represented the 13th District for three terms in the Illinois Senate from 1997 to 2004, when he ran for the U.S. Senate."
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
}
public override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
}
First you need to make sure that scroll is disabled for your UITextView.
commentsTextView.isScrollEnabled = false
Then, set the autolayout constraints in your storyboard for your UITextView, but make sure to not set one for it's height.
You should implement UITableViewDataSource methods heightForRowAt and estimatedHeightForRowAt or use the UITableViewController corresponding attributes.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 77
}
If your UITextView is editable, and you want it to resize dynamically while you're typing, make your controller it's delegate, and implement textViewDidChange as following:
extension ContactDetailsViewController: UITextViewDelegate {
// Resize textview depending on it's content
func textViewDidChange(_ textView: UITextView) {
// Get textview content size
let size = textView.bounds.size
let newSize = textView.sizeThatFits(CGSize(width: size.width, height: .greatestFiniteMagnitude))
// Resize the cell only when cell's size is changed
if size.height != newSize.height {
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
let indexPath = IndexPath(row: 2, section: 0)
tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
}
}
Your TextView wont make your cell height bigger, since textView will grow in his content size. I mean it increase his scroll and not its height.
So if your textView does not increase his height, neither will your dynamic cell height.
Solution: use a label with number of row = 0
You can use PTSMessagingCell
Add PTSMessagingCell.h and PTSMessagingCell.m file in your resources.
#import "PTSMessagingCell.h" in your header file.
Then implement UITableViewDataSource method heightForRowAt as below:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
let textSize = PTSMessagingCell.messageSize(newsDetail.text)
return textSize.height + 2*PTSMessagingCell.textMarginVertical() + 5.0
}
I have a cell that contains a few stackviews, bottom stackView contains a textView and a custom separator.
I want to create an option, when user tap on cell, it shows whole text of tapped text view, so number of maximum lines in that cell is 0 and in other cells should be 3.
I used this tutorial http://www.roostersoftstudios.com/2011/04/14/iphone-uitableview-with-animated-expanding-cells/ and I modified it a little, my code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(iden_tableViewCell4) as! TableViewCell4
if let selectedCellIP = selectedIndexPath {
if indexPath == selectedCellIP {
cell.textTextVIew.textContainer.maximumNumberOfLines = 0
}
else {
cell.textTextVIew.textContainer.maximumNumberOfLines = textVIewMaxNumberOfLines
}
}
else {
cell.textTextVIew.textContainer.maximumNumberOfLines = textVIewMaxNumberOfLines
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
//The user is selecting the cell which is currently expanded
//we want to minimize it back
if selectedIndexPath == indexPath {
selectedIndexPath = nil
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
return
}
//First we check if a cell is already expanded.
//If it is we want to minimize make sure it is reloaded to minimize it back
if let selIndexPath = selectedIndexPath {
let previousPath = selIndexPath
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths([previousPath], withRowAnimation: .Fade)
}
//Finally set the selected index to the new selection and reload it to expand
selectedIndexPath = indexPath
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
in my viewDidLoad I set
tableView.estimatedRowHeight = 160
tableView.rowHeight = UITableViewAutomaticDimension
Expansion and contraction work well, but textView height of other cells has a strange behavior. When first cell is extended, an I scroll down, the last is extended too, and should not be.
Dynamically Resize UITableViewCell upon Selection
Playing with individual selection indexes is dangerous business. Not only are you likely to miss corner case conditions in didSelectRowAtIndexPath, it cannot possibly work for multiple selections.
You should split the cell expansion/compression notification into 2 distinct blocks (no need to keep track of selectedIndexPath, more robust code):
Expand
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView,
indexPath:indexPath)
}
Contract
override func tableView(_ tableView: UITableView,
didDeselectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView,
indexPath:indexPath)
}
Selection is destroyed by selectRowAtIndexPath
This prevents didDeselectRowAtIndexPath from ever being invoked.
A workaround is to cache the entire selection, not individual indexes, and to restore such selection after reload.
Complete code:
Notice when and how cacheSelectedRows is maintained. This uses a plain UITableViewCell. All it needs is a reuseIdentifier.
class TableViewController: UITableViewController {
var cacheSelectedRows:[IndexPath]? = nil
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 160
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
if let textLabel = cell.textLabel {
textLabel.backgroundColor = UIColor.clear
textLabel.numberOfLines = 1
if let cacheSelectedRows = cacheSelectedRows {
textLabel.numberOfLines = (cacheSelectedRows.contains(indexPath)) ? 0 : 1
}
textLabel.text = "\(1 + indexPath.row), Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView, indexPath:indexPath)
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
self.reloadRowsAtIndexPaths(tableView, indexPath:indexPath)
}
func reloadRowsAtIndexPaths(_ tableView: UITableView, indexPath: IndexPath) {
cacheSelectedRows = tableView.indexPathsForSelectedRows
tableView.reloadRows(at: [indexPath], with: .fade)
// Restore selection
if let cacheSelectedRows = cacheSelectedRows {
for path in cacheSelectedRows {
self.tableView.selectRow(at: path, animated: false, scrollPosition: .none)
}
}
}
}
Demo
► Find this solution on GitHub and additional details on Swift Recipes.
I didn't find answer, why the strange behavior of textView and cell height is happening, but I have solution for my problem.
So it looks like tableView.estimatedRowHeight = 160 and tableView.rowHeight = UITableViewAutomaticDimension don't respect textView's maximum number of lines for all cells. cellForRow works fine, it sets the limitation, but the rows height is sometimes expanded and sometimes contracted.
So I unwrapped textView and the bottom separator from StackView, replaced TextView with Label and set autolayaut constraints. Now it works fine, without strange behavior.
Is it possible to self-size a UITableViewCell in iOS 8 without creating a Custom UITableViewCell?
I thought that the standard UITableViewCell types (UITableViewCellStyleDefault, UITableViewCellStyleSubtitle, UITableViewCellStyleValue1, UITableViewCellStyleValue2) had built in auto layout constraints. This is confirmed by the fact that the constraints for non-custom cells cannot be changed in Storyboard.
But when I use a non-custom cell of type UITableViewCellStyleValue1, set it up in Storyboard, set numberOfRows for textLabel and detailTextLabel to 0, and set the viewDidLoad code as below, only the textLabel of the cell is accounted for in the autosizing of the height of the cell. If the detailTextLabel ends up displaying on more lines than the textLabel, the text for detailTextLabel spills out over the top and bottom edges of the cell. Again, the cell does resize properly for the textLabel, but seems to ignore the detailTextLabel in its resizing process.
The main thing I need to know is - do I need to create a custom cell even for the rows that can use a standard cell if I want to properly support Dynamic Text and Self-Sizing?
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView setEstimatedRowHeight:DEFAULT_ROW_HEIGHT];
[self.tableView setRowHeight:UITableViewAutomaticDimension];
}
I just tried this in iOS 10/XCode 8 (same results in iOS 9/XCode 7) with the different cell types and it looks like it's possible ONLY for the textLabel and not for the detailTextLabel.
(basically repeating the issue that the OP mentioned)
ViewController code that sets some text alternately on detailTextLabel and textLabel.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if indexPath.row % 2 == 0 {
cell.textLabel?.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
cell.detailTextLabel?.text = "<- textLabel"
} else {
cell.textLabel?.text = "detailTextLabel ->"
cell.detailTextLabel?.text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
}
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
Make sure you set the textLabel and textDetailLabel's line property to 0 and here are the results.
Basic Cell
Right Detail Cell
Left Detail Cell
Subtitle Cell
I'll report this as a bug.
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];