0

I have a UITableView with and I want to add several categories.

My data looks somewhat like this:

  • MainCategory A
    • SubCategory Red
      • SubSubCategory X
        • SubSubSubCategory
      • SubSubCategory Y
    • SubCategory Blue
  • MainCategory B
    • SubCategory Red
      • SubSubCategory X
      • SubSubCategory Y
    • SubCategory Blue
      • SubSubCategory X
    • SubCategory Green

The first screen should show all the MainCategories, then if I click on A, it should show me SubCategory Red and Blue, and so on...

I searched everywhere but couldn't find a solution.

How should I structure my data so that this would work? cellForRowAt only gives you the option to give the data for the first set of sub categories. Where do I put the data for the rest?

tajhespitz
  • 40
  • 8
  • Can you please add the response data that you want to model. – PGDev Jun 27 '19 at 08:20
  • This question is similar to [this one](https://stackoverflow.com/questions/4342180/iphone-uitableview-nested-sections). Take a look. – haste Jun 27 '19 at 08:45

3 Answers3

3

enter image description here

Create a struct Menu with a title and an array of Menu objects.

struct Menu {
    var title: String
    var subMenus: [Menu]?
}

Add menu detail in a view controller where you want to start the menu. And push a UITableViewController with the menu object. If a submenu is pressed push a UITableViewController with the sub menu object.

//ViewController.swift

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }
    @IBAction func startMenuAction(_ sender: UIButton) {
        let red1 = Menu(title: "SubCategory Red", subMenus: [Menu(title: "SubSubCategory X", subMenus: [Menu(title: "SubSubSubCategory", subMenus: nil)]),
                                                             Menu(title: "SubSubCategory Y", subMenus: nil)])
        let blue1 = Menu(title: "SubCategory Blue", subMenus: nil)

        let red2 = Menu(title: "SubCategory Red", subMenus: [Menu(title: "SubSubCategory X", subMenus: nil),
                                                             Menu(title: "SubSubCategory Y", subMenus: nil)])
        let blue2 = Menu(title: "SubCategory Blue", subMenus: [Menu(title: "SubSubCategory X", subMenus: nil)])
        let green2 = Menu(title: "SubCategory Green", subMenus: nil)

        let categories = [Menu(title: "MainCategory A", subMenus: [red1, blue1]), Menu(title: "MainCategory B", subMenus: [red2, blue2, green2])]

        let mainMenu:Menu = Menu(title: "My Menu", subMenus: categories)

        let menuVC = MenuVC()
        menuVC.currentMenu = mainMenu
        self.navigationController?.pushViewController(menuVC, animated: true)
    }
}

//MenuVC.swift

class MenuVC: UITableViewController {
    var currentMenu: Menu?
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        title = currentMenu?.title
    }

    // MARK: - Table view data source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return (currentMenu?.subMenus?.count ?? 0)
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier") ?? UITableViewCell(style: .default, reuseIdentifier: "reuseIdentifier")
        if currentMenu?.subMenus?[indexPath.row].subMenus?.isEmpty ?? true {
            cell.accessoryType = .none
        } else {
            cell.accessoryType = .disclosureIndicator
        }
        cell.textLabel?.text = currentMenu?.subMenus?[indexPath.row].title
        return cell
    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if !(currentMenu?.subMenus?[indexPath.row].subMenus?.isEmpty ?? true) {
            let menuVC = MenuVC()
            menuVC.currentMenu = currentMenu?.subMenus?[indexPath.row]
            self.navigationController?.pushViewController(menuVC, animated: true)
        } 
    }
}
RajeshKumar R
  • 15,445
  • 2
  • 38
  • 70
1

enter image description here

Create a struct Menu with a title and an array of Menu objects.

struct Menu {
    var title: String
    var subMenus: [Menu]?
}

Instead of table view use an UIScrollView and add nested UIStackView

//ViewController.swift

class ViewController: UIViewController {

    var menus:[Menu] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white

        let red1 = Menu(title: "SubCategory Red", subMenus: [Menu(title: "SubSubCategory X", subMenus: [Menu(title: "SubSubSubCategory", subMenus: nil)]),
                                                             Menu(title: "SubSubCategory Y", subMenus: nil)])
        let blue1 = Menu(title: "SubCategory Blue", subMenus: nil)

        let red2 = Menu(title: "SubCategory Red", subMenus: [Menu(title: "SubSubCategory X", subMenus: nil),
                                                             Menu(title: "SubSubCategory Y", subMenus: nil)])
        let blue2 = Menu(title: "SubCategory Blue", subMenus: [Menu(title: "SubSubCategory X", subMenus: nil)])
        let green2 = Menu(title: "SubCategory Green", subMenus: nil)

        menus = [Menu(title: "MainCategory A", subMenus: [red1, blue1]), Menu(title: "MainCategory B", subMenus: [red2, blue2, green2])]

        let scrollView = UIScrollView()
        scrollView.backgroundColor = .white
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)


        let outerStackView = UIStackView()
        outerStackView.axis = .vertical
        outerStackView.alignment = .fill
        outerStackView.distribution = .fillProportionally
        outerStackView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(outerStackView)


        scrollView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true            view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[scrollView]|", options: [], metrics: nil, views: ["scrollView": scrollView]))
        view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: [], metrics: nil, views: ["scrollView": scrollView]))

        scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[outerStackView]|", options: [], metrics: nil, views: ["outerStackView": outerStackView]))
        scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[outerStackView]|", options: [], metrics: nil, views: ["outerStackView": outerStackView]))
        scrollView.widthAnchor.constraint(equalTo: outerStackView.widthAnchor).isActive = true
        let height = scrollView.heightAnchor.constraint(equalTo: outerStackView.heightAnchor)
        height.priority = .defaultLow
        height.isActive = true
        menus.forEach {
            outerStackView.addArrangedSubview(MenuStackView(menu: $0, padding: 20))
        }
    }

}

//MenuStackView.swift

class MenuStackView: UIStackView {

    convenience init(menu: Menu, padding: CGFloat) {
        self.init()

        axis = .vertical
        alignment = .fill
        distribution = .fillProportionally

        let btn = UIButton(type: .custom)
        btn.contentHorizontalAlignment = .left
        btn.titleEdgeInsets = UIEdgeInsets(top: 0, left: padding, bottom: 0, right: 0)
        btn.addTarget(self, action: #selector(toggle(_:)), for: .touchUpInside)
        btn.setTitleColor(.black, for: .normal)
        btn.setTitle(menu.title, for: .normal)
        btn.translatesAutoresizingMaskIntoConstraints = false
        btn.heightAnchor.constraint(equalToConstant: 40).isActive = true
        addArrangedSubview(btn)

        menu.subMenus?.forEach {
            let stackView = MenuStackView(menu: $0, padding: padding+20)
            stackView.isHidden = true
            self.addArrangedSubview(stackView)
        }
    }
    @objc func toggle(_ sender: UIButton) {
        self.arrangedSubviews.forEach {
            if $0 is MenuStackView {
                $0.isHidden = !$0.isHidden
            }
        }
    }
}
RajeshKumar R
  • 15,445
  • 2
  • 38
  • 70
  • Thank you for your answer. But is there a way where once you press on a cell it takes you to another view controller rather than having everything show up in the save view? – tajhespitz Jun 27 '19 at 18:58
  • @tajhespitz Do you want to show each menu level in a new view controller? – RajeshKumar R Jun 27 '19 at 19:35
0

You can download the sample project from the Github. In that, I am using UITableview for displaying different category as you want.

enter image description here

Rushabh Shah
  • 396
  • 3
  • 19