[Swift] How to make a UITableView static cell segue to a ViewController within a Tab Bar View

Hello community!


Before I explain my question, please understand I'm very new to Swift. I'm very sorry in advance if I ask for clarification or need a little more help. Also, as much sample code as possible please! 🙂


So, I've got a storyboard. Within it, I have a Tab Bar Controller holding 3 seperate ViewControllers; HomeViewController, Page1ViewController, and Page2ViewController. They each have their own seperate Navigation Controllers as well.
So, the Tab Bar Controller creates 3 tabs (Home, Page 1 and Page 2).


On HomeViewController I have a Navigation Controller as it's root, and the actual ViewController contains a UITableView. I have set 2 Prototype cells with labels of "Page 1" and "Page 2" respectively. I am trying to achieve it so when the user taps on cell 1 ("Page 1") it will segue to the Page1ViewController AND change the Tab Bar controller to indicate it is on Page 1.

And the same for cell 2; user taps on cell 2 ("Page 2") it will segue to the Page2ViewController AND change the Tab Bar controller to indicate it is on Page 2.


I haven't got any code as of currently. A friend supplied me some sample code however I ran into countless errors and it was just too confusing.

I hope someone can help me here as this app needs to be more-or-less completed very soon!


Much appreciated!!

Accepted Reply

This is what I did in a mockup, and it seems to behave as you describe.


It is difficult to send the whole project here, if you want to drop an email here for a moment, I will send the whole project.


Here is the code of the HomeViewController, with the tableView


import UIKit

class HomeTableViewController: UITableViewController {

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

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 6     // Just to test
    }

  
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
        if indexPath.row < 3 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Type1Cell", for: indexPath)
            cell.textLabel?.text = "First type cell n° \(indexPath.row)"
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Type2Cell", for: indexPath)
           cell.textLabel?.text = "Second type cell n° \(indexPath.row)"
            cell.detailTextLabel?.text = "With subtitle"          
            return cell
        }
    }
  
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        if let cell = tableView.cellForRow(at: indexPath), let identifier = cell.reuseIdentifier {
            var segueID = ""
            switch identifier {
            case "Type1Cell": segueID = "GoToFirst"
            case "Type2Cell": segueID = "GoToSecond"
            default: return
            }
            performSegue(withIdentifier: segueID, sender: self)
        }
    }
  
    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
      
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
        var index = 0
        switch segue.identifier {
        case "GoToFirst": index = 1
        case "GoToSecond": index = 2
        default: index = 0
        }
        if let tabBarController = self.navigationController?.tabBarController?.tabBarController {
            tabBarController.selectedIndex = index
        }
    }

}

Replies

Have you created everything in Interface Builder (you should).


Then, control-drag from prototype cell1 to Page1ViewController, and similarly for 2


Implement a prepare(forSegue) to set the content you want to see in the destination controller


To "change the tab bar" , I had to do a more complex set up.


here is what I did, in IB.


You have a TabBarController

Create the 3 views HomeViewController, Page1ViewController, Page2ViewController


Connect it with relationship segues to the 3 views HomeViewController, Page1ViewController, Page2ViewController ; give them the title that will appear in the buttons


Then, need a double embedding:

- for each, embed in a navigation controller

- then embed this navigation controller in a tabbarController (which thus will be connected to the top tabBarController)


Form HomeViewController, create 2 segues (from the viewController itself by ontrol drag from the left button at the top of the view to Page1 and another to page2.

Name those segues GoToFirst and GoToSecond


Tehn, in didSelectCell, depending on type of cell,

perform(segue:) : either GoToFirst or GoToSecond


The trick is in prepare, to set the button.


In HomeViewController


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     switch segue.identifier {
          var index = 0
          case "GoToFirst": index = 1
          case "GoToSecond": index = 2
          default: index = 0
     }
        if let tabBarController = self.navigationController?.tabBarController?.tabBarController {
            tabBarController.selectedIndex = index
        }
    }


Explanation:

self.navigationController?.tabBarController?.tabBarController


self.navigationController? : goes to the navController of HomeView

self.navigationController?.tabBarController? : goes to the tabBarController of this NavController

self.navigationController?.tabBarController?.tabBarController : finally reaches the initial TabBar, where we set the selectedIndex


Hope it is clear and works for you.

Why do you need a segue? Normally you’d just set the selected index of the tab bar controller (from didSelectCell, in code) and call it good.

Thank you so much for your reply!

I implemented your suggestions and the HomeViewController worked perfectly! The static cell navigated to the correct ViewController and so did the Tab Bar.
I have found one problem though, once I'm on a PageViewController, the Tab Bar doesn't function. It DOES indicate the correct Tab is 'open', however, clicking on any of the other tabs does nothing to the view or produce any errors. Any idea what could be causing this?

HomeViewController: (ViewController with "Welcome" title)


import UIKit

class HomeViewController: UITableViewController {

    @IBOutlet var btnTable: UITableView!
   
    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()

    }

    var index = 0
   
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        switch segue.identifier {
        case "GoToInfo": index = 1
        default: index = 0
        }
        if let tabBarController = self.navigationController?.tabBarController?.tabBarController {
            tabBarController.selectedIndex = index
        }
    }
   
    func btnTable(_ btnTable: UITableView, didSelectRowAt indexPath: IndexPath) {
        btnTable.deselectRow(at: indexPath, animated: true)
            self.tabBarController?.selectedIndex = 1
     }
}


Storyboard: (only "Information" or Page1ViewController is shown in image)


URL to image: (remove space between 'h' and 't' because it gets caught by moderation filter)
h ttps://imgur.com/a/gE90wBP

I believe this was the method I was attempting to achieve with my friend's code, however, I ended up ditching that idea as it seemed too difficult. If you have an explanation and some sample code for me to try, I'll try that out.
Thank you!

I looked at the screenshot.


What is surprising is that you have only 2 buttons.


In the setup I proposed you, you would have 3 : Home, First and Second.

So there is a first TabBarController that connects to 3 tabBarControllers (Home, First, Second) with a relationship segue.


--> segue (Show Detail) to TabBar (First) to use by didSelect

--> TabBar (Home) --> Nav. --> HomeView (with Table)

--> segue (Show Detail) to TabBar (Second) to use by didSelect

TabBar --> TabBar (First) --> Nav. --> FirstView


--> TabBar (Second) --> Nav. --> SecondView


The segue must be Show Detail and not push to return to Home when you hit Home button


From what you wrote:

On HomeViewController I have a Navigation Controller as it's root, and the actual ViewController contains a UITableView. I have set 2 Prototype cells with labels of "Page 1" and "Page 2" respectively.

I understood that the tableView was in the home ? In fact, I see on your image that it is in View1 and in View2 ?


If that is trhe case, code should be modified.


The didSelectRow should be



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

        if let cellAtRow = tableView.cellForRow(at: indexPath), let cellIdentifier = cellAtRow.reuseIdentifier) {
          var segueId = ""
          switch cellIdentifier {
               case "Cell1": segueId = "GoToFirst"          // Use the identifier you have attributed to the 2 cell prototypes
               case "Cell2": segueId = "GoToSecond"
               default: return
       } else {
          return
       }

       performSegue(withIdentifier: segueId, sender: self)
   }

I'm very sorry, I obviously didn't make my setup clear enough.


I want 3 Views; Home, First and Second.

Their titles respectively; Home, Information and Events.


The screenshot only shows Home and Information in the Tab Bar as I haven't set up the Events view yet.
The Table View on the HomeView should be able to navigate to Information and the Events page.
The Table View on the Information page has nothing to do with this setup as that is for navigating to other ViewControllers without the Tab Bar completely. I have already got this set up and working with push segues.

If it helps here is a diagram on how I want it to operate:


> Tab Bar (Main)

> Tab Bar (Home)

> Navigation Controller (Home)

> HomeViewController (Table View that links to Information and the third page)

> Tab Bar (Information)

> Navigation Controller (Information)

> InformationViewController (Happens to have a Table View also used for navigating other Views but has nothing to do with the Tab Bar controller or this situation)

> Tab Bar (Events)

> Navigation Controller (Events)

> EventsViewController (Will have labels are other UI components)



I am unsure if your latest advice applies to my situation now.
I am very sorry for not explaining my situation in detail originally but I thought it would be easier saving the unneccessary details.
I really appreciate your time.

This is what I did in a mockup, and it seems to behave as you describe.


It is difficult to send the whole project here, if you want to drop an email here for a moment, I will send the whole project.


Here is the code of the HomeViewController, with the tableView


import UIKit

class HomeTableViewController: UITableViewController {

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

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 6     // Just to test
    }

  
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
        if indexPath.row < 3 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Type1Cell", for: indexPath)
            cell.textLabel?.text = "First type cell n° \(indexPath.row)"
            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "Type2Cell", for: indexPath)
           cell.textLabel?.text = "Second type cell n° \(indexPath.row)"
            cell.detailTextLabel?.text = "With subtitle"          
            return cell
        }
    }
  
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        if let cell = tableView.cellForRow(at: indexPath), let identifier = cell.reuseIdentifier {
            var segueID = ""
            switch identifier {
            case "Type1Cell": segueID = "GoToFirst"
            case "Type2Cell": segueID = "GoToSecond"
            default: return
            }
            performSegue(withIdentifier: segueID, sender: self)
        }
    }
  
    // MARK: - Navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
      
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
        var index = 0
        switch segue.identifier {
        case "GoToFirst": index = 1
        case "GoToSecond": index = 2
        default: index = 0
        }
        if let tabBarController = self.navigationController?.tabBarController?.tabBarController {
            tabBarController.selectedIndex = index
        }
    }

}
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("Row \(indexPath.row) selected")
        self.tabBarController?.selectedIndex = indexPath.row + 1
    }


To me it seems like madness to have multiple nested tab bar controllers. There should be one, at the top, with each tab containing whatever other controller hierarchy you want. In this case, the main/menu (I don't like "home" in iOS apps, as it smacks of a web site) controller is a UITableViewController with static cells. The above one line of code switches to the other tab.


I'm still not sure why you need a segue. If you need to pass some information to the other controllers, you can look them up easily either through your view controller hierarchy or (better, in my opinon) through a class method to get a reference to the instance created by the storyboard, and set properties / call methods that way.