Pangu
Pangu

Reputation: 3819

How to push segue to specific View Controller depending on a selected cell?

I have a complicated issue I'd like to explain visually and through code.

Currently, this is how my app is suppose to work visually:

enter image description here

I have a ViewControllerOneclass that contains a UITableView with 9 cells. Any rows selected except rows 2, 6, and 7 will segue to ViewControllerTwo with its own UITableView and number of rows.

If rows 2, 6, or 7 is selected, a push segue will stack another ViewControllerOne onto the existing ViewControllerOne. The reason for doing this is because every row is a category, but rows 2, 6, and 7 also contains sub-categories that looks exactly like ViewControllerOne Main-VC on the left.

Rather than creating another class that contains the exact same code as in Main-VC, I wanted to reuse the ViewControllerOne class.

Now, if any rows in SUB-VC is selected, it will also perform a push segue to ViewControllerTwo.

Since ViewControllerOne and ViewControllerTwo are embedded in a UINavigationController, the issue I'm having is in the 5th step:

  1. I select a row not 2, 6, or 7 in Main-VC, it takes me to ViewControllerTwo (as it should)
  2. I go back to Main-VC via navigation bar back button and select row 2, 6, or 7 in MAIN-VC, it will take me to SUB-VC (as it should)
  3. I select a row not 2, 6, or 7 in Sub-VC, it will take me to ViewControllerTwo (as it should)
  4. I go back to Sub-VC via navigation bar back button on the navigation bar
  5. I select a row 2, 6, or 7 in Sub-VC, it will push segue and stack another Sub-VC on top of the existing Sub-VC instead of doing a push segue to ViewControllerTwo

I have a Manager class that handles the logic and communicates with ViewControllerOne and ViewControllerTwo to display the data.

import UIKit

enum SubGroupState
{
    case SubGroup1, None, SubGroup2, SubGroup3
}

class Manager: NSObject
{
    public var subGroupState = SubGroupState.None
    public var oldSubGroupState = SubGroupState.None
    public var showSubGroups = Bool()

    override init()
    {
        super.init()
    }

    public func initializeGroupState(row: Int) -> UIViewController
    {
        if showSubGroups == false && oldSubGroupState == .None
        {
            switch row
            {
                case 2:
                    subGroupState = .SubGroup1
                    break

                case 6:
                    subGroupState = .SubGroup2
                    break

                case 7:
                    subGroupState = .SubGroup3
                    break

                default:
                    subGroupState = .None
                    break
            }
        }

        if (subGroupState != .None && oldSubGroupState == .None)
            || (subGroupState == .None && oldSubGroupState != .None)
        {
            showSubGroups = true
        }
        else
        {
            showSubGroups = false
        }

        return initializeGroupVC(row: row)
    }

    fileprivate func initializeGroupVC(row: Int) -> UIViewController
    {
        let storyboard = UIStoryboard(name: "Main",
                                      bundle: nil)

        if showSubGroups == true
            && subGroupState != .None
        {
            guard let viewControllerOne = storyboard.instantiateViewController(withIdentifier: "ViewControllerOne")
                as? ViewControllerOne else {
                return UIViewController()
            }
            viewControllerOne.manager.oldSubGroupState = muscleSubGroupState
            viewControllerOne.manager.showSubGroups = showSubGroups

            return viewControllerOne
        }
        else
        {
            guard let viewControllerTwo = storyboard.instantiateViewController(withIdentifier: "ViewControllerTwo")
                as? ViewControllerTwo else {
                return UIViewController()
            }

            return muscleGroupExercisesVC
        }
    }

}

The purpose of the states is so I can handle displaying the different sub-categories depending on the state of the selected cell.

I create an instance of Manager in ViewControllerOne when the user selects a cell:

extension ViewControllerOne: UITableViewDataSource
{
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
    {
        tableView.deselectRow(at: indexPath,
                              animated: true)

        let viewController = manager.initializeGroupState(row: indexPath.row)
        self.navigationController?.pushViewController(viewController,
                                                      animated: true)
    }
}

class ViewControllerOne: UIViewController
{
    public var manager = Manager()

    ....
}

The issue is in logic handling in the function initializeGroupState, but I've tried other different combinations and I always get the Sub-VC stacked on top of an existing Sub-VC for rows 2, 6, and 7, which obviously corresponds to the subgroup rows in Main-VC, and that's where the issue I'm having a difficult time handling the logic with.

If I am doing this the wrong way, is there a better alternative to what I'm trying to achieve without repeating code?

NOTE: My Storboard only has the Main-VC ViewControllerOne with a segue to ViewControllerTwo. The added Sub-VC ViewControllerOne is there to visually see what I'm trying to do, but does not actually exist in my Storyboard.

Upvotes: 0

Views: 223

Answers (4)

motbantuangiauten
motbantuangiauten

Reputation: 151

I think we can keep it simple as posible

class ViewControllerOne: UIViewController, UITableViewDelegate {
  var isSubVC = false

  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: false)

    if self.isSubVC {
      // Push to ViewControllerTwo
    } else {
      // MainVC
      if indexPath.row == 2 || indexPath.row == 6 || indexPath.row == 7 {
        let subVC = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerOne") as! ViewControllerOne
        subVC.isSubVC = true
        self.navigationController?.pushViewController(subVC, animated: true)
      } else {
        // Push to ViewControllerTwo
      }
    }
  }
}

Here is the code using your manager ideas for navigation without any tests.

enum ViewControllerType {
  case main, subGroup1, subGroup2, subGroup3
}

class Manager {
  public var currentState = ViewControllerType.main

  public func initializeGroupState(row: Int) -> UIViewController {
    if self.currentState == .main {
      // MainVC, should push to SubVC if match condition
      switch row {
      case 2:
        return self.makeSubViewController(state: .subGroup1)
      case 6:
        return self.makeSubViewController(state: .subGroup2)
      case 7:
        return self.makeSubViewController(state: .subGroup3)
      default:
        return self.makeViewControllerTwo()
      }
    } else {
      // Current is SubVC, dont care kind of row, should push to ViewControllerTwo
      return self.makeViewControllerTwo()
    }
  }

  private func makeViewControllerTwo() -> UIViewController {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "ViewControllerTwo")
    return vc
  }

  private func makeSubViewController(state: ViewControllerType) -> UIViewController {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let vc = storyboard.instantiateViewController(withIdentifier: "ViewControllerOne") as! ViewControllerOne
    vc.manager.currentState = state
    return vc
  }
}

Upvotes: 1

RJ168
RJ168

Reputation: 1046

You can manage you viewcontroller and manager class as below:

class ViewControllerOne: UIViewController, UITableViewDelegate {
    var isSubVC = false

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: false)

        let wireframe = WireFrame()
        let nextVC = wireframe.getNextVC(forView: self)
        self.navigationController?.pushViewController(nextVC, animated: true)
    }
}

class WireFrame {

    func getNextVC (forView view: UIViewController) -> UIViewController {
        if view.isKind(of: ViewControllerOne) {
            return ViewControllerTwo
        } else {
            // MainVC
            if indexPath.row == 2 || indexPath.row == 6 || indexPath.row == 7 {
                let subVC = self.storyboard?.instantiateViewController(withIdentifier: "ViewControllerOne") as! ViewControllerOne
                subVC.isSubVC = true
                return subVC
            } else {
                return ViewControllerTwo
            }
        }
    }
}

Upvotes: 0

Larme
Larme

Reputation: 26096

In Objective-C, but the logic should be the same, that's what I would do:

CustomModel:

@property (nonatomic, strong) NSArray *subdata;
@property (nonatomic, strong) NSString *title;
@property (nonatomic, assing) BOOL isSpecial; //of course wording should be adapted to the real logic and your needs

By default on the init of a CustomModel object, isSpecial is set to NO.

ViewControllers:

@property (nonatomic, strong), NSArray *tableViewDataSource;

The tableViewDataSource of MainViewController should be like this:

- CustomData isSpecial: NO
             subdata = [CustomData11, CustomData12...] 
- CustomData isSpecial: NO
             subdata = [CustomData21, CustomData22...] 
- CustomData isSpecial: YES
             subdata = [CustomData31, CustomData32...] 
- CustomData isSpecial: NO
             subdata = [CustomData41, CustomData42...] 
- CustomData isSpecial: NO
             subdata = [CustomData51, CustomData52...] 
- CustomData isSpecial: NO
             subdata = [CustomData61, CustomData62...] 
- CustomData isSpecial: YES
             subdata = [CustomData71, CustomData72...] 
- CustomData isSpecial: YES
             subdata = [CustomData81, CustomData82...] 
- CustomData isSpecial: NO
             subdata = [CustomData91, CustomData92...] 

Logic for deciding on next viewcontroller: You can use either Segue or manual instantiation/push, but I'd suggest you keep doing the same method for both of thems

CustomModel *selectedData = tableViewDataSource[indexPath.row]
if ([selectedData isSpecial])
{
    //Go to SubVC, ie. another MainVC
    SubViewController *nextVC = ... //segue.destinationViewController looping on itself or storyboard.instatianteViewController instantiating another MainViewController
    nextVC.data = selectedData.subdata;
}
else
{
    //Go VC2
    ViewController *nextVC = ... //segue.destinationViewController or storyboard.instatianteViewController...
    nextVC.data = selectedData.subdata;
}

Upvotes: 0

Oscar Apeland
Oscar Apeland

Reputation: 6662

OK! What's happening here is that you have segues connected to your tableView, and that causes issues when you also push a new view controller in your code.

From the code you've posted here, I think just removing the segues from your storyboard would solve the issue. Doing it in only code as you're already doing in tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) is perfectly fine.

You can delete segues by clicking on them and pressing backspace, view controller can float for themselves in the storyboard without being connected by segues.

Upvotes: 0

Related Questions