Set tableview cell row height dynamically? - xcode

I have a tableview with a label and an image. in some cells, there is no image as i used imageView.removeFromSuperview() to remove it from the cell. When there is an image in the cell, the row height is 445 and "Custom" is checked off.
how can i set the row height dynamically according to how long the label is instead of how long/big the imageview is after i remove the imageview?

If you want dynamic row height, you can define your constraints (making sure they're unambiguous), set the label's numberOfLines to zero, and then in viewDidLoad, tell it that rows should automatically adjust their height:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
If you want to hide/show a UIImageView as well, I must confess that I'm not crazy about the removeFromSuperview approach (because when the cell is reused, you have to re-add the image view, and possibly rebuilding its constraints, too) there are a few options:
You could have a different cell prototype for the cell with the image view and another without the image view. Then cellForRowAtIndexPath just needs to instantiate the right cell.
You could go ahead and define a full set of constraints that are unambiguous for both the presence of the image and without the image. And then, you can activate the constraint and set the image view to hidden as appropriate:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! CustomCell
let image = ... // let's say it was an optional, set if needed, left `nil` if not
cell.customImageView?.image = image
if image == nil {
cell.customImageView.hidden = true
cell.imageBottomConstraint.active = false
cell.customLabel.text = ...
} else {
cell.customImageView.hidden = false
cell.imageBottomConstraint.active = true
}
return cell
}
The trick when having competing sets of constraints that dictate the height of the cell is to just make sure that they have different priorities and/or they use inequality so if both sets are in effect, you don't have an unsatisfiable conflict (e.g. the image view might have higher priority).
You can, if you want, go "old school" and programmatically determine the size of the label field, and then implement heightForRowAtIndexPath, but auto layout makes this process unnecessary.

You need to override this method:
Override func tableview(tableview:UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {}
Return the desired height for the indexPath you want in this method

If you're using UILabel, it has a frame with a height property.
And heightForRowAtIndexPath, should cover what you're wanting for the appropriate method to use:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableViewDelegate_Protocol/#//apple_ref/occ/intfm/UITableViewDelegate/tableView:heightForRowAtIndexPath:
not sure how your image is currently set up, but here's a rough example:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if image != nil {
return 445.0
else {
label.frame.height (or whichever value you'd prefer)
}
}

Related

Why the table cell default selection style is not returned? Swift 5.2

Why the default style for the selectionStyle property in the tableview cell is not returned? I wrote a condition to check and the print command works, i.e. it shows that default style is returning. But the table cell is not grayed out, as it should be with the default style.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
if cell?.selectionStyle == .default {
cell?.selectionStyle = .none
print("none")
} else {
cell?.selectionStyle = .default
print("default")
}
}
I found a solution to the problem. The fact is that the return of the style works, it just is not visible, because the change is very fast.
To see how the cell selection style changes (white color changes to gray and vice versa) you just need to click on the cell and hold it pressed for a while. And then you can see how the cell is grayed out.

NSTableView reloadData(forRowIndexes:columnIndexes:) breaks autolayout

I have an NSTableView that can swap in different cell views based on data values for the row. When the model changes, I reload the table, and the table's delegate will provide the right table cell view for the new data.
The table uses autolayout for its cell views. All cell views load normally initially. When updating the table after a model change, I get different results depending on whether I call reloadData() or reloadData(forRowIndexes:columnIndexes). When using reloadData(), the cell view is loaded and autolayout works fine. If I use reloadData(forRowIndexes:columnIndexes), autolayout produces completely different, unexpected results.
I created a sample project to demonstrate the problem.
Here is an image of the project setup including constraints set on the table cell views. There are two row templates, one with a blue view (even rows), one with green (odd rows) that should span the table width (minus a bit of padding). A controller supplies the cell views:
class TableController: NSObject {
#IBOutlet weak var tableView: NSTableView!
var colorData = [1, 0, 1, 0]
#IBAction func swapLine(_ sender: Any) {
colorData[1] = (colorData[1] + 1) % 2
// tableView.reloadData()
tableView.reloadData(forRowIndexes: [1], columnIndexes: [0])
}
}
extension TableController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return colorData.count
}
}
extension TableController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cellId = (colorData[row]) % 2 == 0 ? "EvenCell" : "OddCell"
return tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(cellId), owner: self)
}
}
A button in the interface just swaps the data for row 1 and reloads the data. The initial view looks like this (alternating green and blue rects). If you use reloadData(), it looks like this (row 1 changed from blue to green). But, if you use reloadData(withRowIndexes:columnIndexes:), the cell view shrinks to 40 points wide vice 480 as in the others. Here's a grab of the view debugger showing the cell view with the wrong size and showing ambiguous width constraints (this doesn't happen when using reloadData()).
The documentation mentions that the row view is reused with reloadData(forRowIndexes:columnIndexes:), but not with reloadData(), which I've verified. I imagine this reusing of the row view is what's causing the autolayout problems, but I can find no connection. Nothing found at SO, AppKit release notes, WWDC videos, Google searches or from pounding my head on the table. Would be truly grateful for assistance.
Update:
Here's the code for ColorView:
class ColorView: NSView {
#IBInspectable var intrinsicHeight: CGFloat = 20
#IBInspectable var color: NSColor = NSColor.blue
override var intrinsicContentSize: NSSize {
return NSSize(width: NSView.noIntrinsicMetric, height: intrinsicHeight)
}
override func draw(_ dirtyRect: NSRect) {
color.setFill()
dirtyRect.fill()
}
}
I think I've got it working. If I call layoutSubtreeIfNeeded() on the cell just before it is returned (so that all its subviews like the dynamic text are already set), then it seems to work.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
//...
cell.layoutSubtreeIfNeeded()
return cell
}
I hope that helps.
I ran into the same issue, and noticed the actual auto-layout constraints were missing for the rows that reloadData is called for. My (hacky) solution was to add the constraints that are supposed to be automatically set up for the cell manually as well. Note that in my table view I'm just using one column so I'm able to set the width constraint to equal the row's width instead of relying on the columns specified width.
class CustomRowView: NSTableRowView {
override func addSubview(_ view: NSView) {
super.addSubview(view)
// Add constraints NSTableView is supposed to set up
view.topAnchor.constraint(equalTo: topAnchor).isActive = true
view.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0).isActive = true
view.layoutSubtreeIfNeeded()
}
}

Autoresizing a tableview cell with a downloaded image and a multi line label

I have tableview cells with an image view and a label.
The images are downloaded from a URL. They can be landscape or portrait which will determine their height constraint later on.
The label can be multiple lines and its lines property is set to 0, and the imageview is set to Aspect fill, in the attributes inspector.
I want to vertically resize the cells to fit the image and the label.
In cellForRow if I set the imageview's height constraint to something small like 100 then the image view covers the label. If I set it to 300 then it partially covers the label. And if I set it to something really high like 10000 it stretches the image but the label is not covered at all.
Here is the code:
class ArtistListViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 250
}
}
extension ArtistListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ArtistTableViewCell
cell.imageHeightConstraint.constant = 100
let artist = artists[indexPath.row]
cell.descriptionLabel.text = artist.description
setupCellForImage(cell, artist: artist)
return cell
}
fileprivate func setupCellForImage(_ cell: ArtistTableViewCell, artist: Artist) {
let url = URL(string: artist.image)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
DispatchQueue.main.async(execute: {
cell.artistImageView.image = UIImage(data: data!)
})
}).resume()
}
}
ImageView constraints:
Top space to superview = 0
Bottom space to description label = 20
Height = 235
Label constraints:
Bottom space to superview = 8
I have not changed priority of content hugging or compression resistance.
Here are 2 images where the height constraint is set to 300. While the images are loading and once finished loading:
What is going on here? Why is the cell not resizing according to the image view's height?
In ImageView, Attribute Inspector you need to select Clip To Bounds . Then it will solve one part of your problem. Then you need to setup constraint correctly.

TableViewCell with UITextView not aligned and cutted until scolling

I've a TableViewCell with a UITextView, which content is not aligned and cutted at bottom at the first display:
When I scroll down and then up to the top, everything is fine:
My cellForRowAtIndexPath to get the content from a fetchedResultsController is simple:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("TextViewCell") as! TextViewCell
let data = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
let text = data.valueForKey("textDu")!.description
cell.textContentView.text = text
return cell
}
How can I get the result after scrolling after start???
Use sizeToFit() after adding content to your textContentView.
cell.textContentView.text = text
cell.textContentView.sizeToFit()
Make sure for sizing cell
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "onContentSizeChange:",
name: UIContentSizeCategoryDidChangeNotification,
object: nil)
tableView.estimatedRowHeight = 89
tableView.rowHeight = UITableViewAutomaticDimension
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func onContentSizeChange(notification: NSNotification) {
tableView.reloadData()
}
Hope it helps you.
In conjunction with #Ashish Kakkad's answer you may want to try to set heightDimensions in viewDidLoad or viewWillAppear:
yourTableView.estimatedRowHeight = 30.0 // Put a real estimate here
yourTableView.rowHeight = UITableViewAutomaticDimension
Use auto layout code to tie the bottom of the cells contentView to the bottom of the text box. When the text box resizes it'll expand the cell with it.
This is in addition to Asish's correct suggestion about automatic cell heights and is quite a high level suggestion as you need to do a few things to get auto layout working right in tableview cells but there's ample examples on that out on the web.
oh, oh. Think I found something. I removed the existing contraints and then I've tried to "add missing constaints". The result was thas the error "Failed to automatically update constraints". Seem's I've a problem with my storyboard-file...

swift UITableView set rowHeight

I am trying to set the height of each row in the tableView to the height of the corresponding cell with this code:
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
var cell = tableView.cellForRowAtIndexPath(indexPath)
return cell.frame.height
}
I get this error when initialising var cell :
Thread 1:EXC_BAD_ACCESS(code=2,address=0x306d2c)
For setting row height there is separate method:
For Swift 3
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0;//Choose your custom row height
}
Older Swift uses
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100.0;//Choose your custom row height
}
Otherwise you can set row height using:
self.tableView.rowHeight = 44.0
In ViewDidLoad.
Put the default rowHeight in viewDidLoad or awakeFromNib. As pointed out by Martin R., you cannot call cellForRowAtIndexPath from heightForRowAtIndexPath
self.tableView.rowHeight = 44.0
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var height:CGFloat = CGFloat()
if indexPath.row == 1 {
height = 150
}
else {
height = 50
}
return height
}
yourTableView.rowHeight = UITableViewAutomaticDimension
Try this.
As pointed out in comments, you cannot call cellForRowAtIndexPath inside heightForRowAtIndexPath.
What you can do is creating a template cell used to populate with your data and then compute its height.
This cell doesn't participate to the table rendering, and it can be reused to calculate the height of each table cell.
Briefly, it consists of configuring the template cell with the data you want to display, make it resize accordingly to the content, and then read its height.
I have taken this code from a project I am working on - unfortunately it's in Objective C, I don't think you will have problems translating to swift
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
static PostCommentCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tblComments dequeueReusableCellWithIdentifier:POST_COMMENT_CELL_IDENTIFIER];
});
sizingCell.comment = self.comments[indexPath.row];
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
Problem Cause:
The problem is that the cell has not been created yet. TableView first calculates the height for row and then populates the data for each row, so the rows array has not been created when heightForRow method gets called. So your app is trying to access a memory location which it does not have the permission to and therefor you get the EXC_BAD_ACCESS message.
How to achieve self sizing TableViewCell in UITableView:
Just set proper constraints for your views contained in TableViewCell's view in StoryBoard. Remember you shouldn't set height constraints to TableViewCell's root view, its height should be properly computable by the height of its subviews -- This is like what you do to set proper constraints for UIScrollView. This way your cells will get different heights according to their subviews. No additional action needed
Make sure Your TableView Delegate are working as well.
if not then
in your story board or in .xib
press and hold Control + right click on tableView drag and Drop to your Current ViewController.
swift 2.0
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 60.0;
}
There is no way to call tableView.dequeueReusableCell from within tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath).
So you have to compute the height from the data.
In my case that was not possible because I have a textView in the cell, which size I did not know.
So I come around with the following strategies:
Do not use tableView.dequeueReusableCell. Just use an array of cells yo have under full control. If your cell is not too large in memory and you don't have too much rows, this is the simplest strategy.
Use a dummy cell, configure it with your data and compute the size.
Try code like this copy and paste in the class
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}

Resources