Expandable and Collapsable TableViewCells - swift

I am trying to do the following logic: if cell1 is tapped, it should expand its height. Then, if cell2 is tapped, it should expand its height and return cell1 to its original height so that only one cell can be expanded.
This is my current code but it expands all the cells and is not doing what I want:
#IBOutlet weak var poemasTableView: UITableView!
var isExpanded = true
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpanded == true {
return 55
} else {
return 240
}
}
My TableViewCell has a button which I am adding this function:
#objc func cambiarTamaño() {
poemasTableView.beginUpdates()
isExpanded = !isExpanded
poemasTableView.endUpdates()
}
How could i fix this?

You need to keep track of which cell is selected, then in heightForRowAt check to see if that cell is the selected one.
var selected: IndexPath?
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == selected?.row {
return 240
} else {
return 55
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selected == indexPath {
selected = nil
} else {
selected = indexPath
}
tableView.reloadData()
}
Looks like you want to use a button in the table view cell to do this, so you will need to pass in the indexPath into the cell, then when that button is pressed, pass the indexPath back to the table view class to update the selected variable.

Related

How can I call the action of a checkbox button when the cell is touched?

I'm using a custom cell which contains a label and a button. In my button, i've an action associated with it i.e when the button is touched the button changes its image and changed to checked image. But i want that action to be call when entire cell is touched.
extension HostOptionViewController: UITableViewDataSource, UITableViewDelegate{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Question.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let question = Question[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionOptionCell") as! ViewCellTableViewCell
cell.setText(option: question)
arrayOfCells.append(cell)
return cell
}
In my Custom cell
import UIKit
class ViewCellTableViewCell: UITableViewCell {
#IBOutlet weak var DataCell: UILabel!
#IBOutlet weak var checkBox: UIButton!
#IBAction func CheckBoxFunc(_ sender: Any) {
if checkBox.isSelected{
checkBox.isSelected = false
}
else{
checkBox.isSelected = true
}
}
func setText(option: Data){
DataCell.text = option.data
}
}
I want to call "CheckBoxFunc" when the cell is touched.
If you want the CheckBoxFunc to be called when selecting cell, then you should call it in didSelectRowAtIndexPath like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get your Custom cell here using dequeueReusableCell
// Call the CheckBoxFunc
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell: ViewCellTableViewCell = tableView.cellForRow(at: indexPath)! as! ViewCellTableViewCell
selectedCell.checkBox.addTarget(self, action: #selector(TableViewController.checkBoxTapped(_:)), for: .touchUpInside)
}
func checkBoxTapped(sender:UIButton) {
let question = Question[sender.tag]
print(question)
}
Don’t forget to add tag for checkbox button in cellForRowIndex method .

How to create dynamic height for TableView cell textview using Swift?

I am creating single Tableview custom cell row with top and bottom two textview. Here, bottom textview need to adjust height dynamically based on textview text length.
My Storyboard
enter image description here
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 280.0
}
//Choose your custom row height
private func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 280.0;
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.sourceTextView.text = “ABCD….”
return cell
}
Just do this
func adjustUITextViewHeight(arg : UITextView)
{
arg.translatesAutoresizingMaskIntoConstraints = true
arg.sizeToFit()
arg.scrollEnabled = false
}
In Swift 4 the syntax of arg.scrollEnabled = false has changed to
arg.isScrollEnabled = false.

Change label text when height expand of cell of tableView in swift

I am new in IOS and i am using swift.I want to change the text of label when height of cell expand.Please tell me how to change text when height expand of cell in swift.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
var selectedIndex = -1
var height = 54
// Data model: These strings will be the data for the table view cells
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
// cell reuse id (cells that scroll out of view can be reused)
let cellReuseIdentifier = "cell"
// don't forget to hook this up from the storyboard
#IBOutlet var tableView: UITableView!
override func viewDidLoad()
{
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return 2
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
// create a new cell if needed or reuse an old one
let cell:Cell1 = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! Cell1!
cell.textLabel?.text = "-"
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell:Cell1 = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! Cell1!
if(selectedIndex == indexPath.row)
{
height = 216
cell.textLabel?.text = "+"
}else{
height = 54
cell.textLabel?.text = "/"
}
return CGFloat(height)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
print("You tapped cell number \(indexPath.row).")
if(selectedIndex == indexPath.row)
{
selectedIndex = -1
}
else
{
selectedIndex = indexPath.row
}
}
}
In your code you are trying to dequeue the cell again in your heightForRowAt function, I think that's your issue.
so try replace your heightForRowAt function with the below code.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = tableView.cellForRow(at: indexPath)
if(selectedIndex == indexPath.row)
{
height = 216
cell.textLabel?.text = "+"
}else{
height = 54
cell.textLabel?.text = "/"
}
return CGFloat(height)
}

tableview persisting cell image after reloading

I have a tableview with cells that when selected displays an image within the selected cell, this image then disappears when the cell is selected again, and so on. When i press a submit button the selected cells are remembered and the tableview reloads with new data. But when doing this all the new data loads but the selected cell image persists. I have tried calling tableView.reloadData() on the main queue but it still persists. The image also persists when i press the submit button several times.
Heres my code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return currentQuestion.answers.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return tableView.frame.height/CGFloat(currentQuestion.answers.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
setSelectedArray()
let cell: AnswersTableViewCell = tableView.dequeueReusableCell(withIdentifier: "answersTableViewCell") as! AnswersTableViewCell
let text = currentQuestion.answers[indexPath.row]
let isAnAnswer = currentQuestion.answerKeys[indexPath.row]
cell.answerTextLabel.text = text
cell.answerView.backgroundColor = UIColor.white.withAlphaComponent(0.5)
cell.contentView.sendSubview(toBack: cell.answerView)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell: AnswersTableViewCell = tableView.cellForRow(at: indexPath) as? AnswersTableViewCell {
if cell.answerImageView.image == nil {
cell.answerImageView.image = UIImage(named: "paw.png")
selected[indexPath.row] = true
} else {
cell.answerImageView.image = nil
selected[indexPath.row] = false
}
}
}
#IBAction func submitButtonWasPressed() {
if questionNumber < questions.count - 1 {
questionNumber += 1
setCurrentQuestion()
self.answersTableView.reloadData()
self.view.setNeedsDisplay()
}
}
Any help would be great. Thanks
You need to set the image back to its correct value in cellForRow. Cells in your table are reused between calls to reloadData, and since you're not touching the imageView, it's keeping its previous value. Looks to me like you want:
cell.answerImageView.image = selected[indexPath.row] ? UIImage(named: "paw.png") : nil
inside of tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath).

Expand and contract tableview cell when tapped, in swift

I'm trying to expand a tableview cell when tapped. Then, when it is tapped again, I want it to go back to its original state.
If cellA is expanded and cellB is tapped, I want cellA to contract and cellB to expand at the same time. So that only one cell can be in its expanded state in any given moment.
My current code:
class MasterViewController: UITableViewController {
var isCellTapped = false
var currentRow = -1
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRowIndex = indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex.row {
if isCellTapped == false {
isCellTapped = true
return 140
} else if isCellTapped == true {
isCellTapped = false
return 70
}
}
return 70
}
Current code works well when:
You tap a row (it expands)
You tap it again (it contracts)
It fails when:
You tap a row (it expands)
You tap another row (it contracts, but the other row does not expand)
How can I solve this?
You need to take in account that you need to update your selected row when another is tapped, see the following code :
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex {
return 140
}
return 44
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedRowIndex != indexPath.row {
// paint the last cell tapped to white again
self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: self.selectedRowIndex, inSection: 0))?.backgroundColor = UIColor.whiteColor()
// save the selected index
self.selectedRowIndex = indexPath.row
// paint the selected cell to gray
self.tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.grayColor()
// update the height for all the cells
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
EDIT:
To handle that the cell where is selected and is tapped again return to its original state you need to check some conditions like the following:
var thereIsCellTapped = false
var selectedRowIndex = -1
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex && thereIsCellTapped {
return 140
}
return 44
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.grayColor()
// avoid paint the cell is the index is outside the bounds
if self.selectedRowIndex != -1 {
self.tableView.cellForRowAtIndexPath(NSIndexPath(forRow: self.selectedRowIndex, inSection: 0))?.backgroundColor = UIColor.whiteColor()
}
if selectedRowIndex != indexPath.row {
self.thereIsCellTapped = true
self.selectedRowIndex = indexPath.row
}
else {
// there is no cell selected anymore
self.thereIsCellTapped = false
self.selectedRowIndex = -1
}
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
With the above modifications in yourdidSelectRowAtIndexPath and heightForRowAtIndexPath functions you can see when a cell is tapped its background color it will be changed to gray when it's height grow and when another cell is tapped the cell is painted to white and the tapped to gray and again and again allowing only tap one cell at time.
I though you can benefit and learn how to do a Accordion Menu in this repo I have created and I plan to update very soon to handle better results, it's handle using a UITableView just like you want.
Any doubt in the repository you can post it here.
I hope this help you.
A simple approach for expanding only one cell at a time:
Swift 3
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
var selectedRowIndex = -1
#IBOutlet weak var tableView: UITableView!
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.row == selectedRowIndex {
return 90 //Expanded
}
return 40 //Not expanded
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedRowIndex == indexPath.row {
selectedRowIndex = -1
} else {
selectedRowIndex = indexPath.row
}
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
This code will expand the clicked cell and close previously open cell with animation.
UITableViewCell:
import UIKit
protocol HierarchyTableViewCellDelegate {
func didTapOnMoreButton(cell:HierarchyTableViewCell)
}
class HierarchyTableViewCell: UITableViewCell {
#IBOutlet weak var viewBtn: UIButton!
//MARK:- CONSTANT(S)
var delegate:HierarchyTableViewCellDelegate!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func actionViewButtonTapped(_ sender: Any) {
delegate.didTapOnMoreButton(cell: self)
}
}
UIViewController:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableViewObj: UITableView!
var HierarchyListArr = NSArray()
var selectedIndex:Int?
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return HierarchyListArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewObj.dequeueReusableCell(withIdentifier: "HierarchyTableViewCell", for: indexPath) as! HierarchyTableViewCell
cell.delegate = self
if let index = selectedIndex,indexPath.row == index{
cell.viewBtn.isSelected = true
}else{
cell.viewBtn.isSelected = false
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let index = selectedIndex,indexPath.row == index{
return 190
}
return 80
}
}
extension ViewController: HierarchyTableViewCellDelegate{
func didTapOnMoreButton(cell: HierarchyTableViewCell) {
guard let index = tableViewObj.indexPath(for: cell)else{return}
if selectedIndex == index.row{
selectedIndex = -1
}else{
selectedIndex = index.row
}
tableViewObj.reloadRows(at: [index], with: .fade)
}
}