I have a tableView, designed in storyboard, that mimics a chat UI. A cell consists of:
A TextView for the message text
A Profile Image of the sender
Right now, the profile image is displayed in every cell, next to the text bubble. This is fine, but if the same users send two or more messages directly after the other, the profile image should only appear on the last bubble and not on the previous one.
I tried calling cellForRowAtIndexPath to get the previous cell's properties and change the hidden property of the profile image, but this gave me two problems:
I'm calling cellForRowAtIndexPath inside cellForRowAtIndexPath, because that's where I make the cell UI and decide wether the profile image has to be hidden or not. I don't think it's a good idea to call this Method inside itself.
Sometimes (when scrolling up and down very fast) this does not work properly.
I also tried to store all the cells in an dictionary (indexPath.row: Cell), so I can access it faster later, but this gave me the same problem namely that it does not work when scrolling up and down really fast.
This is an illustration of how it should be: http://tinypic.com/view.php?pic=2qavj9w&s=8#.Vfcpi7yJfzI
You need to both look ahead inside of your cellForRowAtIndexPath method and, as Paulw11 recommended, call reloadRowsAtIndexPaths after inserting the cell:
import UIKit
struct MyMessage {
let sender: String
let text: String
}
class MyTableViewCell: UITableViewCell {
var message: MyMessage?
var showProfileImage: Bool = false
}
class MyTableViewController: UITableViewController {
private var _messages: [MyMessage] = []
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self._messages.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let message = self._messages[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! MyTableViewCell
cell.message = message
if self._messages.count > indexPath.row + 1 {
let nextMessage = self._messages[indexPath.row + 1]
cell.showProfileImage = message.sender != nextMessage.sender
} else {
cell.showProfileImage = true
}
return cell
}
func addMessage(message: MyMessage) {
let lastIndexPath = NSIndexPath(forRow: self._messages.count - 1, inSection: 0)
let indexPath = NSIndexPath(forRow: self._messages.count, inSection: 0)
self._messages.append(message)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Bottom)
self.tableView.reloadRowsAtIndexPaths([lastIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}
}
Related
I have a UITableView for an app that I'm working on, its connected to navigation controllers and other views that allow the user to add animals to the tableview. This is done via another tableViewController that loads the data into the animal table when the user taps done (an unwind segue). It should all be very simple and the sample data that I set up look like this:
However after the add data process (done while running the app), the test data looks like:
I've managed to get around a few layout problems by writing small functions like this one for cell height:
override func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100 //Whatever fits your need for that cell
}
Any ideas on how to get the table view to display all information with the same layout no matter where the data implemented came from (sample or added within app)?
My cellForRowAtIndexPath ('CatCell' is the table cell, 'Cat' is the name and breed structure):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CatCell", forIndexPath: indexPath)
as! CatCell
let cat = cats[indexPath.row] as Cat
cell.cat = cat
return cell
}
The UITableViewController is made up of:
The tableView is updated like so after the user taps done:
#IBAction func doneToMyCats(segue:UIStoryboardSegue) {
if let catDetailsViewController = segue.sourceViewController as? CatDetailsViewController {
//add the new cat to the cats array
if let cat = catDetailsViewController.cat {
cats.append(cat)
//update the tableView
let indexPath = NSIndexPath(forRow: cats.count-1, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
}
}
So try maybe instead of inserting your cell like you wrote use tableView reload data method:
#IBAction func doneToMyCats(segue:UIStoryboardSegue) {
if let catDetailsViewController = segue.sourceViewController as? CatDetailsViewController {
//add the new cat to the cats array
if let cat = catDetailsViewController.cat {
cats.append(cat)
//update the tableView
tableView.reloadData()
}
}
}
I'm not very experienced in Swift. I have a tableView and custom cells in it, where are several labels, UISlider and UISwitch.
When i change slider values and hit submit(bar button item), I want to collect UISlider and UISwitch values from all cells.
What i tried:
1.Tags: I reached some cells, but stopped and could not reach currently invisible cells, and finally read some opinions, that tags are unlikely to use.
Question: Are there any clear pro et contra?
2.CellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
return cell
}
#IBAction func submitTapped(sender: AnyObject) {
let cell = tableView(self.tableView , cellForRowAtIndexPath: NSIndexPath(forRow: 1, inSection: 0)) as! CustomTableViewCell
print(cell.Label1.text) // gives me
print(cell.Label2.text) // values
print(cell.customSlider.value) // gives me the value stated as
print(cell.customSwitch.on) // default
}
Do i understand correctly, that i call cellForRowAtIndexPath here and no wonder i get new instance of Custom Cell (processed by function)?
3."Wag the dog"
Unfortunately i've lost a SO link to discussion of this solution :(
I tried to reach UIViewController using .superview.superview..., but Xcode refused to eat 4 superviews (and i was not sure that i found correct number of .superviews).
Main idea is to give access to UIViewController property in Custom Cell:
add a property in CustomTableViewCell:
class CustomTableViewCell: UITableViewCell {
var viewController : MyViewController?
var cellNo = 0
//and so on
}
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var sliderValues: [Float] = []
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
self.sliderValues.append (0.0) // just to be sure that each slider can put it's value to Array
cell.cellNo = indexPath.row // so the Custom Cell knows it's No
//---------------------//
cell.viewController = self
//---------------------//
return cell
}
}
//----------------------
//and back to Custom Cell
#IBAction func sliderValueChanged(sender: AnyObject) {
self.viewController?.sliderValues[self.cellNo] = self.customSlider.value
}
// and the same way with UISwitch
Good News, This works!
Question: are there any methods not to "wag the dog" and reach Custom Cell from UIViewController?
Looking at your solution I think you will have an issue because the cell is holding onto a reference to your viewController, so it will lead to memory leaks. You should use "weak var viewController : MyViewController?" if you were going to go down this route
However, as matt has said, you should not do this. It is better to update your model with this data. You might be able to pass the data directly to the cell to modify the data, but I do not know the format of your data, so another idea is you could create a delegate to pass the values back from the cell. An example is:
protocol CustomTableViewCellDelegate: class {
func didChangeSlider(value: Float, cellNo: Int)
//func didSwitchOn(value: Bool, cellNo: Int)
}
You would then add this to your cell, like this:
class CustomTableViewCell: UITableViewCell {
weak var delegate: CustomTableViewCellDelegate?
var cellNo = 0
//and so on
}
Then use the delegate here:
#IBAction func sliderValueChanged(sender: AnyObject) {
self.delegate?.didChangeSlider(self.customSlider.value, cellNo)
}
Finally in your ViewController when you create the cell, you need to do this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomTableViewCell") as! CustomTableViewCell
cell.Label1.text = "Long Tongue, size \(indexPath.row) cm"
cell.Label2.text = "Big Banana, size \(indexPath.row) inches"
cell.delegate = self
cell.cellNo = indexPath.row
return cell
}
Add at the end of your ViewController, add the delegate handler:
extension MyViewController: CustomTableViewCellDelegate {
func didChangeSlider(value: Float, cellNo: Int) {
//Save your value here
}
}
I have a UITableView in my ViewController.
One of the cell could be tap into another TableViewController to allow select a value.
I want to update my cell after back from the callee ViewController.
right now, i could pass back the selected value by delegate.
However, i tried following way, none of them works.
self.mainTable.reloadData()
dispatch_async(dispatch_get_main_queue()) {
self.mainTable.reloadData()
}
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
self.mainTable.endUpdates()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
was called and executed without error.
but the UI just doesn't change
here is the way I update value in cellForRowAtIndexPath
if let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell! {
currentCell.textLabel?.text = address
return currentCell
}
Here is my cellForRowAtIndexPath -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let id = "Cell"
println(indexPath)
if indexPath.row == 1 {
var cell = tableView.dequeueReusableCellWithIdentifier(id) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: id)
cell?.contentMode = UIViewContentMode.Center
cell?.selectionStyle = UITableViewCellSelectionStyle.None
cell?.contentView.addSubview(mapView!)
}
return cell!
}else{
let cell = UITableViewCell()
cell.textLabel?.text = self.address
return cell
}
}
Here is the delegate method -
func passBackSelectedAddress(address: String) {
self.address = address
var indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.mainTable.endUpdates()
}
My fix:
After more debug, i find the cause,
the self.address value is updated in delegate, however it roll back to previous value in cellForRowAtIndexPath.
I change the property to a static property, then resolve the problem.
I'm not sure what's wrong with instance property, and why it reverses back.
static var _address:String = ""
It seems like you're trying to grab a cell from the UITableView and then update the textLabel value that way. However, UITableView and UITableViewCell are not meant to be updated in this way. Instead, store the value of address in your class and update this value when the delegate calls back into your class. If cellForRowAtIndexPath constructs the UITableViewCell with the value of self.address, calling mainTable.reloadData() after should update the cell to the new value.
For example:
var address: String
func delegateCompleted(address: String) {
self.address = address
self.mainTable.reloadData()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(<your identifier>)
if (indexPath == <your address cell indexPath>) {
let textLabel = <get your textLabel from the cell>
textLabel?.text = self.address
}
return cell
}
Your cellForRowAtIndexPath has some problems -
You are using the same re-use identifier for different types of cell (one with a map, one without)
When you allocate the table view cell for the other row, you don't include the re-use identifier.
You have no way of referring to the map view that you are adding after the method exits because you don't keep a reference.
If you are using a storyboard then you should create the appropriate prototype cells and subclass(es) and assign the relevant cell reuse ids. If you aren't then I suggest you create a cell subclass and register the classes against the reuse identifiers. Your cellForRowAtIndexPath will then look something like -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var returnCell:UITableViewCell
if indexPath.row == 1 {
var myMapCell = tableView.dequeueReusableCellWithIdentifier("mapCell", forIndexPath:indexPath) as MYMapCell
myMapCell.contentMode = UIViewContentMode.Center
myMapCell.selectionStyle = UITableViewCellSelectionStyle.None
// Set the properties for a map view in the cell rather than assigning adding an existing map view
returnCell=myMapCell
}else{
returnCell = tableView.dequeueReusableCellWithIdentifier("addressCell", forIndexPath:indexPath)
returnCell.textLabel?.text = self.address
}
return returnCell;
}
I have a problem which i'm not sure how to solve.
I have two custom cell nibs - data for both is fetched from separate arrays.
The structure is the following
nib1-cell line1
nib1-cell line2
...
nib1-cell line n
nib2-cell line1
there is always one nib2-cell at the end with the uibutton.
Once the uibutton is pressed - the nib1 array is appended.
I figured out a way how to insert values at the bottom of the tableview, but when i scroll downwards or upwards the cell with nib2 is reused and replaced with nib1-cell data.
How can i either prevent those cells from being reused or save their state ?
Thank you.
UPDATE: datasource and cellForRowAtIndexPath code
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return someTagsArray.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(indexPath.row < someTagsArray.count - 1){
var cell:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
cell.lblCarName.text = someTagsArray[indexPath.row]
return cell
} else if (indexPath.row == someTagsArray.count - 1){
var celle:vwAnswers = self.tableView.dequeueReusableCellWithIdentifier("cell2") as! vwAnswers
celle.Answer1.setTitle(answersdict[answersdict.endIndex - 2], forState:UIControlState.Normal)
answertitle1 = "\(celle.Answer1.currentTitle!)"
celle.Answer2.setTitle(answersdict.last, forState:UIControlState.Normal)
answertitle2 = "\(celle.Answer2.currentTitle!)"
//println(answertitle2)
return celle
} else {
var cell2:TblCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TblCell
return cell2
}
}
You have to determine which type of cell you want in cellForRowAtIndexPath and dequeue the correct reusable cell. Maybe something like if (indexPath.row + 1)%3 == 0 then dequeue an answer cell.
However, you may possibly want to look in to using a section header for this instead. Hard to say without seeing how you implement your data source.
I have been trying to implement a feature in my app so that when a user taps a cell in my table view, the cell expands downwards to reveal notes. I have found plenty of examples of this in Objective-C but I am yet to find any for Swift.
This example seems perfect: Accordion table cell - How to dynamically expand/contract uitableviewcell?
I had an attempt at translating it to Swift:
var selectedRowIndex = NSIndexPath()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRowIndex = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedRowIndex == selectedRowIndex.row && indexPath.row == selectedRowIndex.row {
return 100
}
return 70
}
However this just seems to crash the app.
Any ideas?
Edit:
Here is my cellForRowAtIndexPath code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
if tableView == self.searchDisplayController?.searchResultsTableView {
cell.paymentNameLabel.text = (searchResults.objectAtIndex(indexPath.row)) as? String
//println(searchResults.objectAtIndex(indexPath.row))
var indexValue = names.indexOfObject(searchResults.objectAtIndex(indexPath.row))
cell.costLabel.text = (values.objectAtIndex(indexValue)) as? String
cell.dateLabel.text = (dates.objectAtIndex(indexValue)) as? String
if images.objectAtIndex(indexValue) as NSObject == 0 {
cell.paymentArrowImage.hidden = false
cell.creditArrowImage.hidden = true
} else if images.objectAtIndex(indexValue) as NSObject == 1 {
cell.creditArrowImage.hidden = false
cell.paymentArrowImage.hidden = true
}
} else {
cell.paymentNameLabel.text = (names.objectAtIndex(indexPath.row)) as? String
cell.costLabel.text = (values.objectAtIndex(indexPath.row)) as? String
cell.dateLabel.text = (dates.objectAtIndex(indexPath.row)) as? String
if images.objectAtIndex(indexPath.row) as NSObject == 0 {
cell.paymentArrowImage.hidden = false
cell.creditArrowImage.hidden = true
} else if images.objectAtIndex(indexPath.row) as NSObject == 1 {
cell.creditArrowImage.hidden = false
cell.paymentArrowImage.hidden = true
}
}
return cell
}
Here are the outlet settings:
It took me quite a lot of hours to get this to work. Below is how I solved it.
PS: the problem with #rdelmar's code is that he assumes you only have one section in your table, so he's only comparing the indexPath.row. If you have more than one section (or if you want to already account for expanding the code later) you should compare the whole index, like so:
1) You need a variable to tell which row is selected. I see you already did that, but you'll need to return the variable to a consistent "nothing selected" state (for when the user closes all cells). I believe the best way to do this is via an optional:
var selectedIndexPath: NSIndexPath? = nil
2) You need to identify when the user selects a cell. didSelectRowAtIndexPath is the obvious choice. You need to account for three possible outcomes:
the user is tapping on a cell and another cell is expanded
the user is tapping on a cell and no cell is expanded
the user is tapping on a cell that is already expanded
For each case we check if the selectedIndexPath is equal to nil (no cell expanded), equal to the indexPath of the tapped row (same cell already expanded) or different from the indexPath (another cell is expanded). We adjust the selectedIndexPath accordingly. This variable will be used to check the right rowHeight for each row. You mentioned in comments that didSelectRowAtIndexPath "didn't seem to be called". Are you using a println() and checking the console to see if it was called? I included one in the code below.
PS: this doesn't work using tableView.rowHeight because, apparently, rowHeight is checked only once by Swift before updating ALL rows in the tableView.
Last but not least, I use reloadRowsAtIndexPath to reload only the needed rows. But, also, because I know it will redraw the table, relayout when necessary and even animate the changes. Note the [indexPath] is between brackets because this method asks for an Array of NSIndexPath:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("didSelectRowAtIndexPath was called")
var cell = tableView.cellForRowAtIndexPath(indexPath) as! MyCustomTableViewCell
switch selectedIndexPath {
case nil:
selectedIndexPath = indexPath
default:
if selectedIndexPath! == indexPath {
selectedIndexPath = nil
} else {
selectedIndexPath = indexPath
}
}
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
}
3) Third and final step, Swift needs to know when to pass each value to the cell height. We do a similar check here, with if/else. I know you can made the code much shorter, but I'm typing everything out so other people can understand it easily, too:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let smallHeight: CGFloat = 70.0
let expandedHeight: CGFloat = 100.0
let ip = indexPath
if selectedIndexPath != nil {
if ip == selectedIndexPath! {
return expandedHeight
} else {
return smallHeight
}
} else {
return smallHeight
}
}
Now, some notes on your code which might be the cause of your problems, if the above doesn't solve it:
var cell:CustomTransactionTableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell
I don't know if that's the problem, but self shouldn't be necessary, since you're probably putting this code in your (Custom)TableViewController. Also, instead of specifying your variable type, you can trust Swift's inference if you correctly force-cast the cell from the dequeue. That force casting is the as! in the code below:
var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier" forIndexPath: indexPath) as! CustomTransactionTableViewCell
However, you ABSOLUTELY need to set that identifier. Go to your storyboard, select the tableView that has the cell you need, for the subclass of TableViewCell you need (probably CustomTransactionTableViewCell, in your case). Now select the cell in the TableView (check that you selected the right element. It's best to open the document outline via Editor > Show Document Outline). With the cell selected, go to the Attributes Inspector on the right and type in the Identifier name.
You can also try commenting out the cell.selectionStyle = UITableViewCellSelectionStyle.None to check if that's blocking the selection in any way (this way the cells will change color when tapped if they become selected).
Good Luck, mate.
The first comparison in your if statement can never be true because you're comparing an indexPath to an integer. You should also initialize the selectedRowIndex variable with a row value that can't be in the table, like -1, so nothing will be expanded when the table first loads.
var selectedRowIndex: NSIndexPath = NSIndexPath(forRow: -1, inSection: 0)
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex.row {
return 100
}
return 70
}
Swift 4.2 var selectedRowIndex: NSIndexPath = NSIndexPath(row: -1, section: 0)
I suggest solving this with modyfing height layout constraint
class ExpandableCell: UITableViewCell {
#IBOutlet weak var img: UIImageView!
#IBOutlet weak var imgHeightConstraint: NSLayoutConstraint!
var isExpanded:Bool = false
{
didSet
{
if !isExpanded {
self.imgHeightConstraint.constant = 0.0
} else {
self.imgHeightConstraint.constant = 128.0
}
}
}
}
Then, inside ViewController:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.estimatedRowHeight = 2.0
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.tableFooterView = UIView()
}
// TableView DataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ExpandableCell = tableView.dequeueReusableCell(withIdentifier: "ExpandableCell") as! ExpandableCell
cell.img.image = UIImage(named: indexPath.row.description)
cell.isExpanded = false
return cell
}
// TableView Delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
UIView.animate(withDuration: 0.3, animations: {
tableView.beginUpdates()
cell.isExpanded = !cell.isExpanded
tableView.scrollToRow(at: indexPath, at: UITableViewScrollPosition.top, animated: true)
tableView.endUpdates()
})
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell
else { return }
UIView.animate(withDuration: 0.3, animations: {
tableView.beginUpdates()
cell.isExpanded = false
tableView.endUpdates()
})
}
}
Full tutorial available here
A different approach would be to push a new view controller within the navigation stack and use the transition for the expanding effect. The benefits would be SoC (separation of concerns). Example Swift 2.0 projects for both patterns.
https://github.com/justinmfischer/SwiftyExpandingCells
https://github.com/justinmfischer/SwiftyAccordionCells
After getting the index path in didSelectRowAtIndexPath just reload the cell with following method
reloadCellsAtIndexpath
and in heightForRowAtIndexPathMethod check following condition
if selectedIndexPath != nil && selectedIndexPath == indexPath {
return yourExpandedCellHieght
}