Primary/Master - Table view with multiple levels

Viewed 321

I'm trying to develop an app for both iPad and iPhone with split view controller where MasterViewController has a UITableView, let's say folders. Selecting a row (a folder) from MasterViewController should show it's sub items (sub folders & files). These should replace the original rows (folders) in Master view.

Selecting a file should show its information on Detail view. Selecting a sub folder should replace it's files/ sub folders list in Master view. I also need to keep the Back button in navigation bar to go back to main folders.

I searched but couldn't find any examples or suggestions on replacing the Master view and keeping the Back button. I've to do this programmatically without storyboards. Any ideas? Thanks in advance.

enter image description here

3 Answers

You could try to use UITableViewDiffableDataSource and apply a different snapshot based on the thing that is selected

What you need to do is to use ContainerView in storyboard add containerView and in it's ViewController add your embedded one and access it like this from code

Storyboard: enter image description here

We Don't have your models so I created one to showcase.

struct Folder {
    var files: [String]
    var folders: [Folder]?
    var name: String
}
extension Folder {
   static var mainDataSource: [Folder]  {
        return [
            Folder(files: ["f1","f2","f3"], folders: nil, name: "Folder 1"),
            Folder(files: ["f4","f5","f6"], folders: [Folder(files: ["foof2","foof3","foof4"], folders: nil, name: "Folder2-1")], name: "Folder2")
        ]
    }
}

MasterVC should handle it like this:

class MasterViewController: UIViewController {
    private var  embedded: EmbededViewController!
    @IBOutlet weak var container: UIView!
    @IBOutlet weak var tableview: UITableView!
    var path: String = "" // append later for titles
    var selectedFolder: Folder?
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "EmbeddedSeg" {
            let vc = segue.destination as! EmbededViewController
            embedded = vc
        }
    }

}

extension MasterViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        var button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 30)) //creating button just to showcase
        let view = UIView()
        button.setTitle( "back", for: .normal)
        button.backgroundColor = .red
        button.addTarget(self, action: #selector(didTapBack), for: .touchUpInside) // adding trigger on click
        view.addSubview(button) // back button modify as needed
        return view
    }
    // on back set the new selected Folder
    @objc func didTapBack() {
        selectedFolder = nil
        tableview.reloadData()
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        var cell = UITableViewCell()
        if selectedFolder != nil {
            if indexPath.row > selectedFolder!.files.count - 1 { // if the  indexPath pointing on folder
                cell.textLabel?.text = selectedFolder!.folders?[(indexPath.row) - selectedFolder!.files.count].name // get folder name

            } else {
                cell.textLabel?.text = selectedFolder!.files[indexPath.row] // else get the file
            }

        } else {
            cell.textLabel?.text = Folder.mainDataSource[indexPath.row].name // if there is no selected folder just get the name of folders/files from main
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if selectedFolder != nil { // if there is selected folder
            return selectedFolder!.files.count + (selectedFolder?.folders?.count ?? 0) // return files and folders count
        } else {
            return  Folder.mainDataSource.count // no selected then user main data source
        }
    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if selectedFolder != nil { // if there is a folder
            if indexPath.row > selectedFolder!.files.count - 1 { // if the clicked indexPath pointing on folder
                selectedFolder = selectedFolder!.folders![(indexPath.row) - selectedFolder!.files.count] // set the selected folder with our tapped one
                tableView.reloadData()
            } else  { // if was tapped on file just show the name
            embedded.lblContents.text = selectedFolder!.files[indexPath.row]
            }
        } else {
            var item = Folder.mainDataSource[indexPath.row] // if folder is nil then just show the item or selected folder in our case select folder only for the sake of example
                selectedFolder = item
                if item.files.count != 0 {
                    embedded.lblContents.text = item.files[0]
                    tableView.reloadData()
                }
        }
    }
}

Result:

enter image description here

For pathing strings on top that's something else to handle based on the object you got and adding a header to the tableView

The top navigation that you are showing with back button, Keep it separate from your MasterViewController. Show it always with the Navigation title.

For the back button check if subfolder selected OR your details page is in ViewStack then show it else hide it.

Related