I am using Xcode 7 and I am trying to set the height of a UITableViewCell in the storyboard settings to have a different cell height for different devices (eg. normal and compact x regular).
I cannot find a place for these settings. Is this only possible by doing it programmatically?
Click on Table View after that click on Size Inspector
and adjust your cell row height here -
Add this below code in your controller class viewDidLoad
tableView.estimatedRowHeight = 100.0 // Adjust Primary table height
tableView.rowHeight = UITableViewAutomaticDimension //This line adjust cell height according to containts
Swift 4 or later
You need to change from UITableViewAutomaticDimension to UITableView.automaticDimension
tableView.estimatedRowHeight = 100.0 // Adjust Primary table height
tableView.rowHeight = UITableView.automaticDimension
Go to the storyboard, find you table view and select the cell. You will notice a white square at the bottom middle of the cell. You can drag it to change the cell height.
You can set UITableView height in this way.Try this code
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath(NSIndexPath *)indexPath
{
if UIDevice.currentDevice().userInterfaceIdiom == .Phone && ScreenSize.SCREEN_MAX_LENGTH < 568.0
{
return 97
}
if UIDevice.currentDevice().userInterfaceIdiom == .Phone && ScreenSize.SCREEN_MAX_LENGTH == 568.0
{
return 115
}
if UIDevice.currentDevice().userInterfaceIdiom == .Phone && ScreenSize.SCREEN_MAX_LENGTH == 667.0
{
return 135
}
if UIDevice.currentDevice().userInterfaceIdiom == .Phone && ScreenSize.SCREEN_MAX_LENGTH == 736.0
{
return 150
}
return 90
}
You can set height from user interface and to reflect your set value you need to override table view heightForRowAt method e.g.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 73
}
Related
I am trying to show items in NSTableView but one of them (the item that previously was activated by an action (its name is stored in alreadyActivatedItem variable)) should be disabled and shown with a red text.
So far I managed to make disabling work properly.
I just cannot manage colouring the already activated item be red text. My code below will colour ALL cells' text in red.
extension PreferencesViewController: NSTableViewDelegate {
// disable selecting the already activated item
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
return !(myArray[row].name == alreadyActivatedItem)
}
// colouring the already activated item in red (it is also disabled)
func tableView(_ tableView: NSTableView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, row: Int) {
guard let c = cell as? NSTextFieldCell else {
return
}
if c.stringValue == alreadyActivatedItem {
c.textColor = .red
}
}
}
I also tried an other way:
// colouring the already activated item in red (it is also disabled)
func tableView(_ tableView: NSTableView, willDisplayCell cell: Any, for tableColumn: NSTableColumn?, row: Int) {
guard let c = tableColumn?.dataCell(forRow: row) as? NSTextFieldCell else {
return
}
if c.stringValue == alreadyActivatedRow {
c.textColor = .red
}
}
In both cases I will have all the rows with red text:
see as all items are red text
While debugging, I can see that:
let c = cell as? NSTextFieldCell seems to get the current row's cell, at least I get back the row's stringValue correctly with c.stringValue
if c.stringValue == alreadyActivatedRow seems to work good, at least it only steps inside if the condition is true.
So why still do all the items get red colour?
How to achieve my goal then?
(Xcode 11.3.1, Swift 5.1.3)
Cells are reused. You have to add an else clause to set the color always to a defined state.
if c.stringValue == alreadyActivatedItem {
c.textColor = .red
} else {
c.textColor = .black
}
Or simpler
c.textColor = c.stringValue == alreadyActivatedItem ? .red : .black
I recommend even a view based table view and Cocoa Bindings.
I have an NSCollectionView which consists of dozens of rows of single-columned items (chat messages) in a messaging application.
Each item contains a text area of which the heights vary. Therefore the view should be defaulted to the bottom when the view is created and scrolled to the bottom when new messages are received.
I am struggling to either default the scroll to the bottom or work out how to get the height of the CollectionView's contents to scroll to the bottom.
Any help would be greatly appreciated, thanks!
If you support only macOS 10.11 and up, and if you use a NSCollectionViewLayout, you could ask the layouter for the size of the entire content. That's also how the scrollbars are getting sized.
So, in ObjC, you'd simply ask your collectionView for its layouter's content size and scroll to the bottom:
NSSize contentSize = theCollectionView.collectionViewLayout.collectionViewContentSize.height;
[theCollectionView.enclosingScrollView.contentView scrollPoint:NSMakePoint(0, contentSize.height)];
hi i suggest you to work on Offset for horizontal UICollectionView and Apple provided a method for bottom for vertical UICollectionView
UICollectionView Horizontal Next and Previous Item
extension UICollectionView {
func scrollToNextItem() {
let contentOffset = CGFloat(floor(self.contentOffset.x + self.bounds.size.width))
self.moveToFrame(contentOffset: contentOffset)
}
func scrollToPreviousItem() {
let contentOffset = CGFloat(floor(self.contentOffset.x - self.bounds.size.width))
self.moveToFrame(contentOffset: contentOffset)
}
func moveToFrame(contentOffset : CGFloat) {
let frame: CGRect = CGRect(x: contentOffset, y: self.contentOffset.y , width: self.frame.width, height: self.frame.height)
self.scrollRectToVisible(frame, animated: true)
}
}
UICollectionView Vertical Scroll to Bottom
extension UICollectionView {
func scrollToBottom(animated: Bool) {
let sections = self.numberOfSections
if sections > 0 {
let rows = self.numberOfItems(inSection: sections - 1)
let last = IndexPath(row: rows - 1, section: sections - 1)
DispatchQueue.main.async {
self.scrollToItem(at: last, at: .bottom, animated: animated)
}
}
}
}
UITableView Scroll to Bottom
extension UITableView {
func scrollToBottom(animated: Bool) {
let sections = self.numberOfSections
if sections > 0 {
let rows = self.numberOfRows(inSection: sections - 1)
let last = IndexPath(row: rows - 1, section: sections - 1)
DispatchQueue.main.async {
self.scrollToRow(at: last, at: .bottom, animated: animated)
}
}
}
}
Usage
self.collectionView.scrollToBottom(animated: true)
self.tableView.scrollToBottom(animated: true)
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()
}
}
I have a label and an image.
if image != nil {
cell height = 445
} else {
//how do i set the cell height according to the label?
}
You should set the height of tableView cells in heightForRowAt. This function returns the height of the cell for a given indexPath (as you probably have guessed). Inside there you can use return label.frame.height
In Swift 3:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if image != nil {
return 445
} else {
return label.frame.height
}
}
Edit: You can't use cellForRowAt until cells are initialized in cellForRowAt. So Try this instead. It assumes the image and label are #IBOutlets
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
}