I need to increase UITableView height based on UITableViewCell content size.
I'm using Custom Google Auto Complete. I have an UITextField. When I enter a letter in that UITextField it will call shouldChangeCharactersIn range delegate method.
Inside that method I will send dynamic request to Google AutoComplete API to get predictions result.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if tableView.numberOfRows(inSection: 0) > 0
{
let newLength = (textField.text?.characters.count)! + string.characters.count - range.length
let enteredString = (textField.text! as NSString).replacingCharacters(in: range, with:string)
if newLength == 0 {
tableView.isHidden = true
}
if newLength > 0
{
address.removeAll()
self.tableView.isHidden = false
GetMethod("https://maps.googleapis.com/maps/api/place/autocomplete/json?input=\(enteredString)&key=MYAPIKEY", completion: { (resultDict) in
let resultArray = resultDict.object(forKey: "predictions")! as! NSArray
print(resultArray.count)
for temp in resultArray
{
let newtemp = temp as! NSDictionary
let tempAdd = newtemp.value(forKey:"description") as! String
let placeId = newtemp.value(forKey:"place_id") as! String
var dict = [String : AnyObject]()
dict["address"] = tempAdd as AnyObject?
dict["latlong"] = placeId as AnyObject?
self.address.append(dict as AnyObject)
print(newtemp.value(forKey:"description"))
print(self.address.count)
self.tableView.reloadData()
}
})
}
return true
}
After I will store all address to Address array dynamically, I need to increase UITableView height based on that incoming address content.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "TableViewCell"
var cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
if cell == nil
{
cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
}
let addresstoDisp = address[indexPath.row] as! NSDictionary
let name = addresstoDisp.value(forKey: "address")
cell?.textLabel?.numberOfLines = 0
cell?.translatesAutoresizingMaskIntoConstraints = false
cell?.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell?.textLabel?.textAlignment = NSTextAlignment.center
cell?.textLabel?.text = name as! String
return cell!
}
UITableViewCell height is increasing perfectly. Also I need to increase tableView height.
Add these lines after your cells are created. Because it returns 0 height in viewDidLoad()
var frame = tableView.frame
frame.size.height = tableView.contentSize.height
tableView.frame = frame
UPDATE
//After Tableviews data source values are assigned
tableView.reloadData()
tableView.layoutIfNeeded()
tableView.heightAnchor.constraint(equalToConstant: tableView.contentSize.height).isActive = true
The below code worked for me with UITableViewCell who has AutomaticDimension.
Create an outlet for tableViewHeight.
#IBOutlet weak var tableViewHeight: NSLayoutConstraint!
var tableViewHeight: CGFloat = 0 // Dynamically calcualted height of TableView.
For the dynamic height of the cell, I used the below code:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.estimatedRowHeight
}
For height of the TableView, with dynamic heights of the TableView Cells:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
print(cell.frame.size.height, self.tableViewHeight)
self.tableViewHeight += cell.frame.size.height
tableViewBillsHeight.constant = self.tableViewHeight
}
Explanation:
After the TableView cell is created, we fetch the frame height of the cell that is about to Display and add the height of the cell to the main TableView.
In your viewDidLoad write:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myTable.reloadData()
myTable.layoutIfNeeded()
}
Now override the viewDidLayoutSubviews method to give the tableview explicit height constraint:
override func viewDidLayoutSubviews() {
myTable.heightAnchor.constraint(equalToConstant:
myTable.contentSize.height).isActive = true
}
This makes sure that the tableview is loaded and any constraints related layout adjustments are done.
Without setting and resetting a height constraint you can resize a table view based on its content like so:
class DynamicHeightTableView: UITableView {
override open var intrinsicContentSize: CGSize {
return contentSize
}
}
I have been struggling with scroll view that must increase height when table view cells are loaded and also table view shouldn't be scrollable as it should display all cells (scroll is handled by scroll view). Anyway, you should use KeyValueObserver. First you create outlet for height constraint:
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
Then you add observation for table view:
private var tableViewKVO: NSKeyValueObservation?
After that, just add table view to observation and change height constraint size as your content size changes.
self.tableViewKVO = tableView.observe(\.contentSize, changeHandler: { [weak self] (tableView, _) in
self?.tableViewHeightConstraint.constant = tableView.contentSize.height
})
this is what works for me, very simple straight forward solution:
Create a new UIElement of the TableView height constraint and connecting it to the view
#IBOutlet weak var tableViewHeightConst: NSLayoutConstraint!
Then add the following wherever you are creating your cells, in my case I was using RxSwift
if self.tableView.numberOfRows(inSection: 0) > 0 {
//gets total number of rows
let rows = self.tableView.numberOfRows(inSection: 0)
//Get cell desired height
var cellHeight = 60
self.tableViewHeightConst.constant = CGFloat(rows * cellHeight)
}
this should do the trick.
Related
In my code, I'm adding a subview to a subview inside each cell. Each nested subview can be of various sizes. The nested subview is not causing the cell to increase its height, so the subview is getting cut off. How can I get the cell to increase in height based on the nested subview?
import WSTagsField
class Search {
#IBOutlet weak var searchResultsTableView: UITableView!
override func viewDidLoad()
{
super.viewDidLoad()
searchResultsTableView.rowHeight = UITableView.automaticDimension
searchResultsTableView.estimatedRowHeight = 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var availableTagsString = ""
var matchingAvailableTagsWithSearch = [String]()
let availableTagsArray = documentKeysWithinRadius[indexPath.row]["available_tags"] as! [String]
for i in 0...availableTagsArray.count - 1
{
if searchTags.contains(availableTagsArray[i])
{
availableTagsString += "\(availableTagsArray[i]) "
matchingAvailableTagsWithSearch.append(availableTagsArray[i])
}
}
availableTagsString = availableTagsString.trimmingCharacters(in: .whitespacesAndNewlines)
let cell : MyCustomCell = self.searchResultsTableView.dequeueReusableCell(withIdentifier: "cell") as! MyCustomCell
let tagsField = MyFunctions().createTagsField(fontSize: 14.0)
//add the view only tags
if (matchingAvailableTagsWithSearch.count > 0)
{
for i in 0...matchingAvailableTagsWithSearch.count - 1
{
tagsField.addTag(matchingAvailableTagsWithSearch[i])
}
}
tagsField.readOnly = true
tagsField.frame = cell.tableCellTagsView.bounds
//tableCellTagsView is a UIView in the prototype cell
cell.tableCellTagsView.addSubview(tagsField)
return cell
}
}
Constraints
To resize a tableView you need to set constraints for any view added , I think the best way is to use a vertical stackView for that by hooking it's top , left , bottom and right constraints and use
cell.stackTags.addArranagedSubview(tagsField)
and give it a height constraint
tagsField.heightAnchor.constraint(equalToConstant:50).isActive = true
If the element you add is a UILabel/UIButton that has an intrinsic content size then no need for the height constraint
Also this
tagsField.frame = cell.tableCellTagsView.bounds
won't correctly get the real bounds as it's not yet known , plus frame-layout won't resize the cell
Plus to clear anything either by implement prepareForReuse inside the cell subclass , or remove all previously added subviews with
cell.stackTags.subviews.forEach { $0.removeFromsuperview() }
after this line
let cell:MyCustomCell = self.searchResultsTableView.dequeueReusableCell(withIdentifier: "cell") as! MyCustomCell
With no need for :MyCustomCell
I have a custom subclass of UITableViewController. It has one section containing many rows. Each row corresponds to the same custom table view cell class. Each custom cell has two labels: myLabel1 & myLabel2, both subviews of the cell's contentView.
Every myLabel1 has one line of text, and every myLabel2 has one or two lines of text, but every cell should have the same height, as if every myLabel2 has two lines of text.
The labels use Dynamic Type.
myLabel1.font = UIFont.preferredFont(forTextStyle: .headline)
myLabel2.font = UIFont.preferredFont(forTextStyle: .subheadline)
According to Working with Self-Sizing Table View Cells, I've positioned each label with Auto Layout and "set the table view’s rowHeight property to UITableViewAutomaticDimension" so that the row height changes with Dynamic Type.
How do I make every cell have the same height?
How should I estimate the table view's row height?
UITableViewAutomaticDimension will estimate the cell size depending on cells content size. We must calculate the max height that a cell could possibly have, and return this height for all cells instead of using UITableViewAutomaticDimension.
CustomTableViewCell
let kMargin: CGFloat = 8.0
class CustomTableViewCell: UITableViewCell {
#IBOutlet var singleLineLabel: UILabel!
#IBOutlet var doubleLineLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
updateFonts()
}
override func prepareForReuse() {
updateFonts()
}
func updateFonts() {
singleLineLabel.font = UIFont.preferredFont(forTextStyle:.title3)
doubleLineLabel.font = UIFont.preferredFont(forTextStyle:.body)
}
func updateCellForCellWidth(_ width:CGFloat) {
doubleLineLabel.preferredMaxLayoutWidth = width - (2*kMargin)
}
func fillCellWith(_ firstString: String, _ secondString: String) {
singleLineLabel.text = firstString
doubleLineLabel.text = secondString
}
}
On View Controller
Setting up a dummy cell and listing to notifications for dynamic type
var heightCalculatorDummyCell: CustomTableViewCell!
var maxHeight: CGFloat = 0.0
override func viewDidLoad() {
super.viewDidLoad()
heightCalculatorDummyCell = tableView.dequeueReusableCell(withIdentifier: "cell_id") as! CustomTableViewCell
maxHeight = getMaxHeight()
NotificationCenter.default.addObserver(self, selector: #selector(AutomaticHeightTableViewController.didChangePreferredContentSize), name: .UIContentSizeCategoryDidChange, object:nil)
}
Getting max height using a dummy cell.
func getMaxHeight() -> CGFloat {
heightCalculatorDummyCell.updateFonts()
heightCalculatorDummyCell.fillCellWith("Title","A string that needs more than two lines. A string that needs more than two lines. A string that needs more than two lines. A string that needs more than two lines. A string that needs more than two lines.")
heightCalculatorDummyCell.updateCellForCellWidth(tableView.frame.size.width)
let size = heightCalculatorDummyCell.contentView.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
return (size.height + 1)
}
Handling Table view reloads on notification
deinit {
NotificationCenter.default.removeObserver(self)
}
func didChangePreferredContentSize() {
maxHeight = getMaxHeight()
tableView.reloadData()
}
Final step, returning max heights in tableview delegate
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titles.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return maxHeight
}
Make myLabel2 a FixedHeightLabel so that its height is always two lines.
class FixedHeightLabel: TopAlignedLabel {
override var intrinsicContentSize: CGSize {
let oldText = text
text = "\n"
let height = sizeThatFits(CGSize(width: .max, height: .max)).height
text = oldText
return CGSize(width: UIViewNoIntrinsicMetric, height: height)
}
}
class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
super.drawText(in: textRect)
}
}
I have a TableViewController, inside the TableViewCell, I have a UIWebView. I want the UIWebView to display some content from the internet, but I don't want the scroll effect, I want the WebView to have a dynamic height based on the length of the content. In addition, I want the TableViewCell to be able to adjust its cell height dynamically based on the dynamic height of WebView. Is this possible?
This is how I implemented my TableViewController:
class DetailTableViewController: UITableViewController {
var passPost: Posts = Posts()
var author: Author = Author()
override func viewDidLoad() {
super.viewDidLoad()
getAuthor()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("detailCell")
let postImageUrlString = passPost.postThumbnailUrlString
let postImageUrl = NSURL(string: postImageUrlString)
let size = CGSize(width: 414.0, height:212.0 )
let filter = AspectScaledToFillSizeFilter(size: size)
(cell?.contentView.viewWithTag(1) as! UIImageView).af_setImageWithURL(postImageUrl!, filter: filter)
//Set Author Avatar
let authorAvatarUrlString = author.authorAvatarUrlString
let authorAvatarUrl = NSURL(string: authorAvatarUrlString)
//Mark - Give Author Avatar a Round Corner
let filter2 = AspectScaledToFillSizeWithRoundedCornersFilter(size: (cell?.contentView.viewWithTag(2) as! UIImageView).frame.size, radius: 20.0)
(cell?.contentView.viewWithTag(2) as! UIImageView).af_setImageWithURL(authorAvatarUrl!, filter: filter2)
//Set Post Title and Content and so on
(cell?.contentView.viewWithTag(4) as! UILabel).text = passPost.postTitle
(cell?.contentView.viewWithTag(6) as! UIWebView).loadHTMLString("\(passPost.postContent)", baseURL: nil)
for the heightForCellAtIndexPath I did
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as! tableViewCell
tableView.rowHeight = cell.theWebView.scrollView.contentSize.height
return tableView.rowHeight
}
This is working fine, except the WebView has a scroll effect, and the height is limited due to the limitation of the TableViewCell. So, how to achieve what I need?
You need to set the height of your cell in the UITableViewDelegate method
tableView(_:heightForRowAt:)
You would do all your calculations on each individual cell height in this method and return it. If you want to display the UIWebView in it's whole without the need to scroll, you should return the height of the UIWebView's scroll view contentView – plus any height for anything else you might want to display in this cell.
Follow the steps:
1) set leading, trailing, top, bottom constraint 0(zero) from webview to tableviewCell
2) Now no need to call HeightForRow method of tableView.
3) in webview delegate method
func webViewDidFinishLoad(webView: UIWebView)
{
var frame = cell.webView.frame
frame.size.height = 1
let fittingSize = cell.webView.sizeThatFits(CGSizeZero)
frame.size = fittingSize
}
4) webview scrollenabled = false
You can return, in heightForRowAtIndexPath:
tableView.rowHeight = UITableViewAutomaticDimension
You also need to have a value setted in estimatedRowHeight, like
tableView.estimatedRowHeight = 85.0
If you have all the constraints defined correctly in the Storyboard (Or programmatically), you shouldn't get any error.
Reference: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithSelf-SizingTableViewCells.html
How can I make the TableViewCell change height to make the UILabel fit?
I am not using auto layout in my project, and because this is a big project I am not going to change to that either - so I need a fix that works without auto layout.
This is my CommentsViewController.swift code:
import UIKit
import Parse
import ActiveLabel
class CommentsViewController: UITableViewController, UITextFieldDelegate {
var commentsArray: [String] = []
var currentObjID = ""
#IBOutlet var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.textField.delegate = self
queryComments()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func queryComments(){
self.commentsArray.removeAll()
let query = PFQuery(className:"currentUploads")
query.whereKey("objectId", equalTo: self.currentObjID)
query.findObjectsInBackgroundWithBlock { (objects:[PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects {
for object in objects {
let list: AnyObject? = object.objectForKey("comments")
self.commentsArray = list! as! NSArray as! [String]
self.tableView.reloadData()
self.textField.text = ""
}
}
} else {
print("\(error?.userInfo)")
}
}
self.sendButton.enabled = true
self.refreshButton.enabled = true
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return commentsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! TableViewCell;
if self.commentsArray.count > indexPath.row{
cell.commentsText.font = UIFont.systemFontOfSize(15.0)
cell.commentsText.text = commentsArray[commentsArray.count - 1 - indexPath.row]
cell.commentsText.numberOfLines = 0
}
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
let height:CGFloat = self.calculateHeightForString(commentsArray[indexPath.row])
return height + 70.0
}
func calculateHeightForString(inString:String) -> CGFloat
{
let messageString = inString
let attributes = [NSFontAttributeName: UIFont.systemFontOfSize(15.0)]
let attrString:NSAttributedString? = NSAttributedString(string: messageString, attributes: attributes)
let rect:CGRect = attrString!.boundingRectWithSize(CGSizeMake(300.0,CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, context:nil )//hear u will get nearer height not the exact value
let requredSize:CGRect = rect
return requredSize.height //to include button's in your tableview
}
}
Screenshot:
This makes all the cells very big, even the cells that only has 1 line. Any ideas?
Im not 100% sure without Autolayout but you could set the estimated row height along with dimension. So in your viewDidLoad enter this
self.tableView.estimatedRowHeight = //Largest Cell Height
self.tableView.rowHeight = UITableViewAutomaticDimension
Can you tell what happens when you remove the heightForRowAtIndexPath method and add this in your viewDidLoad:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 80
With these two lines we instruct the tableview to calculate the cell's size matching its content and render it dynamically.
EDIT: I just read you don't want to use Auto Layout. I don't think if this'll still work in that case.
You can use heightForRowAtIndexPath to edit a table cell's height. This is a delegate method you'll be able to use after subclassing and setting your tableview's delegate property (IBoutlet or view.delegate = self)
See this thread if you don't already know your label's height: how to give dynamic height to UIlabel programatically in swift?
The way this works is you'll give a height for every index path row (ideally out of some collection - array). As your table loads cells it will automatically adjust for you.
I'm having a problem with my TableViewCell
I have two type of cell in my storyboard.
when i scroll, the text overlaps in some cells. I Try everything but I do not know how else to do. thank you very much for the help
public func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var storeNew = systemsBlog.getStore(listNews[indexPath.row].getIdStore())
var newNotice = listNews[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("TimelineCell", forIndexPath: indexPath) as? TimelineCell
cell!.nameLabel.text = storeNew.getName()
cell!.postLabel?.text = newNotice.getText()
cell!.postLabel?.numberOfLines = 0
cell!.dateLabel.text = newNotice.getDate()
cell!.typeImageView?.tag = indexPath.row;
return cell!
}
class TimelineCell : UITableViewCell {
#IBOutlet var nameLabel : UILabel!
#IBOutlet var postLabel : UILabel?
#IBOutlet var dateLabel : UILabel!
override func awakeFromNib() {
postLabel?.font = UIFont(name: "Roboto-Thin", size: 14)
}
override func layoutSubviews() {
super.layoutSubviews()
}
I can fix the problem. In the storyboard, the label have unchacked "Clears Graphics Context". I checked and for now it solved! Thanks for the help!
I had a similar issue with one of my UITableViews in the past. There are a bunch of things that could cause this, but maybe it is the same thing that happened to me.
I see that you are using a custom tableViewCell. What could be happening is when you set the text of the cell, it adds a label view with that text. Now say you scroll through the tableview and that cell disappears. If you were to reuse that cell and you did not remove the label from the subview, or set the text of that label again to the desired text, you will be reusing the tableviewcell with a previous label on it and adding a new label with new text to it, overlapping the text.
My suggestion would be to make sure you do not keep adding UIlabels as subviews in the TimelineCell class unless no label exists. if a label exists edit the text of that label not of the cell.
As per apple documentation:
The table view’s data source implementation of
tableView:cellForRowAtIndexPath: should always reset all content when
reusing a cell.
It seems that your problem is that you not always setting postLabel, causing it to write on top of the other cells, try this:
//reuse postLabel and set to blank it no value is returned by the function
let cell = tableView.dequeueReusableCellWithIdentifier("TimelineCell", forIndexPath: indexPath) as? TimelineCell
cell!.nameLabel.text = storeNew.getName()
if newNotice.getText() != nil{
cell!.postLabel.text = newNotice.getText()
} else {cell!.postLabel.text = ''}
cell!.postLabel.numberOfLines = 0
cell!.dateLabel.text = newNotice.getDate()
cell!.typeImageView?.tag = indexPath.row;
return cell!
}
//Make postLabel mandatory and set the font details in the xcode
class TimelineCell : UITableViewCell {
#IBOutlet var nameLabel : UILabel!
#IBOutlet var postLabel : UILabel!
#IBOutlet var dateLabel : UILabel!
override func awakeFromNib() {
//set this in xcode
}
override func layoutSubviews() {
super.layoutSubviews()
}
Also be sure that you are not creating any UI element and appending to the cell, as if you are you need to dispose it before you recycle the cell.
You can try setting:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 44.0 // Set this value as a good estimation according to your cells
}
In the View Controller containing your tableView.
Make sure the layout constraints in your TimelineCell define a clear line of height constraints
Another option is responding to:
tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 44.0 // Your height depending on the indexPath
}
Always in the ViewController that contains your tableView and, I assume, is the tableView's UITableViewDelegate
Hope this helps
Set cell to nil that will fix some error.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? ImageCellTableViewCell
cell = nil
if cell == nil {
cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for:indexPath) as? ImageCellTableViewCell
}
cell.yourcoustomtextTextLabel.text = "this is text"
cell.yourcoustomtextImageView.image = image
return cell
}