I have a custom UITableViewCell with a UILabel called CellLabel. I want to be able to access this label at a certain tableView section e.g. section 3 and provide it a string to display without the use of an array and arrayName[indexPath.section].
You can catch it in cellForRowAtIndexPath override of a TableView
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("YourCustomCellRegisterName") as YourCustomCell
if(indexPath.section == 3){
cell.CellLabel.text = "foo"
}
return cell
}
My table view has some elements sorted by some types: TypeA, TypeB and TypeC.
I want that when I click on a cell with TypeA to change the selection color to Red, when I type on TypeB to change color to Blue and when pressing on TypeC to change color to Yellow.
Right now I came up with this code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
guard let mode = dataSource.selectedObject else {
fatalError("willDisplayCell, but no selected row?")
}
let type = ModeType(rawValue: mode.type)!
let selectionColor = UIView() as UIView
selectionColor.backgroundColor = type.color()
cell.selectedBackgroundView = selectionColor
}
My issue with this is that willDisplayCell is called when I start my app and my data source is empty so I get a fatal error.
How can I overcome this ? Maybe using a flag to do this only when didSelectRowAtIndexPath was called.
Or is there another way to achieve what I am after ?
I assume you have created custom UITableviewCell. Create a cell type enum.
enum CellType {
case RedCell
case Yellowcell
case OrangeCell
}
//Create enum property
class CustomCell : UITableViewCell {
var cellType:CellType = CellType.RedCell //Default is RedCell
}
Now you have to assign the cell type in your ViewController tableview datasource.
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell : UITableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell") as! CustomCell
cell.cellType = .RedCell //your choice
return cell
}
override func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.cellForRowAtIndexPath(indexPath)
switch(cell.cellType) {
//Handle Switch case
case .RedCell:
cell?.contentView.backgroundColor = UIColor.redColor()
cell?.backgroundColor = UIColor.redColor()
}
}
override func tableView(tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.cellForRowAtIndexPath(indexPath)
// Set unhighlighted color
cell?.contentView.backgroundColor = UIColor.blackColor()
cell?.backgroundColor = UIColor.blackColor()
}
EDIT:
If you have created 3 different types of cell class check tableview cell class type and change the color in didHighlightRowAtIndexPath method.
My issue with this is that willDisplayCell is called when I start my
app and my data source is empty so I get a fatal error.
tableView(_:willDisplayCell:forRowAtIndexPath:) will only be called if your data source tells the table view that there are rows to display. So the problem more likely is that your tableView(_:numberOfRowsInSection:) method is returning a number larger than zero when your data source is empty.
Also, your code looks like it expects tableView(_:willDisplayCell:forRowAtIndexPath:) to get called only for selected rows. It gets called for all displayed rows. But this method isn't necessary to affect the background color. In fact, it's rarely used in most apps. There are only a few edge cases where you need to mess with the cell just before it's displayed.
The proper way to set the selection background color is to create and assign a UIView to the cell's selectedBackgroundView property. You can do that either from the cell's subclass (preferred for complex cells) or from the tableView:cellForRowAtIndexPath: data source method:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCell")
cell!.textLabel?.text = "Kilroy was here."
cell!.selectedBackgroundView = UIView(frame: cell!.bounds)
cell!.selectedBackgroundView!.backgroundColor = .greenColor()
return cell!
}
I've been scouring the web trying to figure this out but can't find a clear answer. Here's what's going on:
I've created a UITableViewController called MainTableViewController and I have two custom cells that live in that: "FirstCell" and "SecondCell". They both use that naming convention as their reuse identifiers. I've also created cell class files for each: "FirstTableViewCell" and "SecondTableViewCell" that I've assigned each cell accordingly. I've also created IBOutlets for all the objects in the cells within each UITableViewCell class file.
So in MainTableViewController I have no problem getting "FirstCell" loaded. I'm doing that like so:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell") as! FirstTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
}
How do I now call "SecondCell"? I know I will need to change my return to 2. But then how do I change the dequeueReusableCellWithIdentifier and cellForRowAtIndexPath code to make both my cells work? "Second Cell" has a radically different layout with different objects defined.
try this
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourArrayCount
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row % 2 == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell") as! FirstTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("SecondCell") as! SecondTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
}
}
You just need to deque your second cell with the reuse identifier assigned. This is completely on your requirements when you want to use first and when second.
You should simply change the parameter value you send to dequeueReusableCellWithIdentifier based on [indexPath row]. If it's 0, use "firstCell" and if it's 1, use "secondCell" and create a cell for the matching object type.
here i see you have static text to show in the cell view. Usually we have NSArray of data to populate the data in tableview and we define the count in umberOfRowsInSection method
so it will be very easy if you know what data you want to display in which cell like that you can easily use If statement for selecting your desire table cellview
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayCount
}
after checking count you can do this by if statement
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
NSUInteger row = [indexPath row];
if [yourArray objectAtIndex:row] =="Michael" {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell") as! FirstTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("SecondCell") as! SecondTableViewCell
cell.friendfirstnameLabel.text = “Unknown"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
}
}
I am building an app that has a feed view for user-submitted posts. This view has a UITableView with a custom UITableViewCell implementation. Inside this cell, I have another UITableView for displaying comments. The gist is something like this:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
The initial feed will download with 3 comments for previewing, but if there are more comments, or if the user adds or deletes a comment, I want to update the PostCell in place inside of the feed table view by adding or removing CommentCells to the comments table inside of the PostCell. I am currently using the following helper to accomplish that:
// (PostCell.swift) Handle showing/hiding comments
func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) {
let table = self.superview?.superview as UITableView
// "table" is outer feed table
// self is the PostCell that is updating it's comments
// self.comments is UITableView for displaying comments inside of the PostCell
table.beginUpdates()
self.comments.beginUpdates()
// This function handles inserting/removing/reloading a range of comments
// so we build out an array of index paths for each row that needs updating
var indexPaths = [NSIndexPath]()
for var index = startRow; index <= endRow; index++ {
indexPaths.append(NSIndexPath(forRow: index, inSection: 0))
}
switch operation {
case .INSERT:
self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .DELETE:
self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .RELOAD:
self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
self.comments.endUpdates()
table.endUpdates()
// trigger a call to updateConstraints so that we can update the height constraint
// of the comments table to fit all of the comments
self.setNeedsUpdateConstraints()
}
override func updateConstraints() {
super.updateConstraints()
self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height
}
This accomplishes the update just fine. The post is updated in place with comments added or removed inside of the PostCell as expected. I am using auto sizing PostCells in the feed table. The comments table of the PostCell expands to show all of the comments, but the animation is a bit jerky and the table sort of scrolls up and down a dozen pixels or so while the cell update animation takes place.
The jumping during resizing is a bit annoying, but my main issue comes afterwards. Now if I scroll down in the feed, the scrolling is smooth as before, but if I scroll up above the cell I just resized after adding comments, the feed will jump backwards a few times before it reaches the top of the feed. I setup iOS8 auto sizing cells for the Feed like this:
// (FeedController.swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
If I remove the estimatedRowHeight, the table just scrolls to the top anytime a cell height changes. I'm feeling pretty stuck on this now and as a new iOS developer, could use any tips you might have.
Here is the best solution I found to solve this kind of problem (scrolling problem + reloadRows + iOS 8 UITableViewAutomaticDimension);
It consists by keeping every heights in a dictionary and updating them (in the dictionary) as the tableView will display the cell.
You will then return the saved height in - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath method.
You should implement something like this :
Objective-C
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
if(height) {
return height.floatValue;
} else {
return UITableViewAutomaticDimension;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = #(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
Swift 3
#IBOutlet var tableView : UITableView?
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
We had the same problem. It comes from a bad estimation of the cell height that causes the SDK to force a bad height which will cause the jumping of cells when scrolling back up. Depending on how you built your cell, the best way to fix this is to implement the UITableViewDelegate method - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
As long as your estimation is pretty close to the real value of the cell height, this will almost cancel the jumping and jerkiness. Here's how we implemented it, you'll get the logic:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// This method will get your cell identifier based on your data
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kFirstCellIdentifier])
return kFirstCellHeight;
else if ([cellType isEqualToString:kSecondCellIdentifier])
return kSecondCellHeight;
else if ([cellType isEqualToString:kThirdCellIdentifier])
return kThirdCellHeight;
else {
return UITableViewAutomaticDimension;
}
}
Added Swift 2 support
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// This method will get your cell identifier based on your data
let cellType = reuseIdentifierForIndexPath(indexPath)
if cellType == kFirstCellIdentifier
return kFirstCellHeight
else if cellType == kSecondCellIdentifier
return kSecondCellHeight
else if cellType == kThirdCellIdentifier
return kThirdCellHeight
else
return UITableViewAutomaticDimension
}
dosdos answer worked for me in Swift 2
Declare the ivar
var heightAtIndexPath = NSMutableDictionary()
in func viewDidLoad()
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Then add the following 2 methods:
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = self.heightAtIndexPath.objectForKey(indexPath)
if ((height) != nil) {
return CGFloat(height!.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let height = cell.frame.size.height
self.heightAtIndexPath.setObject(height, forKey: indexPath)
}
SWIFT 3:
var heightAtIndexPath = [IndexPath: CGFloat]()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightAtIndexPath[indexPath] = cell.frame.size.height
}
#dosdos solution is working fine
but there is something you should added
following #dosdos answer
Swift 3/4
#IBOutlet var tableView : UITableView!
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
then use this lines when ever you want , for me I use it inside textDidChange
first reload Tableview
update constraint
finally move to top Tableview
tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(CGPoint.zero, animated: true)
I was facing the same problem too. I did find a workaround, but it doesn't completely fix the jerk. But it seems to be a lot better compared to the previous choppy scrolling.
In your UITableView delegate method :cellForRowAtIndexPath:, try using the following two methods to update the constraints before returning the cell. (Swift language)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
EDIT: You may also have to play around with the tableView.estimatedRowHeight value to get a smoother scrolling.
Following #dosdos answer.
I also found interesting to implement: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Specially for my code, where the cell is changing Constraints dynamically while the cell is already displayed on screen. Updating the Dictionary like this helps the second time the cell is displayed.
var heightAtIndexPath = [NSIndexPath : NSNumber]()
....
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = UITableViewAutomaticDimension
....
extension TableViewViewController: UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = heightAtIndexPath[indexPath]
if let height = height {
return CGFloat(height)
}
else {
return UITableViewAutomaticDimension
}
}
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
}
I'm creating a UiTableView with 2 different cell nibs. it is working perfectly fine expect 1 specific scenario.
ISSUE:
On cell selection, i perform some action in didSelectRowAtIndexPath. After didSelectRowAtIndexPath execution, iOS invokes cellForRowAtIndexPath method and recreates that clicked cell.
What is the affects of recreation:
My cells heights are dynamic. When UiTableView recreates any cell from middle, it causes unnatural jerks on scrolling back/up.
Below is the code snippet of my methods. Any help would be appriciated :)
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var post = self.posts[indexPath.row]
if(post.valueForKey(MEDIA) != nil) {
var cell:PostImageTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("PicCell") as PostImageTableViewCell
cell.loadPostData(post)
return cell
} else {
var cell:PostTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as PostTableViewCell
cell.loadPostData(post)
return cell
}
}
Selection method:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.selectedRow = indexPath.row
self.performSegueWithIdentifier("detailSegue", sender: nil);
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
Call Stack: