Expand and contract tableview cell when tapped, in swift - 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)
}
}

Related

Disable users to scroll up in tableView?

Similar to tinder, if a user goes down, I don't want them to be able to go back upwards. How do I do this?
I tried the code below, but it keeps running an error: Index out of range.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! FeedTableViewCell
cell.collectionView.delegate = self
cell.collectionView.dataSource = self
//reloading the tableview
let artist: UserModel
artist = self.userList[indexPath.row]
cell.nameLabel.text = artist.name
cell.passionLabel.text = artist.passion
cell.collectionView.reloadData()
if indexPath.row == 1 {
userList.removeFirst()
}
return cell
}
Refer below code,
You need to store last scroll Content Offset of tableview, when user scroll down, refer scrollViewDidScroll method.
#IBOutlet weak var tableView: UITableView!
var lastContentOffsetY:CGFloat = 0.0
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.bounces = false
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuse")
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuse")
return cell!
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if(scrollView.contentOffset.y < self.lastContentOffsetY){
self.tableView.setContentOffset(CGPoint(x:scrollView.contentOffset.x, y: self.lastContentOffsetY), animated: false)
}else{
self.lastContentOffsetY = scrollView.contentOffset.y
}
}

Expandable and Collapsable TableViewCells

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.

Multiple prototype cells in UITableView

So I am trying to set up my UITableViewController that has a headerview, footerview, and then multiple prototype cells in between them. When I go to run my code on my simulator, only the hearderview and footerview are displaying and the prototype cells are not. Here is my code:
import UIKit
class UploadTrackTableViewController: UITableViewController {
var headerView: UploadHeader!
var footerView: UploadFooter!
override func viewDidLoad() {
super.viewDidLoad()
headerView = tableView.tableHeaderView as! UploadHeader
footerView = tableView.tableFooterView as! UploadFooter
}
}
extension UploadTrackTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 4
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else if section == 1 {
return 1
} else if section == 2 {
return 1
} else {
return 1
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "trackTitleCell", for: indexPath) as! TrackTitleTableViewCell
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "genreCell", for: indexPath) as! SelectGenreTableViewCell
return cell
} else if indexPath.section == 2 {
let cell = tableView.dequeueReusableCell(withIdentifier: "featuredArtistCell", for: indexPath) as! AddFeaturedArtistTableViewCell
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "audioCell", for: indexPath) as! SelectAudioFileTableViewCell
return cell
}
}
}
And here is a screenshot of how my controller looks on interface builder.
And here is a screenshot of the simulator.
Not sure if setting this up as a tableview is the best option maybe I should go for a scrollview with this UI but I am not sure what would work best.
here is what the view controller looks like now.
You can use only 1 section and UITableView has delegate functions of
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return 'your custom view'
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
return 'your custom view'
}
You can return you own custom view of header and footer here.
Happy coding. :)

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)
}

Can't un check all cell before check one swift 2

I m using swift 2 and UITableViews and when I press a cell a checkmark appear, but I wan't that only one cell can be checked in my tableview so the other checkmarks will disappear from my tableview. I tried different technics without success. I have a CustomCell with just a label.
Here is my code :
import UIKit
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var tableView: UITableView!
var answersList: [String] = ["One","Two","Three","Four","Five"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return answersList.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCustomCell", forIndexPath: indexPath) as! MyCustomCell
cell.displayAnswers(answersList[indexPath.row]) // My cell is just a label
return cell
}
// Mark: Table View Delegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Element selected in one of the array list
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark {
cell.accessoryType = .None
} else {
cell.accessoryType = .Checkmark
}
}
}
}
Assuming you've only section here's what you can do
// checkmarks when tapped
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let section = indexPath.section
let numberOfRows = tableView.numberOfRowsInSection(section)
for row in 0..<numberOfRows {
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section)) {
cell.accessoryType = row == indexPath.row ? .Checkmark : .None
}
}
}
Fixed code from #SirH to work with Swift 3
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let section = indexPath.section
let numberOfRows = tableView.numberOfRows(inSection: section)
for row in 0..<numberOfRows {
if let cell = tableView.cellForRow(at:IndexPath(row: row, section: section)) {
cell.accessoryType = row == indexPath.row ? .checkmark : .none
}
}
}