Auto layout multiple lines label in UITableview // Swift 4.2 - ios

I wrote such extension to display my data in UITableview. But sometimes my data can contain more than 1 line and I need to create something to display full content. How could I change my code (below) to do it?
extension ViewController: UITableViewDataSource, UITableViewDelegate {
// Define no of rows in your tableView
func tableView(_ chatHistoryTable: UITableView, numberOfRowsInSection section: Int) -> Int {
return messagesData.count
}
func tableView(_ chatHistoryTable: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = chatHistoryTable.dequeueReusableCell(withIdentifier: "userMessage")! as UITableViewCell
cell.textLabel!.text = messagesData[indexPath.row]
cell.textLabel!.textAlignment = .right
return cell;
}
}
I think that I should write something for UITableViewCell too, but I don't know, am I correct.
Please help me with this question.

in here you need to follow two changes.
initially set the estimate height and auntomaticdimension for Self Sizing Cell, on your page loads call the following line.
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableView.automaticDimension
for e.g
#IBOutlet weak var yourTableviewName: UITableView!{
didSet{
yourTableviewName.tableFooterView = UIView()
yourTableviewName.estimatedRowHeight = 44.0
yourTableviewName.rowHeight = UITableView.automaticDimension
}
}
secondary for your word wrap and numberOfLines for your lables. follow the below line on your cellforRow method
func tableView(_ chatHistoryTable: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = chatHistoryTable.dequeueReusableCell(withIdentifier: "userMessage")! as UITableViewCell
cell.textLabel!.numberOfLines = 0
cell.textLabel!.lineBreakMode = .byWordWrapping
cell.textLabel!.text = messagesData[indexPath.row]
cell.textLabel!.textAlignment = .right
return cell;
}
}

Step 1. Add a chain of unbroken vertical constraints in Custom Table View Cell
Step 2. Set the cell estimated height
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
Step 3:Make the label support multiple lines
textLabel.LineBreakMode = UILineBreakMode.WordWrap;
textLabel.Lines = 0;

Related

Self sizing tableview inside self sizing tableview cell

Let's say I have hierarchy like this:
*TableViewCell
**TableView
***TableViewCell
and all of them should be resizable. Did someone face this kind of problem? In past I've used many workarounds like systemLayoutSizeFitting or precalculation of height in heightForRowAt, but it always breaks some constraints, because TableViewCell has height constraint equal to estimated row height and there appear some kinds of magic behavior. Any ways to make this live?
Current workaround:
class SportCenterReviewsTableCell: UITableViewCell, MVVMView {
var tableView: SelfSizedTableView = {
let view = SelfSizedTableView(frame: .zero)
view.clipsToBounds = true
view.tableFooterView = UIView()
view.separatorStyle = .none
view.isScrollEnabled = false
view.showsVerticalScrollIndicator = false
view.estimatedRowHeight = 0
if #available(iOS 11.0, *) {
view.contentInsetAdjustmentBehavior = .never
} else {
// Fallback on earlier versions
}
return view
}()
private func markup() {
contentView.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(ReviewsTableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.snp.makeConstraints() { make in
make.top.equalTo(seeAllButton.snp.bottom).offset(12)
make.left.equalTo(contentView.snp.left)
make.right.equalTo(contentView.snp.right)
make.bottom.lessThanOrEqualTo(contentView.snp.bottom)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ReviewsTableViewCell
cell.viewModel = viewModel.cellViewModels[indexPath.row]
cell.setNeedsLayout()
cell.layoutIfNeeded()
let size = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .defaultLow)
return size.height
}
}
Self sizing tableView class:
class SelfSizedTableView: UITableView {
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
self.setNeedsLayout()
self.layoutIfNeeded()
return contentSize
}
}
This is actually not an answer to the question, but just an explanation.
(Wrote here because of the character count limitation for the comments).
The thing is that you're trying to insert a vertically scrollable view inside another vertically scrollable view. If you don't disable the nested tableview's scroll ability, you will have a glitch while scrolling, because the system wouldn't know to whom pass the scroll event (to the nested tableview, or to the parent tableview).
So in our case, you'll have to disable the "scrollable" property for the nested tableviews, hence you'll have to set the height of the nested tableview to be equal to its content size. But this way you will lose the advantages of tableview (i.e. cell reusing advantage) and it will be the same as using an actual UIScrollView. But, on the other hand, as you'll have to set the height to be equal to its content size, then there is no reason to use UIScrollView at all, you can add your nested cells to a UIStackView, and you tableview will have this hierarchy:
*TableView
**TableViewCell
***StackView
****Items
****Items
****Items
****Items
But again, the right solution is using multi-sectional tableview. Let your cells be section headers of the tableview, and let inner cells be the rows of the tableview.
here is an example of how to make a tableview inside a table view cell with automatic height for the cells.
You should use the 'ContentSizedTableView' class for the inner tableViews.
class ViewController: UIViewController {
#IBOutlet weak var outerTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
outerTableView.rowHeight = UITableView.automaticDimension
outerTableView.estimatedRowHeight = UITableView.automaticDimension
outerTableView.delegate = self
outerTableView.dataSource = self
}
}
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
sizeToFit()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? TableTableViewCell
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
Use xib files to simplify the hierarchy.
Get a tableView on your storyboard, and create a nib file for your tableViewCell(say CustomTableViewCell). Inside it create a tableView and again create one more tableViewCell xib file. Now, no need of setting labels into your xib file,(if you want only labels in cells and nothing else, if not, there is another way of adding constraints)
Say you have an array of text, some strings are long and some are short.
register nib file in CustomTableViewCell and extend it to use Delegate and DataSource.
register this CustomTableViewCell in ViewController.
While declaring a cell in CustomTableViewCell, just do=
cell.textLabel?.text = content
cell.textLabel?.numberOfLines = 0
Use heightForRowAt to set outer tableViewCell's height, and let the inner tableView to scroll inside.

Dynamic height for UITableViewCell not working correctly

I'm having some height problems with my dynamic UITableViewCell (sew picture below). Some cells have the correct height and some not, and when I drag the tableView some of the cells become correct and some don't.
I'm using this code to get the cell's height to be dynamic and reloading it in viewDidAppear and viewDidLoad.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = tableView.rowHeight
As mentioned the cells are sometimes correct and sometimes not. Is there another way to do it or am I doing something wrong? I have tried many different solutions, all mentioned here as well as other suggestions both here at StackOverflow and other sites.
I appreciate all help!
Edit!
TableView
extension ChatVC: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupMessages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "groupFeedCell", for: indexPath) as? GroupFeedCell else { return UITableViewCell() }
let message = groupMessages[indexPath.row]
DataService.instance.getUsername(forUID: message.senderId, handler: { (name) in
cell.configureCell(name: name, content: message.content)
})
cell.layoutSubviews()
cell.layoutIfNeeded()
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 66
}
}
Cell
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var contentLbl: UILabel!
func configureCell(name: String, content: String) {
self.nameLbl.text = name //email
self.contentLbl.text = content
}
For dynamic tableViewCell
1- Setup this 2 lines with an inital value for the row height to help autolayout drawing it (take it from current cell height in the nib file)
tableView.rowHeight = UITableViewAutomaticDimension;
tableView.estimatedRowHeight = number;
2- don't implement this function heightForRowAtIndexPath . or implement it and return this
return UITableViewDynamicHeight;
3- make sure all constraints in the cell nib file or in storyboard are hooked correctly from top to bottom.
4- in cellForRowAtIndexPath before the line retrun cell insert that
[cell layoutSubviews];
[cell layoutIfneeded];
5- Test in simulator some versions like ios 8 it's a bug also in the viewController call
[tableView LayouSubviews];
in viewdidLayoutSubViews function to re relayout again correctly
6- Make lines property of any UILabel that you want to wrap = 0 and hook it's leading and trailing constarints to superView
Your are facing this issue because the content of your label comes from an async function.
The cell uses its content to work out its height dynamically. When your async request returns it has already done its work and will not recalculate and resize.
You need to make these requests, cache/store the results and reload the cells as needed. Usually in chat there would only be a couple of users to load usernames for anyway. You could also try pre-loading this data before the chat is displayed.
You can quickly confirm this by creating an array of random usernames and messages (sample data) and adding that to the cell straight away.
I guess the problem is asynchronous function. So Should you try
Step 1: Create names array
var names: [String?] = Array<String>.init(repeating: nil, count: groupMessages.count)
Step 2: Replace
DataService.instance.getUsername(forUID: message.senderId, handler: { (name) in
cell.configureCell(name: name, content: message.content)
})
By
if names[indexPath.row] == nil {
DataService.instance.getUsername(forUID: message.senderId, handler: { (name) in
names[indexPath.row] = name
tableView.reloadRows(at: [indexPath], with: .automatic)
})
} else {
cell.configureCell(name: name, content: message.content)
}

In Tableview, I want to know how to i set multiline UILabel in cell last position

In UITableView, I want to know how to i set multilineUILabel (Needs to update dynamically.) in cell last position, I want to calculate for UILabel count and set multiline in tableview
#Ashu you can set tableView cell height dynamically for that particular cell.
override func viewDidLoad() {
self.tableView.estimatedRowHeight = 41.0
self.tableView.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == 0 {
return 100
} else {
return UITableViewAutomaticDimension
}
}
Something like code mentioned above.

How to adjust label height and width in custom tableView Cell

I have a expandable tableView, in which when i expand a section, than there are three cell. On firth Cell there is only name and in second cell. It have a big content. Now I want to auto adjust this label height and width according to content.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tblView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
let dataArrayTblView = dataArrayForTableView
let titleName = dataArrayTblView.valueForKey("name")
let dueDate = dataArrayTblView.valueForKey("deadlinedate")
let description = dataArrayTblView.valueForKey("description")
cell.titleLabel.text = titleName[indexPath.row] as AnyObject! as! String!
cell.dueDateLabel.text = dueDate[indexPath.row] as? String
cell.descriptionLabel.text = description[indexPath.row] as? String
cell.descriptionLabel.sizeToFit()
cell.textLabel?.backgroundColor = UIColor.clearColor()
cell.selectionStyle = .None
return cell
}
But not getting complete content
Try to set this. It will automatically adjust the height of the row for you. If it is not working, then you have something wrong with your constraints inside your storyboard.
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
You should use UITableViewAutomaticDimension as row height something like,
// this should be set in viewDidload
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
for that you must use autolayout and should set proper constraints to your label in cell.
Constraint should be in linear chain, I mean your label's top and bottom both constraint must be set and your label should resize according to content so your cell will resize accordingly!
You can refer Self-sizing Table View Cells by Raywenderlich !
Put below in viewDidLoad and set autolayout as per below screenshots.
tblview.rowHeight = UITableViewAutomaticDimension
tblview.estimatedRowHeight = 44
Screenshot 1
Screenshot 2
Screenshot 3
Screenshot 4
Use heightForRowAtIndexPath method to adjust height of row. Calculate
size of string with boundingRectWithSize this method. example:
Try This:
if let YOUR_STRING:NSString = str as NSString? {
let sizeOfString = ns_str.boundingRectWithSize(
CGSizeMake(self.titleLabel.frame.size.width, CGFloat.infinity),options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: lbl.font], context: nil).size
}

How to add spacing between UITableViewCells - Swift

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

Resources