Rameez Hussain
Rameez Hussain

Reputation: 6754

Swift/iOS: Collapsing a section in a UITableView

I have a UITableView with about 5 sections. I am trying to collapse and expand one of those section by the click of a button, but I am seeing an issue where the code I'm using to do so results in the collapsing of other sections as well. Specifically, the first row of all visible sections are collapsed.

Here is what that code looks like:

func didClickSectionCollapseButton() {
    shouldCollapseSection = !shouldCollapseSection
    tableView.beginUpdates()
    tableView.reloadSections(NSIndexSet(index: 1), withRowAnimation: .Fade)
    tableView.endUpdates()
}

And here is the numberOfRowInSection method:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    switch section {
    case 0:
        return 1
    case 1:
        // collapsible section
        return shouldCollapse ? 0 : collapsibleSectionCellCount
    case 2:
        return getCellCount()
    case 3:
        return 1
    case 4:
        return 1
    default:
        return 0
    }
}

Is there anything I'm missing here? I've gone through various tutorials and questions, but I haven't been able to find a solution yet.

Upvotes: 0

Views: 9050

Answers (5)

Arshad Shaik
Arshad Shaik

Reputation: 1254

Hi after a lot of research, i found a solution which worked for me perfectly using storyboard.

storyboard setup

View controller code:

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

@IBOutlet weak var tblView: UITableView!

var sections = ["section1","section2","section3"]

var cells = ["cell1","cell2","cell3","cell4"]

var selectedIndx = -1

var thereIsCellTapped = false

override func viewDidLoad() {
    super.viewDidLoad()
    tblView.reloadData()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func numberOfSections(in tableView: UITableView) -> Int {
    return sections.count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    switch section {
    case 0:
        return 2
    case 1:
        return 3
    default:
        return 4
    }
}

func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 50
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.section == selectedIndx && thereIsCellTapped{
        return 50
    }else{
        return 0
    }
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let headerCell = tableView.dequeueReusableCell(withIdentifier: "SectionTableViewCell") as! SectionTableViewCell
    headerCell.lblHeader.text = sections[section]
    headerCell.btnSelection.tag = section
    headerCell.btnSelection.addTarget(self, action: #selector(ViewController.btnSectionClick(sender:)), for: .touchUpInside)
    return headerCell
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "ExpandeTableViewCell") as! ExpandeTableViewCell
    cell.lblCell.text = cells[indexPath.row]
    return cell
}

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    print(indexPath.section)
}

@objc func btnSectionClick(sender:UIButton!){
    print("selected index",sender.tag)
    if selectedIndx != sender.tag {
        self.thereIsCellTapped = true
        self.selectedIndx = sender.tag
    }
    else {
        // there is no cell selected anymore
        self.thereIsCellTapped = false
        self.selectedIndx = -1
    }
    tblView.reloadData()
}
}

If you don't want to do select and unselect on the same selection then, see code below.

@objc func btnSectionClick(sender:UIButton!){
    print("selected index",sender.tag)

    selectedIndx = sender.tag

    tblView.reloadData()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    if indexPath.section == selectedIndx{
        return 50
    }else{
        return 0
    }
}

It works for me, i referred lot of answers and made it. I hope it will help you.

Upvotes: 2

vivek agravat
vivek agravat

Reputation: 251

I've Used this code long time ago it is in Swift 2.3. I don't know if this will help or not but worth to mention it.

class DriversVC : UIViewController , UITableViewDelegate , UITableViewDataSource {
    //-----------------------------------------------------------------------
    //MARK: - Outlets

    @IBOutlet var tvDriverList: UITableView! {
        didSet {
            tvDriverList.delegate = self
            tvDriverList.dataSource = self
        }
    }

    //-----------------------------------------------------------------------
    //MARK: - Variables

    var arrDriverList : NSArray? //Section data
    var arrWorkerList : NSArray? //Section data
    var collapseSection0 : Bool = false
    var collapseSection1 : Bool = false
    var btnSection0Headder : UIButton = UIButton()
    var btnSection1Headder : UIButton = UIButton()

    //------------------------------------------------------

    func btnSection0HeadderTapped () {
        if collapseSection0 {
            collapseSection0 = false
        } else {
            collapseSection0 = true
        }
        tvDriverList.reloadSections(NSIndexSet(index: 0), withRowAnimation: UITableViewRowAnimation.Fade)
    }

    //------------------------------------------------------

    func btnSection1HeadderTapped () {
        if collapseSection1 {
            collapseSection1 = false
        } else {
            collapseSection1 = true
        }
        tvDriverList.reloadSections(NSIndexSet(index: 1), withRowAnimation: UITableViewRowAnimation.Fade)
    }

    //-----------------------------------------------------------------------------------
    //MARK:- Table delegate and data sources

    func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 50
    }

    //------------------------------------------------------

    func tableView(tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        return 20
    }

    //------------------------------------------------------

    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: 50))
        view.backgroundColor = OrangeColor //Set your color
        let lbl = UILabel(frame: CGRect(x: 10, y: 5, width: UIScreen.mainScreen().bounds.width - 20, height: 40))
        lbl.font = UIFont(name: OpenSansRegular, size: 18) //Set your font
        lbl.textColor = UIColor.whiteColor()
        view.addSubview(lbl)
        if section == 0 {
            lbl.text = "D R I V E R"
            btnSection0Headder.addTarget(self, action: #selector(self.btnSection0HeadderTapped), forControlEvents: .TouchUpInside)
            btnSection0Headder.frame = view.frame
            view.addSubview(btnSection0Headder) // uncomment to apply collapse effect
        } else {
            lbl.text = "W O R K E R"
            btnSection1Headder.addTarget(self, action: #selector(self.btnSection1HeadderTapped), forControlEvents: .TouchUpInside)
            btnSection1Headder.frame = view.frame
            view.addSubview(btnSection1Headder) // uncomment to apply collapse effect
        }
        return view
    }

    //------------------------------------------------------

    func tableView(tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        return UIView()
    }

    //------------------------------------------------------

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        if arrWorkerList != nil && arrWorkerList?.count > 0 {
            return 2
        }
        return 1
    }

    //------------------------------------------------------

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            if !collapseSection0 {
                guard arrDriverList != nil else {return 0}
                return arrDriverList!.count
            } else {
                return 0
            }
        } else {
            if !collapseSection1 {
                guard arrWorkerList != nil else {return 0}
                return arrWorkerList!.count
            } else {
                return 0
            }
        }
    }

    //------------------------------------------------------

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCellWithIdentifier(NSStringFromClass(DriversCVC).componentsSeparatedByString(".").last!) as? DriversCVC else { fatalError("unexpected DriversCVC dequeued from tableView") }
        cell.superViewController = self
        if indexPath.section == 0 {
            guard let dict = arrDriverList![indexPath.row] as? NSDictionary else {return cell}
            cell.data = dict
        } else {
            guard let dict = arrWorkerList![indexPath.row] as? NSDictionary else {return cell}
            cell.data = dict
        }
        cell.setup()
        return cell
    }

    //----------------------------------------------------------------------
    //MARK: - Action Method

    @IBAction func btnBackTapped(sender: AnyObject) {
        guard self.navigationController != nil else {
            self.dismissViewControllerAnimated(true, completion: nil)
            return
        }
        guard self.navigationController?.popViewControllerAnimated(true) != nil else {
            guard self.navigationController?.dismissViewControllerAnimated(true, completion: nil) != nil else {
                AppDelegate.sharedInstance().loginCall()
                return
            }
            return
        }
    }

    //-----------------------------------------------------------------------
    //MARK: - View Life Cycle Methods

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    //----------------------------------------------------------------------

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        setUpVC()
    }

    //----------------------------------------------------------------------

    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
    } }

Upvotes: 1

Alain T.
Alain T.

Reputation: 42133

Is there a difference between the shouldCollapseSection variable being set in the button action and the shouldCollapse variable used in the numberOfRowsInSection method ?

It would seem that you are not setting the same variable you are using in the data source delegate.

Upvotes: 0

childrenOurFuture
childrenOurFuture

Reputation: 1987

beginUpdates() and endUpdates() works in pair if you want subsequent insertions, deletion, and selection operations, but not for the reloadData.
In your code, remove beginUpdates() and endUpdates().

Upvotes: 0

Diogo Antunes
Diogo Antunes

Reputation: 2291

You can use:

func didClickSectionCollapseButton() {
    shouldCollapseSection = !shouldCollapseSection
    tableView.beginUpdates()
    tableView.deleteSections(NSIndexSet(index: 1), withRowAnimation: .Fade)
    tableView.endUpdates()
}

Upvotes: 0

Related Questions