1

I'm very new to swift and programing for IOS but I'm trying to create an app. I want to have a UITableView where i have some headers that if you click them they will open or close depending on if they are open or not. Is it possible to do this with headers or will I need to use a normal cell? This is how i have sett everything up so far. Thank you for any help?

Edits:

I am now able to open and close sections now but some gets removed. I have updated the code with the edits I have done so far.

her is how it looks when i click something.

Before clickafter click

var settings: [settingOptions] = [
    settingOptions(isOpened: true, setting: "appearance", options: ["light mode", "dark mode"]),
    settingOptions(isOpened: true, setting: "unit system", options: ["liters & milli liters", "ounzes"]),
    settingOptions(isOpened: true, setting: "change goal", options: ["goal"]),
    settingOptions(isOpened: true, setting: "how to use", options: []),
    settingOptions(isOpened: true, setting: "remove data", options: [])]
@objc func expandOrCollapsSection(_ sender: UIGestureRecognizer){
    print("Do something with section")
    guard let section = sender.view?.tag else { return }
    var indexPaths = [IndexPath]()
    for row in settings[section].options.indices {
        let indexPath = IndexPath(row: row, section: section)
        indexPaths.append(indexPath)
    }

    let ioOpend = settings[section].isOpened
    settings[section].isOpened = !ioOpend
    if ioOpend {
        tableView.deleteRows(at: indexPaths, with: .none)
    } else {
        tableView.insertRows(at: indexPaths, with: .none)
    }
}



extension AboutVC: UITableViewDelegate, UITableViewDataSource{

     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "header") as! SettingOptionCell
        cell.setting = settings[indexPath.section].options[indexPath.row]

        return cell
    }

     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return settings[section].isOpened ? settings[section].options.count : 0
    }

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

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
         let cell = tableView.dequeueReusableCell(withIdentifier: "settingCell") as! SettingsCell
        cell.setting = settings[section]
        cell.selectionStyle = .none
        cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(expandOrCollapsSection)))
        cell.tag = section
        return cell
    }
     func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
         return 60
    }


    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    }

    }
}

class SettingOptionCell: UITableViewCell {
    var setting: String? {
        didSet {
            guard let string    = setting else {return}
            option.text         = string.capitalized
        }
    }
    let option: UILabel = {
        let lable           = UILabel()
        lable.text          = "test"
        lable.textColor     = UIColor.white
        lable.font          = UIFont(name: "AmericanTypewriter", size: 17)
        lable.translatesAutoresizingMaskIntoConstraints = false
        return lable
        }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.addSubview(option)
        option.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 20).isActive = true
        option.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
        self.backgroundColor = .none
    }



    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class SettingsCell: UITableViewCell {
    var setting: settingOptions? {
        didSet {
            guard let setting = setting else {return}
            self.title.text = setting.setting.uppercased()
        }
    }
    var title: UILabel = {
        let lable       = UILabel()
        lable.text      = "Test"
        lable.font      = UIFont(name: "AmericanTypewriter", size: 20)
        lable.textColor = .white
        lable.translatesAutoresizingMaskIntoConstraints = false
        return lable
    }()
    var container: UIView = {
        let view                                        = UIView()
        view.clipsToBounds                              = true
        view.backgroundColor                            = .none
        view.translatesAutoresizingMaskIntoConstraints  = false
        return view
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.backgroundColor                = .none
        self.isUserInteractionEnabled       = true
        contentView.addSubview(container)
        container.addSubview(title)
        title.leftAnchor.constraint(equalTo: container.leftAnchor).isActive                     = true
        title.centerYAnchor.constraint(equalTo: container.topAnchor,constant: 30).isActive      = true

        container.topAnchor.constraint(equalTo:contentView.topAnchor).isActive                  = true
        container.leftAnchor.constraint(equalTo:contentView.leftAnchor).isActive                = true
        container.rightAnchor.constraint(equalTo:contentView.rightAnchor).isActive              = true
        container.bottomAnchor.constraint(equalTo:contentView.bottomAnchor).isActive            = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}
Petter Braka
  • 311
  • 1
  • 11

3 Answers3

2

Yes its possible with headers, Add tag and UITapGestureRecogniser for all header views

Add isExpanded boolean key to settings data structure

here you go, write an action method for gesture

@objc func handleTapGesture(_ sender:UIGestureRecogniser) {
              let gesture = UIGestureRecognizer.init()
    guard let section = gesture.view?.tag else { return }
    let indexPaths = [IndexPath]()
    for row in settings[section].options.indices {
        let indexpath = IndexPath.init(row: row, section: section)
    }
    // revising the value....
    let isExpanded = settings[section].isExpanded
    settings[section].isExpanded = !isExpanded
    if isExpanded {
         tableView.deleteRows(at: indexPaths, with: .fade)
    } else {
         tableView.insertRows(at: indexPaths, with: .fade)
    }
}

Small modificaton in number of rows method

 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    return settings[section].isExpanded ? settings[section].options.count : 0
}

I recommend below video. https://www.youtube.com/watch?v=Q8k9E1gQ_qg

Sreekanth M
  • 151
  • 1
  • 6
  • This didn't work but after some modifications to your code it will open and close a section but it will remove some headers. her is the modifications i made: guard let section = sender.view?.tag else { return } var indexPaths = [IndexPath]() for row in settings[section].options.indices { let indexPath = IndexPath(row: row, section: section) indexPaths.append(indexPath) print(indexPath) } – Petter Braka Apr 25 '20 at 12:12
  • I have updated the code and added images of what happens – Petter Braka Apr 25 '20 at 12:27
  • @PetterBraka. First of all thats great you found the missed line, I forgot to add that line. I don't recommend reuse cells for header views. I think that is the reason for removing headers issue. For ref: https://stackoverflow.com/a/960157/8879068 – Sreekanth M Apr 25 '20 at 21:01
  • thank you. Yeah, when I changed to have a custom header instead it worked perfectly. @sreekanth-m – Petter Braka Apr 25 '20 at 21:07
0

Define variable for store your table view section status

var isCollapsed:Bool = true

Then you can add UITapGestureRecognizer to your table view header view

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let cell = tableView.dequeueReusableCell(withIdentifier: "settingCell") as! SettingsCell
        cell.setting = settings[section]
        cell.selectionStyle = .none
        cell.isUserInteractionEnabled = true
        cell.tag = section
        cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didMenuItemClicked(_:))))
        return cell
}

Next step, you want to config your table view cell for behave according to isCollapsed status

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if !isCollapsed {
            let cell = tableView.dequeueReusableCell(withIdentifier: "header") as! SettingOptionCell
            cell.setting = settings[indexPath.section].options[indexPath.row]
            return cell
        }else{
            let cell = UITableViewCell()
            cell.translatesAutoresizingMaskIntoConstraints = false
            cell.heightAnchor.constraint(equalToConstant: 0).isActive = true
            cell.widthAnchor.constraint(equalToConstant: 0).isActive = true
            return cell
        }

}

Now you can write function to show,hide sub items,

@objc func didMenuItemClicked(_ sender : UITapGestureRecognizer){

        isCollapsed = !isCollapsed
        myTable.reloadSections([sender.view!.tag], with: .fade)
}
Dilan
  • 2,610
  • 7
  • 23
  • 33
0

To fix the issue with disappearing headers, I fixed that with changing the cell type used as a header to a UITableViewHeaderFooterView.

class SettingsCell: UITableViewHeaderFooterView {
//init()
}

Register like this:

tableView.register(SettingsCell.self, forHeaderFooterViewReuseIdentifier: "header")

Dequeue like this:

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as! SettingsCell
    cell.setting = settings[section]
    cell.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(expandOrCollapsSection)))
    cell.tag = section
    return cell
}

And to fix make the section open and close i implemented the solution from sreekanth.

Petter Braka
  • 311
  • 1
  • 11